diff --git a/app_config.h b/app_config.h new file mode 120000 index 0000000..299bb98 --- /dev/null +++ b/app_config.h @@ -0,0 +1 @@ +config.h \ No newline at end of file diff --git a/ciderpress/nufxlib/Archive.c b/ciderpress/nufxlib/Archive.c index 616573f..7c97cb3 100644 --- a/ciderpress/nufxlib/Archive.c +++ b/ciderpress/nufxlib/Archive.c @@ -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); diff --git a/ciderpress/nufxlib/COPYING-LIB b/ciderpress/nufxlib/COPYING-LIB new file mode 100644 index 0000000..d27a925 --- /dev/null +++ b/ciderpress/nufxlib/COPYING-LIB @@ -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. + diff --git a/ciderpress/nufxlib/INSTALL b/ciderpress/nufxlib/INSTALL new file mode 100644 index 0000000..50dbe43 --- /dev/null +++ b/ciderpress/nufxlib/INSTALL @@ -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. + diff --git a/ciderpress/nufxlib/Makefile b/ciderpress/nufxlib/Makefile index f6f4864..b51181e 100644 --- a/ciderpress/nufxlib/Makefile +++ b/ciderpress/nufxlib/Makefile @@ -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 + diff --git a/ciderpress/nufxlib/Makefile.in b/ciderpress/nufxlib/Makefile.in new file mode 100644 index 0000000..66c2ae4 --- /dev/null +++ b/ciderpress/nufxlib/Makefile.in @@ -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 + diff --git a/ciderpress/nufxlib/Makefile.msc b/ciderpress/nufxlib/Makefile.msc new file mode 100644 index 0000000..8c2bb90 --- /dev/null +++ b/ciderpress/nufxlib/Makefile.msc @@ -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) + diff --git a/ciderpress/nufxlib/NOTES.md b/ciderpress/nufxlib/NOTES.md new file mode 100644 index 0000000..caccd89 --- /dev/null +++ b/ciderpress/nufxlib/NOTES.md @@ -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. + diff --git a/ciderpress/nufxlib/README.txt b/ciderpress/nufxlib/README.txt new file mode 100644 index 0000000..3e246ee --- /dev/null +++ b/ciderpress/nufxlib/README.txt @@ -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. + diff --git a/ciderpress/nufxlib/SysDefs.h b/ciderpress/nufxlib/SysDefs.h index fb0de64..aad4713 100644 --- a/ciderpress/nufxlib/SysDefs.h +++ b/ciderpress/nufxlib/SysDefs.h @@ -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 diff --git a/ciderpress/nufxlib/config.guess b/ciderpress/nufxlib/config.guess new file mode 100644 index 0000000..ad5281e --- /dev/null +++ b/ciderpress/nufxlib/config.guess @@ -0,0 +1,1466 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-08-03' + +# This file 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., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerppc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm:riscos:*:*|arm:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit ;; + crisv32:Linux:*:*) + echo crisv32-axis-linux-gnu + exit ;; + frv:Linux:*:*) + echo frv-unknown-linux-gnu + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + or32:Linux:*:*) + echo or32-unknown-linux-gnu + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && { + echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + exit + } + test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + case $UNAME_PROCESSOR in + *86) UNAME_PROCESSOR=i686 ;; + unknown) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NSE-?:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/ciderpress/nufxlib/config.h.in b/ciderpress/nufxlib/config.h.in new file mode 100644 index 0000000..dd294e2 --- /dev/null +++ b/ciderpress/nufxlib/config.h.in @@ -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 doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if your declares struct tm. */ +#undef TM_IN_SYS_TIME + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if 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 header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_UTIME_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the 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 + diff --git a/ciderpress/nufxlib/config.sub b/ciderpress/nufxlib/config.sub new file mode 100644 index 0000000..1c366df --- /dev/null +++ b/ciderpress/nufxlib/config.sub @@ -0,0 +1,1579 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-07-08' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file 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., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ + kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | bfin \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | m32r | m32rle | m68000 | m68k | m88k | maxq | mcore \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | ms1 \ + | msp430 \ + | ns16k | ns32k \ + | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xscale | xscalee[bl] | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m32c) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | ms1-* \ + | msp430-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xps100-* | xscale-* | xscalee[bl]-* \ + | xstormy16-* | xtensa-* \ + | ymp-* \ + | z8k-*) + ;; + m32c-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16c) + basic_machine=cr16c-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -zvmoe) + os=-zvmoe + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/ciderpress/nufxlib/configure b/ciderpress/nufxlib/configure new file mode 100755 index 0000000..f459119 --- /dev/null +++ b/ciderpress/nufxlib/configure @@ -0,0 +1,5503 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file="NufxLibPriv.h" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +SHARE_FLAGS +BUILD_FLAGS +EGREP +GREP +CPP +RANLIB +SET_MAKE +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_sq +enable_lzw +enable_lzc +enable_deflate +enable_bzip2 +enable_dmalloc +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --disable-sq disable SQ compression + --disable-lzw disable LZW/1 and LZW/2 compression + --disable-lzc disable 12- and 16-bit LZC compression + --disable-deflate disable zlib deflate compression + --enable-bzip2 enable libbz2 bzip2 compression + --enable-dmalloc do dmalloc stuff + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + eval "$3=yes" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_type + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_config_headers="$ac_config_headers config.h" + + +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \ + sys/utime.h unistd.h utime.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +LIBS="" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5 +$as_echo_n "checking for an ANSI C-conforming const... " >&6; } +if ${ac_cv_c_const+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + +#ifndef __cplusplus + /* Ultrix mips cc rejects this sort of thing. */ + typedef int charset[2]; + const charset cs = { 0, 0 }; + /* SunOS 4.1.1 cc rejects this. */ + char const *const *pcpcc; + char **ppc; + /* NEC SVR4.0.2 mips cc rejects this. */ + struct point {int x, y;}; + static struct point const zero = {0,0}; + /* AIX XL C 1.02.0.0 rejects this. + It does not let you subtract one const X* pointer from another in + an arm of an if-expression whose if-part is not a constant + expression */ + const char *g = "string"; + pcpcc = &g + (g ? g-g : 0); + /* HPUX 7.0 cc rejects these. */ + ++pcpcc; + ppc = (char**) pcpcc; + pcpcc = (char const *const *) ppc; + { /* SCO 3.2v4 cc rejects this sort of thing. */ + char tx; + char *t = &tx; + char const *s = 0 ? (char *) 0 : (char const *) 0; + + *t++ = 0; + if (s) return 0; + } + { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ + int x[] = {25, 17}; + const int *foo = &x[0]; + ++foo; + } + { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ + typedef const int *iptr; + iptr p = 0; + ++p; + } + { /* AIX XL C 1.02.0.0 rejects this sort of thing, saying + "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ + struct s { int j; const int *ap[3]; } bx; + struct s *b = &bx; b->j = 5; + } + { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; + if (!foo) return 0; + } + return !cs[0] && !zero.x; +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_const=yes +else + ac_cv_c_const=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5 +$as_echo "$ac_cv_c_const" >&6; } +if test $ac_cv_c_const = no; then + +$as_echo "#define const /**/" >>confdefs.h + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 +$as_echo_n "checking for inline... " >&6; } +if ${ac_cv_c_inline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __cplusplus +typedef int foo_t; +static $ac_kw foo_t static_foo () {return 0; } +$ac_kw foo_t foo () {return 0; } +#endif + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_inline=$ac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + test "$ac_cv_c_inline" != no && break +done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 +$as_echo "$ac_cv_c_inline" >&6; } + +case $ac_cv_c_inline in + inline | yes) ;; + *) + case $ac_cv_c_inline in + no) ac_val=;; + *) ac_val=$ac_cv_c_inline;; + esac + cat >>confdefs.h <<_ACEOF +#ifndef __cplusplus +#define inline $ac_val +#endif +_ACEOF + ;; +esac + +ac_fn_c_check_type "$LINENO" "mode_t" "ac_cv_type_mode_t" "$ac_includes_default" +if test "x$ac_cv_type_mode_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define mode_t int +_ACEOF + +fi + +ac_fn_c_check_type "$LINENO" "off_t" "ac_cv_type_off_t" "$ac_includes_default" +if test "x$ac_cv_type_off_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define off_t long int +_ACEOF + +fi + +ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" +if test "x$ac_cv_type_size_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define size_t unsigned int +_ACEOF + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5 +$as_echo_n "checking whether struct tm is in sys/time.h or time.h... " >&6; } +if ${ac_cv_struct_tm+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include + +int +main () +{ +struct tm tm; + int *p = &tm.tm_sec; + return !p; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_struct_tm=time.h +else + ac_cv_struct_tm=sys/time.h +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_tm" >&5 +$as_echo "$ac_cv_struct_tm" >&6; } +if test $ac_cv_struct_tm = sys/time.h; then + +$as_echo "#define TM_IN_SYS_TIME 1" >>confdefs.h + +fi + + +for ac_func in fdopen ftruncate memmove mkdir mkstemp mktime timelocal \ + localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if snprintf is declared" >&5 +$as_echo_n "checking if snprintf is declared... " >&6; } +if ${nufxlib_cv_snprintf_in_header+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "snprintf" >/dev/null 2>&1; then : + nufxlib_cv_snprintf_in_header=yes +else + nufxlib_cv_snprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_snprintf_in_header = "yes"; then + $as_echo "#define SNPRINTF_DECLARED 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_snprintf_in_header" >&5 +$as_echo "$nufxlib_cv_snprintf_in_header" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if vsnprintf is declared" >&5 +$as_echo_n "checking if vsnprintf is declared... " >&6; } +if ${nufxlib_cv_vsnprintf_in_header+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "vsnprintf" >/dev/null 2>&1; then : + nufxlib_cv_vsnprintf_in_header=yes +else + nufxlib_cv_vsnprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_vsnprintf_in_header = "yes"; then + $as_echo "#define VSNPRINTF_DECLARED 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_vsnprintf_in_header" >&5 +$as_echo "$nufxlib_cv_vsnprintf_in_header" >&6; } + +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + + +SHARE_FLAGS='-shared' +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + 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 + SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"' +fi + + + + +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' + + + + + + fi +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if sprintf returns int" >&5 +$as_echo_n "checking if sprintf returns int... " >&6; } +if ${nufxlib_cv_sprintf_returns_int+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "$cross_compiling" = yes; then : + nufxlib_cv_sprintf_returns_int=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + int main(void) + { + int count; + char buf[8]; + count = sprintf(buf, "123"); /* should return three */ + exit(count != 3); + } + +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + nufxlib_cv_sprintf_returns_int=yes +else + nufxlib_cv_sprintf_returns_int=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + +fi + + +if test $nufxlib_cv_sprintf_returns_int = "yes"; then + $as_echo "#define SPRINTF_RETURNS_INT 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_sprintf_returns_int" >&5 +$as_echo "$nufxlib_cv_sprintf_returns_int" >&6; } + + +# Check whether --enable-sq was given. +if test "${enable_sq+set}" = set; then : + enableval=$enable_sq; +else + enable_sq=yes +fi + +if test $enable_sq = "yes"; then + $as_echo "#define ENABLE_SQ 1" >>confdefs.h + +fi + +# Check whether --enable-lzw was given. +if test "${enable_lzw+set}" = set; then : + enableval=$enable_lzw; +else + enable_lzw=yes +fi + +if test $enable_lzw = "yes"; then + $as_echo "#define ENABLE_LZW 1" >>confdefs.h + +fi + +# Check whether --enable-lzc was given. +if test "${enable_lzc+set}" = set; then : + enableval=$enable_lzc; +else + enable_lzc=yes +fi + +if test $enable_lzc = "yes"; then + $as_echo "#define ENABLE_LZC 1" >>confdefs.h + +fi + +# Check whether --enable-deflate was given. +if test "${enable_deflate+set}" = set; then : + enableval=$enable_deflate; +else + enable_deflate=yes +fi + +if test $enable_deflate = "yes"; then + got_zlibh=false + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for deflate in -lz" >&5 +$as_echo_n "checking for deflate in -lz... " >&6; } +if ${ac_cv_lib_z_deflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char deflate (); +int +main () +{ +return deflate (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_z_deflate=yes +else + ac_cv_lib_z_deflate=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_deflate" >&5 +$as_echo "$ac_cv_lib_z_deflate" >&6; } +if test "x$ac_cv_lib_z_deflate" = xyes; then : + got_libz=true +else + got_libz=false +fi + + if $got_libz; then + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + got_zlibh=true LIBS="$LIBS -lz" +fi + + + fi + if $got_zlibh; then + echo " (found libz and zlib.h, enabling deflate)" + $as_echo "#define ENABLE_DEFLATE 1" >>confdefs.h + + else + echo " (couldn't find libz and zlib.h, not enabling deflate)" + fi +fi + +# Check whether --enable-bzip2 was given. +if test "${enable_bzip2+set}" = set; then : + enableval=$enable_bzip2; +else + enable_bzip2=no +fi + +if test $enable_bzip2 = "yes"; then + got_bzlibh=false + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for BZ2_bzCompress in -lbz2" >&5 +$as_echo_n "checking for BZ2_bzCompress in -lbz2... " >&6; } +if ${ac_cv_lib_bz2_BZ2_bzCompress+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lbz2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char BZ2_bzCompress (); +int +main () +{ +return BZ2_bzCompress (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_bz2_BZ2_bzCompress=yes +else + ac_cv_lib_bz2_BZ2_bzCompress=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bz2_BZ2_bzCompress" >&5 +$as_echo "$ac_cv_lib_bz2_BZ2_bzCompress" >&6; } +if test "x$ac_cv_lib_bz2_BZ2_bzCompress" = xyes; then : + got_libbz=true +else + got_libbz=false +fi + + if $got_libbz; then + ac_fn_c_check_header_mongrel "$LINENO" "bzlib.h" "ac_cv_header_bzlib_h" "$ac_includes_default" +if test "x$ac_cv_header_bzlib_h" = xyes; then : + got_bzlibh=true LIBS="$LIBS -lbz2" +fi + + + fi + if $got_bzlibh; then + echo " (found libbz2 and bzlib.h, enabling bzip2)" + $as_echo "#define ENABLE_BZIP2 1" >>confdefs.h + + else + echo " (couldn't find libbz2 and bzlib.h, not enabling bzip2)" + fi +fi + + +# Check whether --enable-dmalloc was given. +if test "${enable_dmalloc+set}" = set; then : + enableval=$enable_dmalloc; echo "--- enabling dmalloc"; + LIBS="$LIBS -L/usr/local/lib -ldmalloc"; $as_echo "#define USE_DMALLOC 1" >>confdefs.h + +fi + + +ac_config_files="$ac_config_files Makefile samples/Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "samples/Makefile") CONFIG_FILES="$CONFIG_FILES samples/Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi + ;; + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/ciderpress/nufxlib/configure.in b/ciderpress/nufxlib/configure.in new file mode 100644 index 0000000..46df1a4 --- /dev/null +++ b/ciderpress/nufxlib/configure.in @@ -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 + 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) diff --git a/ciderpress/nufxlib/nufxlib.def b/ciderpress/nufxlib/nufxlib.def new file mode 100644 index 0000000..ad33e3e --- /dev/null +++ b/ciderpress/nufxlib/nufxlib.def @@ -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 diff --git a/ciderpress/nufxlib/nufxlib.vcxproj b/ciderpress/nufxlib/nufxlib.vcxproj new file mode 100644 index 0000000..4f6f815 --- /dev/null +++ b/ciderpress/nufxlib/nufxlib.vcxproj @@ -0,0 +1,118 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C48AE53B-3DCB-43B1-9207-B7C5B6BB78AF} + Win32Proj + nufxlib + + + + DynamicLibrary + true + v143 + Unicode + Dynamic + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {b66109f4-217b-43c0-86aa-eb55657e5ac0} + + + + + + \ No newline at end of file diff --git a/ciderpress/nufxlib/nufxlib.vcxproj.filters b/ciderpress/nufxlib/nufxlib.vcxproj.filters new file mode 100644 index 0000000..816c720 --- /dev/null +++ b/ciderpress/nufxlib/nufxlib.vcxproj.filters @@ -0,0 +1,102 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/diskimg/ASPI.cpp b/diskimg/ASPI.cpp new file mode 100644 index 0000000..5bd91d5 --- /dev/null +++ b/diskimg/ASPI.cpp @@ -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*/ diff --git a/diskimg/ASPI.h b/diskimg/ASPI.h new file mode 100644 index 0000000..a6223c5 --- /dev/null +++ b/diskimg/ASPI.h @@ -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*/ diff --git a/diskimg/CFFA.cpp b/diskimg/CFFA.cpp new file mode 100644 index 0000000..a25b45f --- /dev/null +++ b/diskimg/CFFA.cpp @@ -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; +} diff --git a/diskimg/CMakeLists.txt b/diskimg/CMakeLists.txt new file mode 100644 index 0000000..4ec63de --- /dev/null +++ b/diskimg/CMakeLists.txt @@ -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} +) + + + diff --git a/diskimg/CPM.cpp b/diskimg/CPM.cpp new file mode 100644 index 0000000..ea68b85 --- /dev/null +++ b/diskimg/CPM.cpp @@ -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; +} diff --git a/diskimg/CP_WNASPI32.H b/diskimg/CP_WNASPI32.H new file mode 100644 index 0000000..5695f9b --- /dev/null +++ b/diskimg/CP_WNASPI32.H @@ -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 diff --git a/diskimg/CP_ntddscsi.h b/diskimg/CP_ntddscsi.h new file mode 100644 index 0000000..5ac6a45 --- /dev/null +++ b/diskimg/CP_ntddscsi.h @@ -0,0 +1,184 @@ +/* + * ntddscsi.h + * + * SCSI port IOCTL interface. + * + * This file is part of the w32api package. + * + * Contributors: + * Created by Casper S. Hornstrup + * + * 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*/ diff --git a/diskimg/Container.cpp b/diskimg/Container.cpp new file mode 100644 index 0000000..93a5fc2 --- /dev/null +++ b/diskimg/Container.cpp @@ -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; +} diff --git a/diskimg/DDD.cpp b/diskimg/DDD.cpp new file mode 100644 index 0000000..b0c7cb7 --- /dev/null +++ b/diskimg/DDD.cpp @@ -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; +} diff --git a/diskimg/DIUtil.cpp b/diskimg/DIUtil.cpp new file mode 100644 index 0000000..eae7d3e --- /dev/null +++ b/diskimg/DIUtil.cpp @@ -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 diff --git a/diskimg/DOS33.cpp b/diskimg/DOS33.cpp new file mode 100644 index 0000000..d41ad4c --- /dev/null +++ b/diskimg/DOS33.cpp @@ -0,0 +1,3400 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Implementation of DiskFSDOS33 and A2FileDOS classes. + * + * Works for DOS 3.2 and "wide DOS" as well. + * + * BUG: does not keep VolumeUsage up to date. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + + +/* + * =========================================================================== + * DiskFSDOS33 + * =========================================================================== + */ + +const int kMaxSectors = 32; + +const int kSctSize = 256; +/* do we need a way to override these? */ +const int kVTOCTrack = 17; +const int kVTOCSector = 0; +const int kCatalogEntryOffset = 0x0b; // first entry in cat sect starts here +const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries +const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector +const int kEntryDeleted = 0xff; // this is used for track# of 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 DOS3.3-ness. + * + * Some notes on tricky disks... + * + * DISK019B (Ultima II player master) has a copy of the VTOC in track 11 + * sector 1, which causes a loop back to track 11 sector f. We may want + * to be clever here and allow it, but we have to be careful because + * we must be similarly clever in the VTOC read routines. (Need a more + * sophisticated loop detector, since a loop will crank our "foundGood" up.) + * + * DISK038B (Congo Bongo) has some "crack" titles and a valid VTOC, but not + * much else. Could allow it if the user explicitly told us to use DOS33, + * but it's a little thin. + * + * DISK112B.X (Ultima I player master) has a catalog that jumps around a lot. + * It's perfectly valid, but we don't really detect it properly. Forcing + * DOS interpretation should be acceptable. + * + * DISK175A (Standing Stones) has an extremely short but valid catalog track. + * + * DISK198B (Aliens+docs) gets 3 and bails with a self-reference. + */ +static DIError TestImage(DiskImg* pImg, 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 = pImg->ReadTrackSectorSwapped(kVTOCTrack, kVTOCSector, + sctBuf, imageOrder, DiskImg::kSectorOrderDOS); + 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 <= DiskFSDOS33::kMaxTracks) || + !(numSectors == 13 || numSectors == 16 || numSectors == 32) || + !(catTrack < numTracks && catSect < numSectors) || + 0) + { + LOGI(" DOS header test failed (order=%d)", imageOrder); + 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 = pImg->ReadTrackSectorSwapped(catTrack, catSect, sctBuf, + imageOrder, DiskImg::kSectorOrderDOS); + 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(" DOS 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) { + /* possible cause: LF->CR conversion screws up link to sector $0a */ + dierr = kDIErrDirectoryLoop; + LOGI(" DOS directory links cause a loop (order=%d)", imageOrder); + goto bail; + } + + LOGI(" DOS foundGood=%d order=%d", foundGood, imageOrder); + *pGoodCount = foundGood; + +bail: + return dierr; +} + +/* + * Test to see if the image is a DOS 3.2 or DOS 3.3 disk. + */ +/*static*/ DIError DiskFSDOS33::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 >= 4 || + (leniency == kLeniencyVery && bestCount >= 2)) + { + LOGI(" DOS test: bestCount=%d for order=%d", bestCount, bestOrder); + assert(bestOrder != DiskImg::kSectorOrderUnknown); + *pOrder = bestOrder; + *pFormat = DiskImg::kFormatDOS33; + if (pImg->GetNumSectPerTrack() == 13) + *pFormat = DiskImg::kFormatDOS32; + return kDIErrNone; + } + + LOGI(" DOS33 didn't find valid DOS3.2 or DOS3.3"); + 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 DiskFSDOS33::Initialize(InitMode initMode) +{ + DIError dierr = kDIErrNone; + + fVolumeUsage.Create(fpImg->GetNumTracks(), fpImg->GetNumSectPerTrack()); + + dierr = ReadVTOC(); + if (dierr != kDIErrNone) + goto bail; + //DumpVTOC(); + + dierr = ScanVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + if (initMode == kInitHeaderOnly) { + LOGI(" DOS - headerOnly set, skipping file load"); + goto bail; + } + + /* 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; + + /* mark DOS tracks appropriately */ + FixVolumeUsageMap(); + + fDiskIsGood = CheckDiskIsGood(); + + fVolumeUsage.Dump(); + +// A2File* pFile; +// pFile = GetNextFile(NULL); +// while (pFile != NULL) { +// pFile->Dump(); +// pFile = GetNextFile(pFile); +// } + +bail: + return dierr; +} + +/* + * Read some fields from the disk Volume Table of Contents. + */ +DIError DiskFSDOS33::ReadVTOC(void) +{ + DIError dierr; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + fFirstCatTrack = fVTOC[0x01]; + fFirstCatSector = fVTOC[0x02]; + fVTOCVolumeNumber = fVTOC[0x06]; + fVTOCNumTracks = fVTOC[0x34]; + fVTOCNumSectors = fVTOC[0x35]; + + if (fFirstCatTrack >= fpImg->GetNumTracks()) + return kDIErrBadDiskImage; + if (fFirstCatSector >= fpImg->GetNumSectPerTrack()) + return kDIErrBadDiskImage; + + if (fVTOCNumTracks != fpImg->GetNumTracks()) { + LOGI(" DOS33 warning: VTOC numtracks %d vs %ld", + fVTOCNumTracks, fpImg->GetNumTracks()); + } + if (fVTOCNumSectors != fpImg->GetNumSectPerTrack()) { + LOGI(" DOS33 warning: VTOC numsect %d vs %d", + fVTOCNumSectors, fpImg->GetNumSectPerTrack()); + } + + // call SetDiskVolumeNum with the appropriate thing + UpdateVolumeNum(); + +bail: + FreeVolBitmap(); + return dierr; +} + +/* + * Call this if fpImg's volume num (derived from nibble formats) or + * the VTOC's volume number changes. + */ +void DiskFSDOS33::UpdateVolumeNum(void) +{ + /* use the sector-embedded volume number, if available */ + if (fpImg->GetDOSVolumeNum() == DiskImg::kVolumeNumNotSet) + SetDiskVolumeNum(fVTOCVolumeNumber); + else + SetDiskVolumeNum(fpImg->GetDOSVolumeNum()); + if (fDiskVolumeNum != fVTOCVolumeNumber) { + LOGI(" NOTE: ignoring VTOC vol (%d) in favor of embedded (%d)", + fVTOCVolumeNumber, fDiskVolumeNum); + } +} + +/* + * Set the disk volume number (fDiskVolumeNum) and derived fields. + */ +void DiskFSDOS33::SetDiskVolumeNum(int val) +{ + if (val < 0 || val > 255) { + // Actual valid range should be 1-254, but it's possible for a + // sector edit to put invalid stuff here. It's just one byte + // though, so 0-255 should be guaranteed. + assert(false); + return; + } + fDiskVolumeNum = val; + sprintf(fDiskVolumeName, "DOS%03d", fDiskVolumeNum); + if (fpImg->GetFSFormat() == DiskImg::kFormatDOS32) + sprintf(fDiskVolumeID, "DOS 3.2 Volume %03d", fDiskVolumeNum); + else + sprintf(fDiskVolumeID, "DOS 3.3 Volume %03d", fDiskVolumeNum); +} + + +/* + * Dump some VTOC fields. + */ +void DiskFSDOS33::DumpVTOC(void) +{ + + LOGI("VTOC catalog: track=%d sector=%d", + fFirstCatTrack, fFirstCatSector); + LOGI(" volnum=%d numTracks=%d numSects=%d", + fVTOCVolumeNumber, fVTOCNumTracks, fVTOCNumSectors); +} + +/* + * Update an entry in the VolumeUsage map, watching for conflicts. + */ +void DiskFSDOS33::SetSectorUsage(long track, long sector, + VolumeUsage::ChunkPurpose purpose) +{ + VolumeUsage::ChunkState cstate; + + //LOGI(" DOS setting usage %d,%d to %d", track, sector, purpose); + + fVolumeUsage.GetChunkState(track, sector, &cstate); + if (cstate.isUsed) { + cstate.purpose = VolumeUsage::kChunkPurposeConflict; +// LOGI(" DOS conflicting uses for t=%d s=%d", track, sector); + } else { + cstate.isUsed = true; + cstate.purpose = purpose; + } + fVolumeUsage.SetChunkState(track, sector, &cstate); +} + +/* + * Examine the volume bitmap, setting fields in the VolumeUsage map + * as appropriate. We mark "isMarkedUsed", but leave "isUsed" clear. The + * "isUsed" flag gets set by the DOS catalog track processor and the file + * scanners. + * + * We can't mark the DOS tracks, because there's no reliable way to tell by + * looking at a DOS disk whether it has a bootable DOS image. It's possible + * the tracks are marked in-use because files are stored there. Some + * tweaked versions of DOS freed up a few sectors on track 2, so partial + * allocation isn't a good indicator. + * + * What we have to do is wait until we have all the information for the + * various files, and mark the tracks as owned by DOS if nobody else + * claims them. + */ +DIError DiskFSDOS33::ScanVolBitmap(void) +{ + DIError dierr; + VolumeUsage::ChunkState cstate; + char freemap[32+1] = "--------------------------------"; + + cstate.isUsed = false; + cstate.isMarkedUsed = true; + cstate.purpose = (VolumeUsage::ChunkPurpose) 0; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + LOGI(" map 0123456789abcdef"); + + for (int i = 0; i < kMaxTracks; i++) { + uint32_t val, origVal; + int bit; + + val = (uint32_t) fVTOC[0x38 + i*4] << 24; + val |= (uint32_t) fVTOC[0x39 + i*4] << 16; + val |= (uint32_t) fVTOC[0x3a + i*4] << 8; + val |= (uint32_t) fVTOC[0x3b + i*4]; + origVal = val; + + /* init the VolumeUsage stuff */ + for (bit = fpImg->GetNumSectPerTrack()-1; bit >= 0; bit--) { + freemap[bit] = val & 0x80000000 ? '.' : 'X'; + + if (i < fpImg->GetNumTracks() && !(val & 0x80000000)) { + /* mark the sector as in-use */ + if (fVolumeUsage.SetChunkState(i, bit, &cstate) != kDIErrNone) { + assert(false); + } + } + val <<= 1; + } + LOGI(" %2d: %s (0x%08x)", i, freemap, origVal); + } + + /* we know the VTOC is used, so mark it now */ + SetSectorUsage(kVTOCTrack, kVTOCSector, VolumeUsage::kChunkPurposeVolumeDir); + +bail: + FreeVolBitmap(); + return dierr; +} + + +/* + * Load the VTOC into the buffer. + */ +DIError DiskFSDOS33::LoadVolBitmap(void) +{ + DIError dierr; + + assert(!fVTOCLoaded); + + dierr = fpImg->ReadTrackSector(kVTOCTrack, kVTOCSector, fVTOC); + if (dierr != kDIErrNone) + return dierr; + + fVTOCLoaded = true; + return kDIErrNone; +} + +/* + * Save our copy of the volume bitmap. + */ +DIError DiskFSDOS33::SaveVolBitmap(void) +{ + if (!fVTOCLoaded) { + assert(false); + return kDIErrNotReady; + } + + return fpImg->WriteTrackSector(kVTOCTrack, kVTOCSector, fVTOC); +} + +/* + * Throw away the volume bitmap, discarding any unsaved changes. + * + * It's okay to call this if the bitmap isn't loaded. + */ +void DiskFSDOS33::FreeVolBitmap(void) +{ + fVTOCLoaded = false; + +#ifdef _DEBUG + memset(fVTOC, 0x99, sizeof(fVTOC)); +#endif +} + +/* + * Return entry N from the VTOC. + */ +inline uint32_t DiskFSDOS33::GetVTOCEntry(const uint8_t* pVTOC, long track) const +{ + uint32_t val; + val = (uint32_t) pVTOC[0x38 + track*4] << 24; + val |= (uint32_t) pVTOC[0x39 + track*4] << 16; + val |= (uint32_t) pVTOC[0x3a + track*4] << 8; + val |= (uint32_t) pVTOC[0x3b + track*4]; + + return val; +} + +/* + * Allocate a new sector from the unused pool. + * + * Only touches the in-memory copy. + */ +DIError DiskFSDOS33::AllocSector(TrackSector* pTS) +{ + uint32_t val; + uint32_t mask; + long track, numSectPerTrack; + + /* we could compute "mask", but it's faster and easier to do this */ + numSectPerTrack = GetDiskImg()->GetNumSectPerTrack(); + if (numSectPerTrack == 13) + mask = 0xfff80000; + else if (numSectPerTrack == 16) + mask = 0xffff0000; + else if (numSectPerTrack == 32) + mask = 0xffffffff; + else { + assert(false); + return kDIErrInternal; + } + + /* + * Start by finding a track with a free sector. We know it's free + * because the bits aren't all zero. + * + * In theory we don't need "mask", because the DOS format routine is + * good about leaving the unused bits clear, and nobody else disturbs + * them. However, it's best not to rely on it. + */ + for (track = kVTOCTrack; track > 0; track--) { + val = GetVTOCEntry(fVTOC, track); + if ((val & mask) != 0) + break; + } + if (track == 0) { + long numTracks = GetDiskImg()->GetNumTracks(); + for (track = kVTOCTrack; track < numTracks; track++) + { + val = GetVTOCEntry(fVTOC, track); + if ((val & mask) != 0) + break; + } + if (track == numTracks) { + LOGI("DOS33 AllocSector unable to find empty sector"); + return kDIErrDiskFull; + } + } + + /* + * We've got the track. Now find the first free sector. + */ + int sector; + sector = numSectPerTrack-1; + while (sector >= 0) { + if (val & 0x80000000) { + //LOGI("+++ allocating T=%d S=%d", track, sector); + SetSectorUseEntry(track, sector, true); + break; + } + + val <<= 1; + sector--; + } + if (sector < 0) { + assert(false); + return kDIErrInternal; // should not have failed + } + + /* + * Mostly for fun, update the VTOC allocation thingy. + */ + fVTOC[0x30] = (uint8_t) track; // last track where alloc happened + if (track < kVTOCTrack) + fVTOC[0x31] = 0xff; // descending + else + fVTOC[0x31] = 0x01; // ascending + + pTS->track = (char) track; + pTS->sector = (char) sector; + + return kDIErrNone; +} + +/* + * Create an in-use map for an empty disk. Sets up the VTOC map only. + * + * If "withDOS" is set, mark the first 3 tracks as in-use. + */ +DIError DiskFSDOS33::CreateEmptyBlockMap(bool withDOS) +{ + DIError dierr; + long track, sector, maxTrack; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + if (withDOS) + maxTrack = 3; + else + maxTrack = 1; + + /* + * Set each bit individually. Slower, but exercises standard functions. + * + * Clear all "in use" flags, except for track 0, track 17, and (if + * withDOS is set) tracks 1 and 2. + */ + for (track = fpImg->GetNumTracks()-1; track >= 0; track--) { + for (sector = fpImg->GetNumSectPerTrack()-1; sector >= 0; sector--) { + if (track < maxTrack || track == kVTOCTrack) + SetSectorUseEntry(track, sector, true); + else + SetSectorUseEntry(track, sector, false); + } + } + + dierr = SaveVolBitmap(); + FreeVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + return kDIErrNone; +} + +/* + * Get the state of an entry in the VTOC sector use map. + * + * Returns "true" if it's in use, "false" otherwise. + */ +bool DiskFSDOS33::GetSectorUseEntry(long track, int sector) const +{ + assert(fVTOCLoaded); + assert(track >= 0 && track < fpImg->GetNumTracks()); + assert(sector >= 0 && sector < fpImg->GetNumSectPerTrack()); + + uint32_t val, mask; + + val = GetVTOCEntry(fVTOC, track); + //val = (uint32_t) fVTOC[0x38 + track*4] << 24; + //val |= (uint32_t) fVTOC[0x39 + track*4] << 16; + //val |= (uint32_t) fVTOC[0x3a + track*4] << 8; + //val |= (uint32_t) fVTOC[0x3b + track*4]; + + /* + * The highest-numbered sector is now in the high bit. If this is a + * 16-sector disk, the high bit holds the state of sector 15. + * + * A '1' indicates the sector is free, '0' indicates it's in use. + */ + mask = 1L << (32 - fpImg->GetNumSectPerTrack() + sector); + return (val & mask) == 0; +} + +/* + * Change the state of an entry in the VTOC sector use map. + */ +void DiskFSDOS33::SetSectorUseEntry(long track, int sector, bool inUse) +{ + assert(fVTOCLoaded); + assert(track >= 0 && track < fpImg->GetNumTracks()); + assert(sector >= 0 && sector < fpImg->GetNumSectPerTrack()); + + uint32_t val, mask; + + val = GetVTOCEntry(fVTOC, track); + + /* highest sector is always in the high bit */ + mask = 1L << (32 - fpImg->GetNumSectPerTrack() + sector); + if (inUse) + val &= ~mask; + else + val |= mask; + + fVTOC[0x38 + track*4] = (uint8_t) (val >> 24); + fVTOC[0x39 + track*4] = (uint8_t) (val >> 16); + fVTOC[0x3a + track*4] = (uint8_t) (val >> 8); + fVTOC[0x3b + track*4] = (uint8_t) val; +} + + +/* + * Get the amount of free space remaining. + */ +DIError DiskFSDOS33::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const +{ + DIError dierr; + long track, sector, freeSectors; + + dierr = const_cast(this)->LoadVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + freeSectors = 0; + for (track = GetDiskImg()->GetNumTracks()-1; track >= 0; track--) { + for (sector = GetDiskImg()->GetNumSectPerTrack()-1; sector >= 0; sector--) + { + if (!GetSectorUseEntry(track, sector)) + freeSectors++; + } + } + + *pTotalUnits = fpImg->GetNumTracks() * fpImg->GetNumSectPerTrack(); + *pFreeUnits = freeSectors; + *pUnitSize = kSectorSize; + + const_cast(this)->FreeVolBitmap(); + return kDIErrNone; +} + + +/* + * Fix up the DOS tracks. + * + * Any sectors marked used but not actually in use by a file are marked as + * in use by the system. We have to be somewhat careful here because some + * disks had DOS removed to add space, un-set the last few sectors of track 2 + * that weren't actually used by DOS, or did some other funky thing. + */ +void DiskFSDOS33::FixVolumeUsageMap(void) +{ + VolumeUsage::ChunkState cstate; + int track, sector; + + for (track = 0; track < 3; track++) { + for (sector = 0; sector < fpImg->GetNumSectPerTrack(); sector++) { + fVolumeUsage.GetChunkState(track, sector, &cstate); + if (cstate.isMarkedUsed && !cstate.isUsed) { + cstate.isUsed = true; + cstate.purpose = VolumeUsage::kChunkPurposeSystem; + fVolumeUsage.SetChunkState(track, sector, &cstate); + } + } + } +} + + +/* + * Read the disk's catalog. + * + * NOTE: supposedly DOS stops reading the catalog track when it finds the + * first entry with a 00 byte, which is why deleted files use ff. If so, + * it *might* make sense to mimic this behavior, though on a health disk + * we shouldn't be finding garbage anyway. + * + * Fills out "fCatalogSectors" as it works. + */ +DIError DiskFSDOS33::ReadCatalog(void) +{ + DIError dierr = kDIErrNone; + uint8_t sctBuf[kSctSize]; + int catTrack, catSect; + int iterations; + + catTrack = fFirstCatTrack; + catSect = fFirstCatSector; + iterations = 0; + + memset(fCatalogSectors, 0, sizeof(fCatalogSectors)); + + while (catTrack != 0 && catSect != 0 && iterations < kMaxCatalogSectors) + { + SetSectorUsage(catTrack, catSect, VolumeUsage::kChunkPurposeVolumeDir); + + LOGI(" DOS33 reading catalog sector T=%d S=%d", catTrack, catSect); + dierr = fpImg->ReadTrackSector(catTrack, catSect, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* + * Watch for flaws that the DOS detector allows. + */ + if (catTrack == sctBuf[0x01] && catSect == sctBuf[0x02]) { + LOGI(" DOS detected self-reference on cat (%d,%d)", + catTrack, catSect); + break; + } + + /* + * Check the next track/sector in the chain. If the pointer is + * broken, there's a very good chance that this isn't really a + * catalog sector, so we want to bail out now. + */ + if (sctBuf[0x01] >= fpImg->GetNumTracks() || + sctBuf[0x02] >= fpImg->GetNumSectPerTrack()) + { + LOGI(" DOS bailing out early on catalog read due to funky T/S"); + break; + } + + dierr = ProcessCatalogSector(catTrack, catSect, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + fCatalogSectors[iterations].track = catTrack; + fCatalogSectors[iterations].sector = catSect; + + catTrack = sctBuf[0x01]; + catSect = sctBuf[0x02]; + + 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 DiskFSDOS33::ProcessCatalogSector(int catTrack, int catSect, + const uint8_t* sctBuf) +{ + A2FileDOS* pFile; + const uint8_t* pEntry; + int i; + + pEntry = &sctBuf[kCatalogEntryOffset]; + + for (i = 0; i < kCatalogEntriesPerSect; i++) { + if (pEntry[0x00] != kEntryUnused && pEntry[0x00] != kEntryDeleted) { + pFile = new A2FileDOS(this); + + pFile->SetQuality(A2File::kQualityGood); + + pFile->fTSListTrack = pEntry[0x00]; + pFile->fTSListSector = pEntry[0x01]; + pFile->fLocked = (pEntry[0x02] & 0x80) != 0; + switch (pEntry[0x02] & 0x7f) { + case 0x00: pFile->fFileType = A2FileDOS::kTypeText; break; + case 0x01: pFile->fFileType = A2FileDOS::kTypeInteger; break; + case 0x02: pFile->fFileType = A2FileDOS::kTypeApplesoft; break; + case 0x04: pFile->fFileType = A2FileDOS::kTypeBinary; break; + case 0x08: pFile->fFileType = A2FileDOS::kTypeS; break; + case 0x10: pFile->fFileType = A2FileDOS::kTypeReloc; break; + case 0x20: pFile->fFileType = A2FileDOS::kTypeA; break; + case 0x40: pFile->fFileType = A2FileDOS::kTypeB; break; + default: + /* some odd arrangement of bit flags? */ + LOGI(" DOS33 peculiar filetype byte 0x%02x", pEntry[0x02]); + pFile->fFileType = A2FileDOS::kTypeUnknown; + pFile->SetQuality(A2File::kQualitySuspicious); + break; + } + + memcpy(pFile->fRawFileName, &pEntry[0x03], A2FileDOS::kMaxFileName); + pFile->fRawFileName[A2FileDOS::kMaxFileName] = '\0'; + + memcpy(pFile->fFileName, &pEntry[0x03], A2FileDOS::kMaxFileName); + pFile->fFileName[A2FileDOS::kMaxFileName] = '\0'; + pFile->FixFilename(); + + pFile->fLengthInSectors = pEntry[0x21]; + pFile->fLengthInSectors |= (uint16_t) pEntry[0x22] << 8; + + 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; + + AddFileToList(pFile); + } + + pEntry += kCatalogEntrySize; + } + + return kDIErrNone; +} + +/* + * Perform consistency checks on the filesystem. + * + * Returns "true" if disk appears to be perfect, "false" otherwise. + */ +bool DiskFSDOS33::CheckDiskIsGood(void) +{ + DIError dierr; + const DiskImg* pDiskImg = GetDiskImg(); + bool result = true; + int i; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Make sure the VTOC is marked in use, or things could go badly. + * Ditto for the catalog tracks. + */ + if (!GetSectorUseEntry(kVTOCTrack, kVTOCSector)) { + fpImg->AddNote(DiskImg::kNoteWarning, "VTOC sector marked as free."); + result = false; + } + for (i = 0; i < kMaxCatalogSectors; i++) { + if (!GetSectorUseEntry(fCatalogSectors[i].track, + fCatalogSectors[i].sector)) + { + fpImg->AddNote(DiskImg::kNoteWarning, + "Catalog sector %d,%d is marked as free.", + fCatalogSectors[i].track, fCatalogSectors[i].sector); + result = false; + } + } + + /* + * Check for used blocks that aren't marked in-use. + * + * This requires that VolumeUsage be accurate. Since this function is + * only run during initial startup, any later deviation between VU and + * the block use map is irrelevant. + */ + VolumeUsage::ChunkState cstate; + long track, sector; + long notMarked, extraUsed, conflicts; + notMarked = extraUsed = conflicts = 0; + for (track = 0; track < pDiskImg->GetNumTracks(); track++) { + for (sector = 0; sector < pDiskImg->GetNumSectPerTrack(); sector++) { + dierr = fVolumeUsage.GetChunkState(track, sector, &cstate); + if (dierr != kDIErrNone) { + fpImg->AddNote(DiskImg::kNoteWarning, + "Internal volume usage error on t=%ld s=%ld.", + track, sector); + result = false; + goto bail; + } + + if (cstate.isUsed && !cstate.isMarkedUsed) + notMarked++; + if (!cstate.isUsed && cstate.isMarkedUsed) + extraUsed++; + if (cstate.purpose == VolumeUsage::kChunkPurposeConflict) + conflicts++; + } + } + if (extraUsed > 0) { + fpImg->AddNote(DiskImg::kNoteInfo, + "%ld sector%s marked used but not part of any file.", + extraUsed, extraUsed == 1 ? " is" : "s are"); + // not a problem, really + } + if (notMarked > 0) { + fpImg->AddNote(DiskImg::kNoteWarning, + "%ld sector%s used by files but not marked used.", + notMarked, notMarked == 1 ? " is" : "s are"); + result = false; + } + if (conflicts > 0) { + fpImg->AddNote(DiskImg::kNoteWarning, + "%ld sector%s used by more than one file.", + conflicts, conflicts == 1 ? " is" : "s are"); + result = false; + } + + /* + * Scan for "damaged" files 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; + } + +bail: + FreeVolBitmap(); + return result; +} + + +/* + * Run through our list of files, computing the lengths and marking file + * usage in the VolumeUsage object. + */ +DIError DiskFSDOS33::GetFileLengths(void) +{ + A2FileDOS* pFile; + TrackSector* tsList = NULL; + TrackSector* indexList = NULL; + int tsCount; + int indexCount; + + pFile = (A2FileDOS*) GetNextFile(NULL); + while (pFile != NULL) { + DIError dierr; + dierr = pFile->LoadTSList(&tsList, &tsCount, &indexList, &indexCount); + if (dierr != kDIErrNone) { + LOGI("DOS failed loading TS list for '%s'", + pFile->GetPathName()); + pFile->SetQuality(A2File::kQualityDamaged); + } else { + MarkFileUsage(pFile, tsList, tsCount, indexList, indexCount); + dierr = ComputeLength(pFile, tsList, tsCount); + if (dierr != kDIErrNone) { + LOGI("DOS unable to get length for '%s'", + pFile->GetPathName()); + pFile->SetQuality(A2File::kQualityDamaged); + } + } + + if (pFile->fLengthInSectors != indexCount + tsCount) { + LOGI("DOS NOTE: file '%s' has len-in-sect=%d but actual=%d", + pFile->GetPathName(), pFile->fLengthInSectors, + indexCount + tsCount); + // expected on sparse random-access text files + } + + delete[] tsList; + delete[] indexList; + tsList = indexList = NULL; + + pFile = (A2FileDOS*) GetNextFile(pFile); + } + + return kDIErrNone; +} + +/* + * Compute the length and starting data offset of the file. + * + * For Text, there are two situations: sequential and random. For + * sequential text files, we just need to find the first 00 byte. For + * random, there can be 00s everywhere, and in fact there can be holes + * in the T/S list. The plan: since DOS doesn't let you "truncate" a + * text file, just scan the last sector for 00. The length is the + * number of previous T/S entries * 256 plus the sector offset. + * --> This does the wrong thing for random-access text files, which + * need to retain their full length, and doesn't work right for sequential + * text files that (somehow) had their last block over-allocated. It does + * the right thing most of the time, but we either need to be more clever + * here or provide a way to override the default (bool fTrimTextFiles?). + * + * For Applesoft and Integer, the file length is stored as the first two + * bytes of the file. + * + * For Binary, the file length is stored in the second two bytes (after + * the two-byte address). Some files (with low-memory loaders) used a + * fake length, and DDD 2.x sets both address and length to zero. + * + * For Reloc, S, A2, B2, and "unknown", we just multiply the sector count. + * We get an accurate sector count from the T/S list (the value in the + * directory entry might have been tampered with). + * + * To handle DDD 2.x files correctly, we need to identify them as such by + * looking for 'B' with address=0 and length=0, a T/S count of at least 8 + * (the smallest possible compression of a 35-track disk is 2385 bytes), + * and a '<' in the filename. If found, we start from offset=0 + * (because DDD Pro 1.x includes the 4 leading bytes) and include all + * sectors, we'll get the actual file plus at most 256 garbage bytes. + * + * On success, we set the following: + * pFile->fLength + * pFile->fSparseLength + * pFile->fDataOffset + */ +DIError DiskFSDOS33::ComputeLength(A2FileDOS* pFile, const TrackSector* tsList, + int tsCount) +{ + DIError dierr = kDIErrNone; + uint8_t sctBuf[kSctSize]; + + assert(pFile != NULL); + assert(tsList != NULL); + assert(tsCount >= 0); + + pFile->fDataOffset = 0; + + pFile->fAuxType = 0; + if (pFile->fFileType == A2FileDOS::kTypeApplesoft) + pFile->fAuxType = 0x0801; + /* for text files it's default record length; assume zero */ + + if (tsCount == 0) { + /* no data at all */ + pFile->fLength = 0; + } else if (pFile->fFileType == A2FileDOS::kTypeApplesoft || + pFile->fFileType == A2FileDOS::kTypeInteger || + pFile->fFileType == A2FileDOS::kTypeBinary) + { + /* read first sector and analyze it */ + //LOGI(" DOS reading first file sector"); + dierr = fpImg->ReadTrackSector(tsList[0].track, tsList[0].sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + if (pFile->fFileType == A2FileDOS::kTypeBinary) { + pFile->fAuxType = + sctBuf[0x00] | (uint16_t) sctBuf[0x01] << 8; + pFile->fLength = + sctBuf[0x02] | (uint16_t) sctBuf[0x03] << 8; + pFile->fDataOffset = 4; // take the above into account + } else { + pFile->fLength = + sctBuf[0x00] | (uint16_t) sctBuf[0x01] << 8; + pFile->fDataOffset = 2; // take the above into account + } + + if (pFile->fFileType == A2FileDOS::kTypeBinary && + pFile->fLength == 0 && pFile->fAuxType == 0 && + tsCount >= 8 && + strchr(pFile->fFileName, '<') != NULL && + strchr(pFile->fFileName, '>') != NULL) + { + LOGI(" DOS found probable DDD archive, tweaking '%s' (lis=%u)", + pFile->GetPathName(), pFile->fLengthInSectors); + //dierr = TrimLastSectorDown(pFile, tsBuf, WrapperDDD::kMaxDDDZeroCount); + //if (dierr != kDIErrNone) + // goto bail; + //LOGI(" DOS scanned DDD file '%s' to length %ld (tsCount=%d)", + // pFile->fFileName, pFile->fLength, pFile->fTSListCount); + pFile->fLength = tsCount * kSctSize; + pFile->fDataOffset = 0; + } + + /* catch bogus lengths in damaged A/I/B files */ + if (pFile->fLength > tsCount * kSctSize) { + LOGI(" DOS33 capping max len from %ld to %d in '%s'", + (long) pFile->fLength, tsCount * kSctSize, + pFile->fFileName); + pFile->fLength = tsCount * kSctSize - pFile->fDataOffset; + if (pFile->fLength < 0) // can't happen here? + pFile->fLength = 0; + + /* + * This could cause a problem, because if the user changes a 'T' + * file to 'B', the bogus file length will mark the file as + * "suspicious" and we won't allow writing to the disk (which + * makes it hard to switch the file type back). We really don't + * want to weaken this test though. + */ + pFile->SetQuality(A2File::kQualitySuspicious); + } + + } else if (pFile->fFileType == A2FileDOS::kTypeText) { + /* scan text file */ + pFile->fLength = tsCount * kSctSize; + dierr = TrimLastSectorUp(pFile, tsList[tsCount-1]); + if (dierr != kDIErrNone) + goto bail; + + LOGI(" DOS scanned text file '%s' down to %d+%ld = %ld", + pFile->fFileName, + (tsCount-1) * kSctSize, + (long)pFile->fLength - (tsCount-1) * kSctSize, + (long)pFile->fLength); + + /* TO DO: something clever to discern random access record length? */ + } else { + /* S/R/A/B: just use the TS count */ + pFile->fLength = tsCount * kSctSize; + } + + /* + * Compute the sparse length for random-access text files. + */ + int i, sparseCount; + sparseCount = 0; + for (i = 0; i < tsCount; i++) { + if (tsList[i].track == 0 && tsList[i].sector == 0) + sparseCount++; + } + pFile->fSparseLength = pFile->fLength - sparseCount * kSctSize; + if (pFile->fSparseLength == -pFile->fDataOffset) { + /* + * This can happen for a completely sparse file. Looks sort of + * stupid to have a length of "-4", so force it to zero. + */ + pFile->fSparseLength = 0; + } + +bail: + return dierr; +} + +/* + * Trim the zeroes off the end of the last sector. We begin at the start + * of the sector and stop at the first zero found. + * + * Modifies pFile->fLength, which should be set to a roughly accurate + * value on entry. + * + * The caller should endeavor to strip out T=0 S=0 entries that come after + * the body of the file. They're valid in the middle for random-access + * text files. + */ +DIError DiskFSDOS33::TrimLastSectorUp(A2FileDOS* pFile, TrackSector lastTS) +{ + DIError dierr; + uint8_t sctBuf[kSctSize]; + int i; + + if (lastTS.track == 0) { + /* happens on files with lots of "sparse" space at the end */ + return kDIErrNone; + } + + //LOGI(" DOS reading LAST file sector"); + dierr = fpImg->ReadTrackSector(lastTS.track, lastTS.sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* start with EOF equal to previous sectors */ + pFile->fLength -= kSctSize; + for (i = 0; i < kSctSize; i++) { + if (sctBuf[i] == 0x00) + break; + else + pFile->fLength++; + } + +bail: + return dierr; +} + +/* + * Given lists of tracks and sector for data and TS index sectors, set the + * entries in the volume usage map. + */ +void DiskFSDOS33::MarkFileUsage(A2FileDOS* pFile, TrackSector* tsList, int tsCount, + TrackSector* indexList, int indexCount) +{ + int i; + + for (i = 0; i < tsCount; i++) { + /* mark all sectors as in-use by file */ + if (tsList[i].track == 0 && tsList[i].sector == 0) { + /* sparse sector in random-access text file */ + } else { + SetSectorUsage(tsList[i].track, tsList[i].sector, + VolumeUsage::kChunkPurposeUserData); + } + } + + for (i = 0; i < indexCount; i++) { + /* mark the T/S sectors as in-use by file structures */ + SetSectorUsage(indexList[i].track, indexList[i].sector, + VolumeUsage::kChunkPurposeFileStruct); + } +} + + + +#if 0 +/* + * Trim the zeroes off the end of the last sector. We begin at the end + * of the sector and back up. + * + * It is possible (one out of between 128 and 256 times) that we have just + * the trailing zero in this sector, and we need to back up to the previous + * sector to find the actual end. We know a file can end with three zeroes + * and we suspect it might be possible to end with four, which means we could + * have between 0 and 3 zeroes in the previous sector, and between 1 and 4 + * in this sector. If we just tack on three more zeroes, we weaken our + * length test slightly, because we must allow a "slop" of up to seven bytes. + * It's a little more work, but scanning the next-to-last sector is probably + * worthwhile given the otherwise flaky nature of DDD storage. + */ +DIError +DiskFSDOS33::TrimLastSectorDown(A2FileDOS* pFile, uint16_t* tsBuf, + int maxZeroCount) +{ + DIError dierr; + uint8_t sctBuf[kSctSize]; + int i; + + //LOGI(" DOS reading LAST file sector"); + dierr = fpImg->ReadTrackSector( + pFile->TSTrack(tsBuf[pFile->fTSListCount-1]), + pFile->TSSector(tsBuf[pFile->fTSListCount-1]), + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* find the first trailing zero by finding the last non-zero */ + for (i = kSctSize-1; i >= 0; i--) { + if (sctBuf[i] != 0x00) + break; + } + if (i < 0) { + /* sector was nothing but zeroes */ + DebugBreak(); + } else { + /* peg it at 256; if it went over that, DDD would've added a sector */ + i += maxZeroCount; + if (i > kSctSize) + i = kSctSize; + pFile->fLength = (pFile->fTSListCount-1) * kSctSize + i; + } + +bail: + return dierr; +} +#endif + + +/* + * 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 DiskFSDOS33::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++; + } +} + + +/* + * Determine whether or not "name" is a valid DOS 3.3 filename. + * + * Names can be up to 30 characters and can contain absolutely anything. + * To make life easier on DOS users, we ban the use of the comma, block + * control characters and high ASCII, and don't allow completely blank + * names. Later on we will later convert to upper case, so we allow lower + * case letters here. + * + * Filenames simply pad out to 30 characters with spaces, so the only + * "invalid" character is a trailing space. Because we're using C-style + * strings, we implicitly ban the use of '\0' in the name. + */ +/*static*/ bool DiskFSDOS33::IsValidFileName(const char* name) +{ + bool nonSpace = false; + int len = 0; + + /* count letters, skipping control chars */ + while (*name != '\0') { + char ch = *name++; + + if (ch < 0x20 || ch >= 0x7f || ch == ',') + return false; + if (ch != 0x20) + nonSpace = true; + len++; + } + if (len == 0 || len > A2FileDOS::kMaxFileName) + return false; // can't be empty, can't be huge + if (!nonSpace) + return false; // must have one non-ctrl non-space char + if (*(name-1) == ' ') + return false; // no trailing spaces + + return true; +} + +/* + * Determine whether "name" is a valid volume number. + */ +/*static*/ bool DiskFSDOS33::IsValidVolumeName(const char* name) +{ + long val; + char* endp; + + val = strtol(name, &endp, 10); + if (*endp != '\0' || val < 1 || val > 254) + return false; + + return true; +} + + +/* + * Put a DOS 3.2/3.3 filesystem image on the specified DiskImg. + * + * If "volName" is "DOS", a basic DOS image will be written to the first three + * tracks of the disk, and the in-use map will be updated appropriately. + * + * It would seem at first glance that putting the volume number into the + * volume name string would make the interface more consistent with the + * rest of the filesystems. The first glance is substantially correct, but + * the DOS stuff has a separate "set volume number" interface already, used + * to deal with the various locations where volume numbers can be stored + * (2MG header, VTOC, sector address headers) in the various formats. + * + * So, instead of stuffing the volume number into "volName" and creating + * some other path for specifying "add DOS image", I continue to use the + * defined ways of setting the volume number and abuse "volName" slightly. + */ +DIError DiskFSDOS33::Format(DiskImg* pDiskImg, const char* volName) +{ + DIError dierr = kDIErrNone; + uint8_t sctBuf[256]; + bool addDOS = false; + + if (pDiskImg->GetNumTracks() < kMinTracks || + pDiskImg->GetNumTracks() > kMaxTracks) + { + LOGI(" DOS33 can't format numTracks=%ld", pDiskImg->GetNumTracks()); + return kDIErrInvalidArg; + } + if (pDiskImg->GetNumSectPerTrack() != 13 && + pDiskImg->GetNumSectPerTrack() != 16 && + pDiskImg->GetNumSectPerTrack() != 32) + { + LOGI(" DOS33 can't format sectors=%d", + pDiskImg->GetNumSectPerTrack()); + return kDIErrInvalidArg; + } + + if (volName != NULL && strcmp(volName, "DOS") == 0) { + if (pDiskImg->GetNumSectPerTrack() != 16 && + pDiskImg->GetNumSectPerTrack() != 13) + { + LOGI("NOTE: numSectPerTrack = %d, can't write DOS tracks", + pDiskImg->GetNumSectPerTrack()); + return kDIErrInvalidArg; + } + addDOS = true; + } + + /* set fpImg so calls that rely on it will work; we un-set it later */ + assert(fpImg == NULL); + SetDiskImg(pDiskImg); + + LOGI(" DOS33 formatting disk image (sectorOrder=%d)", + fpImg->GetSectorOrder()); + + /* write DOS sectors */ + dierr = fpImg->OverrideFormat(fpImg->GetPhysicalFormat(), + DiskImg::kFormatGenericDOSOrd, fpImg->GetSectorOrder()); + if (dierr != kDIErrNone) + goto bail; + + /* + * We should now zero out the disk blocks, but on a 32MB volume that can + * take a little while. The blocks are zeroed for us when a disk is + * created, so this is really only needed if we're re-formatting an + * existing disk. CiderPress currently doesn't do that, so we're going + * to skip it here. + */ +// dierr = fpImg->ZeroImage(); + LOGI(" DOS33 (not zeroing blocks)"); + + if (addDOS) { + dierr = WriteDOSTracks(pDiskImg->GetNumSectPerTrack()); + if (dierr != kDIErrNone) + goto bail; + } + + /* + * Set up the static fields in the VTOC. + */ + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + fVTOC[0x00] = 0x04; // (no reason) + fVTOC[0x01] = kVTOCTrack; // first cat track + fVTOC[0x02] = fpImg->GetNumSectPerTrack()-1; // first cat sector + fVTOC[0x03] = 3; // version + if (fpImg->GetDOSVolumeNum() == DiskImg::kVolumeNumNotSet) + fVTOC[0x06] = kDefaultVolumeNum; // VTOC volume number + else + fVTOC[0x06] = (uint8_t) fpImg->GetDOSVolumeNum(); + fVTOC[0x27] = 122; // max T/S pairs + fVTOC[0x30] = kVTOCTrack+1; // last alloc + fVTOC[0x31] = 1; // ascending + fVTOC[0x34] = (uint8_t)fpImg->GetNumTracks(); // #of tracks + fVTOC[0x35] = fpImg->GetNumSectPerTrack(); // #of sectors + fVTOC[0x36] = 0x00; // bytes/sector (lo) + fVTOC[0x37] = 0x01; // bytes/sector (hi) + if (pDiskImg->GetNumSectPerTrack() == 13) { + // minor changes for DOS 3.2 + fVTOC[0x00] = 0x02; + fVTOC[0x03] = 2; + } + + dierr = SaveVolBitmap(); + FreeVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Fill the sectors in the catalog track. + */ + int sect; + memset(sctBuf, 0, sizeof(sctBuf)); + sctBuf[0x01] = kVTOCTrack; + for (sect = fpImg->GetNumSectPerTrack()-1; sect > 1; sect--) { + sctBuf[0x02] = sect-1; + + dierr = fpImg->WriteTrackSector(kVTOCTrack, sect, sctBuf); + if (dierr != kDIErrNone) + goto bail; + } + + /* + * Generate the initial block usage map. The only entries in use are + * right at the start of the disk. + */ + CreateEmptyBlockMap(addDOS); + + /* check our work, and set some object fields, by reading what we wrote */ + dierr = ReadVTOC(); + if (dierr != kDIErrNone) { + LOGI(" GLITCH: couldn't read header we just wrote (err=%d)", dierr); + goto bail; + } + + /* don't do this -- assume they're going to call Initialize() later */ + //ScanVolBitmap(); + +bail: + SetDiskImg(NULL); // shouldn't really be set by us + return dierr; +} + +/* + * Write a DOS image into tracks 0-2. + * + * This takes the number of sectors per track as an argument so we can figure + * out which version of DOS to write. This probably ought to be an enum so + * we can specify various versions of DOS. + */ +DIError DiskFSDOS33::WriteDOSTracks(int sectPerTrack) +{ + DIError dierr = kDIErrNone; + long track, sector; + const uint8_t* buf = gDOS33Tracks; + + if (sectPerTrack == 13) { + LOGI(" DOS33 writing DOS 3.3 tracks"); + buf = gDOS32Tracks; + + for (track = 0; track < 3; track++) { + for (sector = 0; sector < 13; sector++) { + dierr = fpImg->WriteTrackSector(track, sector, buf); + if (dierr != kDIErrNone) + goto bail; + buf += kSctSize; + } + } + } else if (sectPerTrack == 16) { + LOGI(" DOS33 writing DOS 3.3 tracks"); + buf = gDOS33Tracks; + + // this should be used for 32-sector disks + + for (track = 0; track < 3; track++) { + for (sector = 0; sector < 16; sector++) { + dierr = fpImg->WriteTrackSector(track, sector, buf); + if (dierr != kDIErrNone) + goto bail; + buf += kSctSize; + } + } + } else { + LOGI(" DOS33 *not* writing DOS tracks to %d-sector disk", + sectPerTrack); + assert(false); + } + +bail: + return dierr; +} + +/* + * Normalize a DOS 3.3 path. Used when adding files from DiskArchive. + * The path may contain subdirectory components, which we need to strip away. + * + * "*pNormalizedBufLen" is used to pass in the length of the buffer and + * pass out the length of the string (should the buffer prove inadequate). + */ +DIError DiskFSDOS33::NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) +{ + DIError dierr = kDIErrNone; + char tmpBuf[A2FileDOS::kMaxFileName+1]; + int len; + + DoNormalizePath(path, fssep, tmpBuf); + len = strlen(tmpBuf)+1; + + if (*pNormalizedBufLen < len) + dierr = kDIErrDataOverrun; + else + strcpy(normalizedBuf, tmpBuf); + *pNormalizedBufLen = len; + + return dierr; +} + +/* + * Normalize a DOS 3.3 pathname. Lower case becomes upper case, control + * characters and high ASCII get stripped, and ',' becomes '_'. + * + * "outBuf" must be able to hold kMaxFileName+1 characters. + */ +void DiskFSDOS33::DoNormalizePath(const char* name, char fssep, char* outBuf) +{ + char* outp = outBuf; + const char* cp; + + /* throw out leading pathname, if any */ + if (fssep != '\0') { + cp = strrchr(name, fssep); + if (cp != NULL) + name = cp+1; + } + + while (*name != '\0' && (outp - outBuf) <= A2FileDOS::kMaxFileName) { + if (*name >= 0x20 && *name < 0x7f) { + if (*name == ',') + *outp = '_'; + else + *outp = toupper(*name); + + outp++; + } + name++; + } + *outp = '\0'; + + if (*outBuf == '\0') { + /* nothing left */ + strcpy(outBuf, "BLANK"); + } +} + +/* + * Create a file on a DOS 3.2/3.3 disk. + * + * The file will be created with an empty T/S list. + * + * It is not possible to set the aux type here. Aux types only apply to 'B' + * files, and since they're stored in the first data sector (which we don't + * create), there's nowhere to put it. We stuff it into the aux type value + * in the linear file list, on the assumption that somebody will come along + * and politely Write to the file, even if it's zero bytes long. + * + * (Technically speaking, setting the file type here is bogus, because a + * 'B' file with no data sectors is invalid. However, we don't want to + * handle arbitrary changes later -- switching from 'T' to 'B' requires + * either rewriting the entire file, or confusing the user by changing the + * type without adjusting the first 4 bytes -- so we set it now. It's also + * helpful to set it now because the Write routine needs to know how many + * bytes offset from the start of the file it needs to be. We could avoid + * most of this weirdness by just going ahead and allocating the first + * sector of the file now, and modifying the Write() function to understand + * that the first block is already there. Need to do that someday.) + */ +DIError DiskFSDOS33::CreateFile(const CreateParms* pParms, A2File** ppNewFile) +{ + DIError dierr = kDIErrNone; + const bool createUnique = (GetParameter(kParm_CreateUnique) != 0); + char normalName[A2FileDOS::kMaxFileName+1]; +// char storageName[A2FileDOS::kMaxFileName+1]; + A2FileDOS::FileType fileType; + A2FileDOS* pNewFile = NULL; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + assert(pParms != NULL); + assert(pParms->pathName != NULL); + assert(pParms->storageType == A2FileProDOS::kStorageSeedling); + LOGI(" DOS33 ---v--- CreateFile '%s'", pParms->pathName); + + *ppNewFile = NULL; + + DoNormalizePath(pParms->pathName, pParms->fssep, normalName); + + /* + * See if the file already exists. + * + * If "create unique" is set, we append digits until the name doesn't + * match any others. The name will be modified in place. + */ + if (createUnique) { + MakeFileNameUnique(normalName); + } else { + if (GetFileByName(normalName) != NULL) { + LOGI(" DOS33 create: normalized name '%s' already exists", + normalName); + dierr = kDIErrFileExists; + goto bail; + } + } + + fileType = A2FileDOS::ConvertFileType(pParms->fileType, 0); + + /* + * Allocate a directory entry and T/S list. + */ + uint8_t sctBuf[kSctSize]; + TrackSector catSect; + TrackSector tsSect; + int catEntry; + A2FileDOS* pPrevEntry; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* allocate a sector for the T/S list, and zero it out */ + dierr = AllocSector(&tsSect); + if (dierr != kDIErrNone) + goto bail; + + memset(sctBuf, 0, kSctSize); + dierr = fpImg->WriteTrackSector(tsSect.track, tsSect.sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* + * Find the first free catalog entry. Also returns a pointer to the + * previous entry. + */ + dierr = GetFreeCatalogEntry(&catSect, &catEntry, sctBuf, &pPrevEntry); + if (dierr != kDIErrNone) { + LOGI("DOS unable to find an empty entry in the catalog"); + goto bail; + } + LOGI(" DOS found free catalog entry T=%d S=%d ent=%d prev=0x%08lx", + catSect.track, catSect.sector, catEntry, (long) pPrevEntry); + + /* create the new dir entry at the specified location */ + CreateDirEntry(sctBuf, catEntry, normalName, &tsSect, + (uint8_t) fileType, pParms->access); + + /* + * Flush everything to disk. + */ + dierr = fpImg->WriteTrackSector(catSect.track, catSect.sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + dierr = SaveVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Create a new entry for our file list. + */ + pNewFile = new A2FileDOS(this); + if (pNewFile == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + pNewFile->fTSListTrack = tsSect.track; + pNewFile->fTSListSector = tsSect.sector; + pNewFile->fLengthInSectors = 1; + pNewFile->fLocked = false; + strcpy(pNewFile->fFileName, normalName); + pNewFile->fFileType = fileType; + + pNewFile->fCatTS.track = catSect.track; + pNewFile->fCatTS.sector = catSect.sector; + pNewFile->fCatEntryNum = catEntry; + + pNewFile->fAuxType = (uint16_t) pParms->auxType; + pNewFile->fDataOffset = 0; + switch (pNewFile->fFileType) { + case A2FileDOS::kTypeInteger: + pNewFile->fDataOffset = 2; + break; + case A2FileDOS::kTypeApplesoft: + pNewFile->fDataOffset = 2; + pNewFile->fAuxType = 0x0801; + break; + case A2FileDOS::kTypeBinary: + pNewFile->fDataOffset = 4; + break; + default: + break; + } + pNewFile->fLength = 0; + pNewFile->fSparseLength = 0; + + /* + * Insert it in the proper place, so that the order of the files matches + * the order of entries in the catalog. + */ + InsertFileInList(pNewFile, pPrevEntry); + + *ppNewFile = pNewFile; + pNewFile = NULL; + +bail: + delete pNewFile; + FreeVolBitmap(); + return dierr; +} + +/* + * Make the name pointed to by "fileName" unique. The name should already + * be FS-normalized, and be in a buffer that can hold at least kMaxFileName+1 + * bytes. + * + * (This is nearly identical to the code in the ProDOS implementation. I'd + * like to make it a general DiskFS function, but making the loop condition + * work requires setting up callbacks, which isn't hard here but is a little + * annoying in ProDOS because of the subdir buffer. So it's cut & paste + * for now.) + * + * Returns an error on failure, which should be impossible. + */ +DIError DiskFSDOS33::MakeFileNameUnique(char* fileName) +{ + assert(fileName != NULL); + assert(strlen(fileName) <= A2FileDOS::kMaxFileName); + + if (GetFileByName(fileName) == NULL) + return kDIErrNone; + + LOGI(" DOS found duplicate of '%s', making unique", fileName); + + int nameLen = strlen(fileName); + int dotOffset=0, dotLen=0; + char dotBuf[kMaxExtensionLen+1]; + + /* ensure the result will be null-terminated */ + memset(fileName + nameLen, 0, (A2FileDOS::kMaxFileName - nameLen) +1); + + /* + * If this has what looks like a filename extension, grab it. We want + * to preserve ".gif", ".c", etc. + */ + const char* cp = strrchr(fileName, '.'); + if (cp != NULL) { + int tmpOffset = cp - fileName; + if (tmpOffset > 0 && nameLen - tmpOffset <= kMaxExtensionLen) { + LOGI(" DOS (keeping extension '%s')", cp); + assert(strlen(cp) <= kMaxExtensionLen); + strcpy(dotBuf, cp); + dotOffset = tmpOffset; + dotLen = nameLen - dotOffset; + } + } + + const int kMaxDigits = 999; + int digits = 0; + int digitLen; + int copyOffset; + char digitBuf[4]; + do { + if (digits == kMaxDigits) + return kDIErrFileExists; + digits++; + + /* not the most efficient way to do this, but it'll do */ + sprintf(digitBuf, "%d", digits); + digitLen = strlen(digitBuf); + if (nameLen + digitLen > A2FileDOS::kMaxFileName) + copyOffset = A2FileDOS::kMaxFileName - dotLen - digitLen; + else + copyOffset = nameLen - dotLen; + memcpy(fileName + copyOffset, digitBuf, digitLen); + if (dotLen != 0) + memcpy(fileName + copyOffset + digitLen, dotBuf, dotLen); + } while (GetFileByName(fileName) != NULL); + + LOGI(" DOS converted to unique name: %s", fileName); + + return kDIErrNone; +} + +/* + * Find the first free entry in the catalog. + * + * Also returns an A2File pointer for the previous entry in the catalog. + * + * The contents of the catalog sector will be in "sctBuf". + */ +DIError DiskFSDOS33::GetFreeCatalogEntry(TrackSector* pCatSect, int* pCatEntry, + uint8_t* sctBuf, A2FileDOS** ppPrevEntry) +{ + DIError dierr = kDIErrNone; + uint8_t* pEntry; + int sct, ent; + bool found = false; + + for (sct = 0; sct < kMaxCatalogSectors; sct++) { + if (fCatalogSectors[sct].track == 0 && + fCatalogSectors[sct].sector == 0) + { + /* end of list reached */ + LOGI("DOS catalog is full"); + dierr = kDIErrVolumeDirFull; + goto bail; + } + dierr = fpImg->ReadTrackSector(fCatalogSectors[sct].track, + fCatalogSectors[sct].sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + pEntry = &sctBuf[kCatalogEntryOffset]; + for (ent = 0; ent < kCatalogEntriesPerSect; ent++) { + if (pEntry[0x00] == 0x00 || pEntry[0x00] == kEntryDeleted) { + /* winner! */ + *pCatSect = fCatalogSectors[sct]; + *pCatEntry = ent; + found = true; + break; + } + + pEntry += kCatalogEntrySize; + } + + if (found) + break; + } + + if (sct == kMaxCatalogSectors) { + /* didn't find anything, assume the disk is full */ + dierr = kDIErrVolumeDirFull; + // fall through to "bail" + } else { + /* figure out what the previous entry is */ + TrackSector prevTS; + int prevEntry; + + if (*pCatEntry != 0) { + prevTS = *pCatSect; + prevEntry = *pCatEntry -1; + } else if (sct != 0) { + prevTS = fCatalogSectors[sct-1]; + prevEntry = kCatalogEntriesPerSect-1; + } else { + /* disk was empty; there's no previous entry */ + prevTS.track = 0; + prevTS.sector = 0; + prevEntry = -1; + } + + /* now find it in the linear file list */ + *ppPrevEntry = NULL; + if (prevEntry >= 0) { + A2FileDOS* pFile = (A2FileDOS*) GetNextFile(NULL); + while (pFile != NULL) { + if (pFile->fCatTS.track == prevTS.track && + pFile->fCatTS.sector == prevTS.sector && + pFile->fCatEntryNum == prevEntry) + { + *ppPrevEntry = pFile; + break; + } + pFile = (A2FileDOS*) GetNextFile(pFile); + } + assert(*ppPrevEntry != NULL); + } + } + +bail: + return dierr; +} + +/* + * Fill out the catalog entry in the location specified. + */ +void DiskFSDOS33::CreateDirEntry(uint8_t* sctBuf, int catEntry, + const char* fileName, TrackSector* pTSSect, uint8_t fileType, + int access) +{ + char highName[A2FileDOS::kMaxFileName+1]; + uint8_t* pEntry; + + pEntry = GetCatalogEntryPtr(sctBuf, catEntry); + if (pEntry[0x00] != 0x00 && pEntry[0x00] != kEntryDeleted) { + /* somebody screwed up */ + assert(false); + return; + } + + A2FileDOS::MakeDOSName(highName, fileName); + + pEntry[0x00] = pTSSect->track; + pEntry[0x01] = pTSSect->sector; + pEntry[0x02] = fileType; + if ((access & A2FileProDOS::kAccessWrite) == 0) + pEntry[0x02] |= (uint8_t) A2FileDOS::kTypeLocked; + memcpy(&pEntry[0x03], highName, A2FileDOS::kMaxFileName); + PutShortLE(&pEntry[0x21], 1); // assume file is 1 sector long +} + +/* + * Delete a file. + * + * This entails freeing up the allocated sectors and changing a byte in + * the directory entry. We then remove it from the DiskFS file list. + */ +DIError DiskFSDOS33::DeleteFile(A2File* pGenericFile) +{ + DIError dierr = kDIErrNone; + A2FileDOS* pFile = (A2FileDOS*) pGenericFile; + TrackSector* tsList = NULL; + TrackSector* indexList = NULL; + int tsCount, indexCount; + uint8_t sctBuf[kSctSize]; + uint8_t* pEntry; + + if (pGenericFile == NULL) { + assert(false); + return kDIErrInvalidArg; + } + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + if (pGenericFile->IsFileOpen()) + return kDIErrFileOpen; + + LOGI(" Deleting '%s'", pFile->GetPathName()); + + /* + * Update the block usage map. Nothing is permanent until we flush + * the data to disk. + */ + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + dierr = pFile->LoadTSList(&tsList, &tsCount, &indexList, &indexCount); + if (dierr != kDIErrNone) { + LOGI("Failed loading TS lists while deleting '%s'", + pFile->GetPathName()); + goto bail; + } + + FreeTrackSectors(tsList, tsCount); + FreeTrackSectors(indexList, indexCount); + + /* + * Mark the entry as deleted. + */ + dierr = fpImg->ReadTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + pEntry = GetCatalogEntryPtr(sctBuf, pFile->fCatEntryNum); + assert(pEntry[0x00] != 0x00 && pEntry[0x00] != kEntryDeleted); + pEntry[0x00] = kEntryDeleted; + dierr = fpImg->WriteTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* + * Save our updated copy of the volume bitmap to disk. + */ + dierr = SaveVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Remove the A2File* from the list. + */ + DeleteFileFromList(pFile); + +bail: + FreeVolBitmap(); + delete[] tsList; + delete[] indexList; + return dierr; +} + +/* + * Mark all of the track/sector entries in "pList" as free. + */ +void DiskFSDOS33::FreeTrackSectors(TrackSector* pList, int count) +{ + VolumeUsage::ChunkState cstate; + int i; + + cstate.isUsed = false; + cstate.isMarkedUsed = false; + cstate.purpose = VolumeUsage::kChunkPurposeUnknown; + + for (i = 0; i < count; i++) { + if (pList[i].track == 0 && pList[i].sector == 0) + continue; // sparse file + + if (!GetSectorUseEntry(pList[i].track, pList[i].sector)) { + LOGI("WARNING: freeing unallocated sector T=%d S=%d", + pList[i].track, pList[i].sector); + assert(false); // impossible unless disk is "damaged" + } + SetSectorUseEntry(pList[i].track, pList[i].sector, false); + + fVolumeUsage.SetChunkState(pList[i].track, pList[i].sector, &cstate); + } +} + +/* + * Rename a file. + * + * "newName" must already be normalized. + */ +DIError DiskFSDOS33::RenameFile(A2File* pGenericFile, const char* newName) +{ + DIError dierr = kDIErrNone; + A2FileDOS* pFile = (A2FileDOS*) pGenericFile; + char normalName[A2FileDOS::kMaxFileName+1]; + char dosName[A2FileDOS::kMaxFileName+1]; + uint8_t sctBuf[kSctSize]; + uint8_t* pEntry; + + if (pFile == NULL || newName == NULL) + return kDIErrInvalidArg; + if (!IsValidFileName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + LOGI(" DOS renaming '%s' to '%s'", pFile->GetPathName(), newName); + + /* + * Update the disk catalog entry. + */ + dierr = fpImg->ReadTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + pEntry = GetCatalogEntryPtr(sctBuf, pFile->fCatEntryNum); + + DoNormalizePath(newName, '\0', normalName); + A2FileDOS::MakeDOSName(dosName, normalName); + memcpy(&pEntry[0x03], dosName, A2FileDOS::kMaxFileName); + + dierr = fpImg->WriteTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* + * Update our internal copy. + */ + char storedName[A2FileDOS::kMaxFileName+1]; + strcpy(storedName, dosName); + LowerASCII((uint8_t*)storedName, A2FileDOS::kMaxFileName); + A2FileDOS::TrimTrailingSpaces(storedName); + + strcpy(pFile->fFileName, storedName); + +bail: + return dierr; +} + +/* + * Set the file's attributes. + * + * We allow the file to be locked or unlocked, and we allow the file type + * to be changed. We don't try to rewrite the file if they're changing to or + * from a format with embedded data (e.g. BAS or BIN); instead, we just + * change the type letter. We do need to re-evaluate the end-of-file + * value afterward. + * + * Changing the aux type is only allowed for BIN files. + */ +DIError DiskFSDOS33::SetFileInfo(A2File* pGenericFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) +{ + DIError dierr = kDIErrNone; + A2FileDOS* pFile = (A2FileDOS*) pGenericFile; + TrackSector* tsList = NULL; + int tsCount; + bool nowLocked; + bool typeChanged; + + if (pFile == NULL) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + LOGI("DOS SetFileInfo '%s' type=0x%02x aux=0x%04x access=0x%02x", + pFile->GetPathName(), fileType, auxType, accessFlags); + + /* + * We can ignore the file/aux type, or we can verify that they're not + * trying to change it. The latter is a little more work but makes + * the API a little more communicative. + */ + if (!A2FileDOS::IsValidType(fileType)) { + LOGI("DOS SetFileInfo invalid file type"); + dierr = kDIErrInvalidArg; + goto bail; + } + if (auxType != pFile->GetAuxType() && fileType != 0x06) { + /* this only makes sense for BIN files */ + LOGI("DOS SetFileInfo aux type mismatch; ignoring"); + //dierr = kDIErrNotSupported; + //goto bail; + } + + nowLocked = (accessFlags & A2FileProDOS::kAccessWrite) == 0; + typeChanged = (fileType != pFile->GetFileType()); + + /* + * Update the file type and locked status, if necessary. + */ + if (nowLocked != pFile->fLocked || typeChanged) { + A2FileDOS::FileType newFileType; + uint8_t sctBuf[kSctSize]; + uint8_t* pEntry; + + LOGI("Updating file '%s'", pFile->GetPathName()); + + dierr = fpImg->ReadTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + pEntry = GetCatalogEntryPtr(sctBuf, pFile->fCatEntryNum); + + newFileType = A2FileDOS::ConvertFileType(fileType, 0); + pEntry[0x02] = (uint8_t) newFileType; + if (nowLocked) + pEntry[0x02] |= 0x80; + + dierr = fpImg->WriteTrackSector(pFile->fCatTS.track, pFile->fCatTS.sector, + sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* update our local copy */ + pFile->fLocked = nowLocked; + } + + if (!typeChanged && auxType == pFile->GetAuxType()) { + /* only the locked status has changed; skip the rest */ + goto bail; + } + + /* + * If the file has type BIN (either because it was before and we left it + * alone, or we changed it to BIN), we need to figure out what the aux + * type should be. There are two situations: + * + * (1) User specified an aux type. If the aux type passed in doesn't match + * what's in the A2FileDOS structure, we assume they meant to change it. + * (2) User didn't specify an aux type change. If the file was BIN before, + * we don't need to do anything, but if it was just changed to BIN then + * we need to extract the aux type from the first sector of the file. + * + * There's also a 3rd situation: they changed the aux type for a non-BIN + * file. This should have been blocked earlier. + * + * On top of all this, if we changed the file type at all then we need to + * re-scan the file length and "data offset" value. + */ + uint16_t newAuxType; + newAuxType = (uint16_t) auxType; + + dierr = pFile->LoadTSList(&tsList, &tsCount); + if (dierr != kDIErrNone) { + LOGI(" DOS SFI: unable to load TS list (err=%d)", dierr); + goto bail; + } + + if (fileType == 0x06 && tsCount > 0) { + uint8_t sctBuf[kSctSize]; + + dierr = fpImg->ReadTrackSector(tsList[0].track, + tsList[0].sector, sctBuf); + if (dierr != kDIErrNone) { + LOGI("DOS SFI: unable to get first sector of file"); + goto bail; + } + + if (auxType == pFile->GetAuxType()) { + newAuxType = GetShortLE(&sctBuf[0x00]); + LOGI(" Aux type not changed, extracting from file (0x%04x)", + newAuxType); + } else { + LOGI(" Aux type changed (to 0x%04x), changing file", + newAuxType); + + PutShortLE(&sctBuf[0x00], newAuxType); + dierr = fpImg->WriteTrackSector(tsList[0].track, + tsList[0].sector, sctBuf); + if (dierr != kDIErrNone) { + LOGI("DOS SFI: unable to write first sector of file"); + goto bail; + } + } + } else { + /* not BIN or file has no sectors */ + if (pFile->fFileType == A2FileDOS::kTypeApplesoft) + newAuxType = 0x0801; + else + newAuxType = 0x0000; + } + + /* update our local copy */ + pFile->fFileType = A2FileDOS::ConvertFileType(fileType, 0); + pFile->fAuxType = newAuxType; + + /* + * Recalculate the file's length and "data offset". This may also mark + * the file as "suspicious". We wouldn't be here if the file was + * suspicious when we opened the disk image -- the image would have + * been marked read-only -- so if it's suspicious now, it's probably + * from a previous file type change attempt in the current session. + * Clear the flag so it doesn't "stick". + */ + pFile->ResetQuality(); + (void) ComputeLength(pFile, tsList, tsCount); + +bail: + delete[] tsList; + return dierr; +} + +/* + * Change the disk volume name (number). + * + * We can't change the 2MG header, and we can't change the values embedded + * in the sector headers, so all we do is change the VTOC entry. + */ +DIError DiskFSDOS33::RenameVolume(const char* newName) +{ + DIError dierr = kDIErrNone; + uint8_t sctBuf[kSctSize]; + long newNumber; + char* endp; + + if (!IsValidVolumeName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + // convert the number; we already ascertained that it's valid + newNumber = strtol(newName, &endp, 10); + + dierr = fpImg->ReadTrackSector(kVTOCTrack, kVTOCSector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + sctBuf[0x06] = (uint8_t) newNumber; + + dierr = fpImg->WriteTrackSector(kVTOCTrack, kVTOCSector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + fVTOCVolumeNumber = newNumber; + UpdateVolumeNum(); + +bail: + return dierr; +} + + +/* + * =========================================================================== + * A2FileDOS + * =========================================================================== + */ + +/* + * Constructor. + */ +A2FileDOS::A2FileDOS(DiskFS* pDiskFS) : A2File(pDiskFS) +{ + fTSListTrack = -1; + fTSListSector = -1; + fLengthInSectors = 0; + fLocked = false; + fFileName[0] = '\0'; + fFileType = kTypeUnknown; + + 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". + */ +A2FileDOS::~A2FileDOS(void) +{ + delete fpOpenFile; +} + + +/* + * Convert the filetype enum to a ProDOS type. + * + * Remember that the DOS filetype field is actually a bit field, so we need + * to handle situations where more than one bit is set. + * + * Ideally this is a reversible transformation, so files copied to ProDOS + * volumes can be copied back to DOS with no loss of information. The reverse + * is *not* true, because of file type reduction and the potential loss of + * accurate file length info. + * + * I'm not entirely certain about the conversion of 'R' to REL, largely + * because I can't find any information on the REL format. However, Copy ][+ + * does convert to REL, and the Binary ][ standard says I should as well. + */ +uint32_t A2FileDOS::GetFileType(void) const +{ + long retval; + + switch (fFileType) { + case kTypeText: retval = 0x04; break; // TXT + case kTypeInteger: retval = 0xfa; break; // INT + case kTypeApplesoft: retval = 0xfc; break; // BAS + case kTypeBinary: retval = 0x06; break; // BIN + case kTypeS: retval = 0xf2; break; // $f2 + case kTypeReloc: retval = 0xfe; break; // REL + case kTypeA: retval = 0xf3; break; // $f3 + case kTypeB: retval = 0xf4; break; // $f4 + case kTypeUnknown: + default: + retval = 0x00; // NON + break; + } + + return retval; +} + +/* + * Convert a ProDOS 8 file type to its DOS equivalent. + * + * We need to know the file length because files over 64K can't fit into + * DOS A/I/B files. Text files can be as long as they want, and the + * other types don't have a length word defined, so they're fine. + * + * We can't just convert them later, because by that point they've already + * got a 2-byte or 4-byte header reserved. + * + * Because we don't generally know the eventual length of the file at + * the time we're creating it, this doesn't work nearly as well as could + * be hoped. We can make life a little less confusing for the caller by + * using type 'S' for any unknown type. + */ +/*static*/ A2FileDOS::FileType A2FileDOS::ConvertFileType(long prodosType, + di_off_t fileLen) +{ + const long kMaxBinary = 65535; + FileType newType; + + switch (prodosType) { + case 0xb0: newType = kTypeText; break; // SRC + case 0x04: newType = kTypeText; break; // TXT + case 0xfa: newType = kTypeInteger; break; // INT + case 0xfc: newType = kTypeApplesoft; break; // BAS + case 0x06: newType = kTypeBinary; break; // BIN + case 0xf2: newType = kTypeS; break; // $f2 + case 0xfe: newType = kTypeReloc; break; // REL + case 0xf3: newType = kTypeA; break; // $f3 + case 0xf4: newType = kTypeB; break; // $f4 + default: newType = kTypeS; break; + } + + if (fileLen > kMaxBinary && + (newType == kTypeInteger || newType == kTypeApplesoft || + newType == kTypeBinary)) + { + LOGI(" DOS setting type for large A/I/B file to S"); + newType = kTypeS; + } + + return newType; +} + +/* + * Determine whether the specified type has a valid DOS mapping. + */ +/*static*/ bool A2FileDOS::IsValidType(long prodosType) +{ + switch (prodosType) { + case 0xb0: // SRC + case 0x04: // TXT + case 0xfa: // INT + case 0xfc: // BAS + case 0x06: // BIN + case 0xf2: // $f2 + case 0xfe: // REL + case 0xf3: // $f3 + case 0xf4: // $f4 + return true; + default: + return false; + } +} + +/* + * Match the ProDOS equivalents of "locked" and "unlocked". + */ +uint32_t A2FileDOS::GetAccess(void) const +{ + if (fLocked) + return DiskFS::kFileAccessLocked; // 0x01 read + else + return DiskFS::kFileAccessUnlocked; // 0xc3 read/write/rename/destroy +} + +/* + * "Fix" a DOS3.3 filename. Convert DOS-ASCII to normal ASCII, and strip + * trailing spaces. + */ +void A2FileDOS::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. + */ +/*static*/ void A2FileDOS::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 A2FileDOS::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 A2FileDOS::Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork /*=false*/) +{ + DIError dierr = kDIErrNone; + A2FDDOS* 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 A2FDDOS(this); + + dierr = LoadTSList(&pOpenFile->fTSList, &pOpenFile->fTSCount, + &pOpenFile->fIndexList, &pOpenFile->fIndexCount); + if (dierr != kDIErrNone) { + LOGI("DOS33 unable to load TS for '%s' open", GetPathName()); + goto bail; + } + + 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 A2FileDOS. + */ +void A2FileDOS::Dump(void) const +{ + LOGI("A2FileDOS '%s'", fFileName); + LOGI(" TS T=%-2d S=%-2d", fTSListTrack, fTSListSector); + 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); +} + + +/* + * Load the T/S list for this file. + * + * A single T/S sector holds 122 entries, enough to store a 30.5K file. + * It's very unlikely that a file will need more than two, although it's + * possible for a random-access text file to have a very large number of + * entries. + * + * If "pIndexList" and "pIndexCount" are non-NULL, the list of index blocks is + * also loaded. + * + * It's entirely possible to get a large T/S list back that is filled + * entirely with zeroes. This can happen if we have a large set of T/S + * index sectors that are all zero. We have to leave space for them so + * that the Write function can use the existing allocated index blocks. + * + * THOUGHT: we may want to use the file type to tighten this up a bit. + * For example, we're currently very careful around random-access text + * files, but if the file doesn't have type 'T' then random access is + * impossible. Currently this isn't a problem, but for e.g. T/S lists + * with garbage at the end would could deal with the problem more generally. + */ +DIError A2FileDOS::LoadTSList(TrackSector** pTSList, int* pTSCount, + TrackSector** pIndexList, int* pIndexCount) +{ + DIError dierr = kDIErrNone; + DiskImg* pDiskImg; + const int kDefaultTSAlloc = 2; + const int kDefaultIndexAlloc = 8; + TrackSector* tsList = NULL; + TrackSector* indexList = NULL; + int tsCount, tsAlloc; + int indexCount, indexAlloc; + uint8_t sctBuf[kSctSize]; + int track, sector, iterations; + + LOGI("--- DOS loading T/S list for '%s'", GetPathName()); + + /* over-alloc for small files to reduce reallocs */ + tsAlloc = kMaxTSPairs * kDefaultTSAlloc; + tsList = new TrackSector[tsAlloc]; + tsCount = 0; + + indexAlloc = kDefaultIndexAlloc; + indexList = new TrackSector[indexAlloc]; + indexCount = 0; + + if (tsList == NULL || indexList == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + assert(fpDiskFS != NULL); + pDiskImg = fpDiskFS->GetDiskImg(); + assert(pDiskImg != NULL); + + /* get the first T/S sector for this file */ + track = fTSListTrack; + sector = fTSListSector; + if (track >= pDiskImg->GetNumTracks() || + sector >= pDiskImg->GetNumSectPerTrack()) + { + LOGI(" DOS33 invalid initial T/S %d,%d in '%s'", track, sector, + fFileName); + dierr = kDIErrBadFile; + goto bail; + } + + /* + * Run through the set of t/s pairs. + */ + iterations = 0; + do { + uint16_t sectorOffset; + int lastNonZero; + + /* + * Add the current T/S sector to the index list. + */ + if (indexCount == indexAlloc) { + LOGI("+++ expanding index list"); + TrackSector* newList; + indexAlloc += kDefaultIndexAlloc; + newList = new TrackSector[indexAlloc]; + if (newList == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + memcpy(newList, indexList, indexCount * sizeof(TrackSector)); + delete[] indexList; + indexList = newList; + } + indexList[indexCount].track = track; + indexList[indexCount].sector = sector; + indexCount++; + + + //LOGI("+++ scanning T/S at T=%d S=%d", track, sector); + dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + /* grab next track/sector */ + track = sctBuf[0x01]; + sector = sctBuf[0x02]; + sectorOffset = GetShortLE(&sctBuf[0x05]); + + /* if T/S link is bogus, whole sector is probably bad */ + if (track >= pDiskImg->GetNumTracks() || + sector >= pDiskImg->GetNumSectPerTrack()) + { + // bogus T/S, mark file as damaged and stop + LOGI(" DOS33 invalid T/S link %d,%d in '%s'", track, sector, + GetPathName()); + dierr = kDIErrBadFile; + goto bail; + } + if ((sectorOffset % kMaxTSPairs) != 0) { + LOGI(" DOS33 invalid T/S header sector offset %u in '%s'", + sectorOffset, GetPathName()); + // not fatal, just weird + } + + /* + * Make sure we have enough room to hold an entire sector full of + * T/S pairs in the list. + */ + if (tsCount + kMaxTSPairs > tsAlloc) { + LOGI("+++ expanding ts list"); + TrackSector* newList; + tsAlloc += kMaxTSPairs * kDefaultTSAlloc; + newList = new TrackSector[tsAlloc]; + if (newList == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + memcpy(newList, tsList, tsCount * sizeof(TrackSector)); + delete[] tsList; + tsList = newList; + } + + /* + * Add the entries. If there's another T/S list linked, we just + * grab the entire sector. If not, we grab every entry until the + * last 0,0. (Can't stop at the first (0,0), or we'll drop a + * piece of a random access text file.) + */ + dierr = ExtractTSPairs(sctBuf, &tsList[tsCount], &lastNonZero); + if (dierr != kDIErrNone) + goto bail; + + if (track != 0 && sector != 0) { + /* more T/S lists to come, so we keep all entries */ + tsCount += kMaxTSPairs; + } else { + /* this was the last one */ + if (lastNonZero == -1) { + /* this is ALWAYS the case for a newly-created file */ + //LOGI(" DOS33 odd -- last T/S sector of '%s' was empty", + // GetPathName()); + } + tsCount += lastNonZero +1; + } + + iterations++; // watch for infinite loops + } while (!(track == 0 && sector == 0) && iterations < kMaxTSIterations); + + if (iterations == kMaxTSIterations) { + dierr = kDIErrFileLoop; + goto bail; + } + + *pTSList = tsList; + *pTSCount = tsCount; + tsList = NULL; + + if (pIndexList != NULL) { + *pIndexList = indexList; + *pIndexCount = indexCount; + indexList = NULL; + } + +bail: + delete[] tsList; + delete[] indexList; + return dierr; +} + +/* + * Extract the track/sector pairs from the TS list in "sctBuf". The entries + * are copied to "tsList", which is assumed to have enough space to hold + * at least kMaxTSPairs entries. + * + * The last non-zero entry will be identified and stored in "*pLastNonZero". + * If all entries are zero, it will be set to -1. + * + * Sometimes files will have junk at the tail end of an otherwise valid + * T/S list. We can't just stop when we hit the first (0,0) entry because + * that'll screw up random-access text file handling. What we can do is + * try to detect the situation, and mark the file as "suspicious" without + * returning an error if we see it. + * + * If a TS entry appears to be invalid, this returns an error after all + * entries have been copied. If it looks to be partially valid, only the + * valid parts are copied out, with the rest zeroed. + */ +DIError A2FileDOS::ExtractTSPairs(const uint8_t* sctBuf, TrackSector* tsList, + int* pLastNonZero) +{ + DIError dierr = kDIErrNone; + const DiskImg* pDiskImg = fpDiskFS->GetDiskImg(); + const uint8_t* ptr; + int i, track, sector; + + *pLastNonZero = -1; + memset(tsList, 0, sizeof(TrackSector) * kMaxTSPairs); + + ptr = &sctBuf[kTSOffset]; // offset of first T/S entry (0x0c) + + for (i = 0; i < kMaxTSPairs; i++) { + track = *ptr++; + sector = *ptr++; + + if (dierr == kDIErrNone && + (track >= pDiskImg->GetNumTracks() || + sector >= pDiskImg->GetNumSectPerTrack() || + (track == 0 && sector != 0))) + { + LOGI(" DOS33 invalid T/S %d,%d in '%s'", track, sector, + fFileName); + + if (i > 0 && tsList[i-1].track == 0 && tsList[i-1].sector == 0) { + LOGI(" T/S list looks partially valid"); + SetQuality(kQualitySuspicious); + goto bail; // quit immediately + } else { + dierr = kDIErrBadFile; + // keep going, just so caller has the full set to stare at + } + } + + if (track != 0 || sector != 0) + *pLastNonZero = i; + + tsList[i].track = track; + tsList[i].sector = sector; + } + +bail: + return dierr; +} + +/* + * 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* A2FileDOS::GetRawFileName(size_t* size) const { + if (size) { + *size = strlen(fRawFileName); + } + return fRawFileName; +} + + +/* + * =========================================================================== + * A2FDDOS + * =========================================================================== + */ + +/* + * Read data from the current offset. + * + * Files read back as they would from ProDOS, i.e. if you read a binary + * file you won't see the 4 bytes of length and address. + */ +DIError A2FDDOS::Read(void* buf, size_t len, size_t* pActual) +{ + LOGD(" DOS reading %lu bytes from '%s' (offset=%ld)", + (unsigned long) len, fpFile->GetPathName(), (long) fOffset); + + A2FileDOS* pFile = (A2FileDOS*) fpFile; + + /* + * Don't allow them to read past the end of the file. The length value + * stored in pFile->fLength already has pFile->fDataOffset subtracted + * from the actual data length, so don't factor it in again. + */ + if (fOffset + (long)len > fOpenEOF) { + if (pActual == NULL) + return kDIErrDataUnderrun; + len = (size_t) (fOpenEOF - fOffset); + } + if (pActual != NULL) + *pActual = len; + long incrLen = len; + + DIError dierr = kDIErrNone; + uint8_t sctBuf[kSctSize]; + di_off_t actualOffset = fOffset + pFile->fDataOffset; // adjust for embedded len + int tsIndex = (int) (actualOffset / kSctSize); + int bufOffset = (int) (actualOffset % kSctSize); // (& 0xff) + size_t thisCount; + + if (len == 0) + return kDIErrNone; + assert(fOpenEOF != 0); + + assert(tsIndex >= 0 && tsIndex < fTSCount); + + /* could be more clever in here and avoid double-buffering */ + while (len) { + if (tsIndex >= fTSCount) { + /* should've caught this earlier */ + assert(false); + LOGI(" DOS ran off the end (fTSCount=%d)", fTSCount); + return kDIErrDataUnderrun; + } + + if (fTSList[tsIndex].track == 0 && fTSList[tsIndex].sector == 0) { + //LOGI(" DOS sparse sector T=%d S=%d", + // TSTrack(fTSList[tsIndex]), TSSector(fTSList[tsIndex])); + memset(sctBuf, 0, sizeof(sctBuf)); + } else { + dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector( + fTSList[tsIndex].track, + fTSList[tsIndex].sector, + sctBuf); + if (dierr != kDIErrNone) { + LOGI(" DOS 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; + + bufOffset = 0; + tsIndex++; + } + + fOffset += incrLen; + + return dierr; +} + +/* + * Write data at the current offset. + * + * For simplicity, we assume that we're writing a brand-new file in one + * shot. As it happens, that's all we're currently required to do, so even + * if we wrote a more sophisticated function it wouldn't get exercised. + * Because of the way we write, there's no way to mimic the behavior of + * random-access text file allocation, so that isn't supported. + * + * The data in "buf" should *not* include the 2-4 bytes of header present + * on A/I/B files. That's already factored in. + * + * Modifies fOpenEOF, fOpenSectorsUsed, and sets fModified. + */ +DIError A2FDDOS::Write(const void* buf, size_t len, size_t* pActual) +{ + DIError dierr = kDIErrNone; + A2FileDOS* pFile = (A2FileDOS*) fpFile; + DiskFSDOS33* pDiskFS = (DiskFSDOS33*) fpFile->GetDiskFS(); + uint8_t sctBuf[kSctSize]; + + LOGD(" DOS Write len=%lu %s", (unsigned long) len, pFile->GetPathName()); + + if (len >= 0x01000000) { // 16MB + assert(false); + return kDIErrInvalidArg; + } + assert(fOffset == 0); // big simplifying assumption + assert(fOpenEOF == 0); // another one + assert(fTSCount == 0); // must hold for our newly-created files + assert(fIndexCount == 1); // must hold for our newly-created files + assert(fOpenSectorsUsed == fTSCount + fIndexCount); + assert(buf != NULL); + + long actualLen = (long) len + pFile->fDataOffset; + long numSectors = (actualLen + kSctSize -1) / kSctSize; + TrackSector firstIndex; + int i; + + /* + * Nothing to do for zero-length write; don't even set fModified. Note, + * however, that a zero-length 'B' file is actually 4 bytes long, and + * must have a data block allocated. + */ + if (actualLen == 0) + goto bail; + assert(numSectors > 0); + + dierr = pDiskFS->LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Start by allocating a full T/S list. The existing T/S list is + * empty, but we do have one T/S index sector to fill before we + * allocate any others. + * + * Since we determined above that there was nothing interesting in + * our T/S list, we just grab the one allocated block, throw out + * the lists, and reallocate them. + */ + firstIndex = fIndexList[0]; + delete[] fTSList; + delete[] fIndexList; + fTSList = fIndexList = NULL; + + fTSCount = numSectors; + fTSList = new TrackSector[fTSCount]; + fIndexCount = (numSectors + kMaxTSPairs -1) / kMaxTSPairs; + assert(fIndexCount > 0); + fIndexList = new TrackSector[fIndexCount]; + if (fTSList == NULL || fIndexList == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + /* + * Allocate all of the index sectors. In theory we should to this along + * with the file sectors, so that the index and file sectors are + * interspersed with the data, but in practice 99% of the file have + * only one or two index blocks. By grouping them together we improve + * the performance for emulators and CiderPress. + */ + fIndexList[0] = firstIndex; + for (i = 1; i < fIndexCount; i++) { + TrackSector allocTS; + + dierr = pDiskFS->AllocSector(&allocTS); + if (dierr != kDIErrNone) + goto bail; + fIndexList[i] = allocTS; + } + /* + * Allocate the data sectors. + */ + for (i = 0; i < fTSCount; i++) { + TrackSector allocTS; + + dierr = pDiskFS->AllocSector(&allocTS); + if (dierr != kDIErrNone) + goto bail; + fTSList[i] = allocTS; + } + + /* + * Write the sectors into the T/S list. + */ + const uint8_t* curPtr; + int sectorIdx; + + curPtr = (const uint8_t*) buf; + sectorIdx = 0; + + if (pFile->fDataOffset > 0) { + /* handle first sector specially */ + assert(pFile->fDataOffset < kSctSize); + int dataInFirstSct = kSctSize - pFile->fDataOffset; + if (dataInFirstSct > actualLen - pFile->fDataOffset) + dataInFirstSct = actualLen - pFile->fDataOffset; + + // dataInFirstSct could be zero (== len) + memset(sctBuf, 0, sizeof(sctBuf)); + memcpy(sctBuf + pFile->fDataOffset, curPtr, + dataInFirstSct); + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(fTSList[sectorIdx].track, + fTSList[sectorIdx].sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + sectorIdx++; + actualLen -= dataInFirstSct + pFile->fDataOffset; + curPtr += dataInFirstSct; + } + while (actualLen > 0) { + if (actualLen >= kSctSize) { + /* write directly from input */ + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(fTSList[sectorIdx].track, + fTSList[sectorIdx].sector, curPtr); + if (dierr != kDIErrNone) + goto bail; + } else { + /* make a copy of the partial buffer */ + memset(sctBuf, 0, sizeof(sctBuf)); + memcpy(sctBuf, curPtr, actualLen); + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(fTSList[sectorIdx].track, + fTSList[sectorIdx].sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + } + + sectorIdx++; + actualLen -= kSctSize; // goes negative; that's fine + curPtr += kSctSize; + } + assert(sectorIdx == fTSCount); + + /* + * Fill out the T/S list sectors. Failure here presents a potential + * problem because, once we've written the first T/S entry, the file + * appears to have storage that it actually doesn't. The easiest way + * to handle this safely is to start by writing the last index block + * first. + */ + for (i = fIndexCount-1; i >= 0; i--) { + int tsOffset = i * kMaxTSPairs; + assert(tsOffset < fTSCount); + + memset(sctBuf, 0, kSctSize); + if (i != fIndexCount-1) { + sctBuf[0x01] = fIndexList[i+1].track; + sctBuf[0x02] = fIndexList[i+1].sector; + } + PutShortLE(&sctBuf[0x05], kMaxTSPairs * i); + + int ent = i * kMaxTSPairs; // start here + for (int j = 0; j < kMaxTSPairs; j++) { + if (ent == fTSCount) + break; + sctBuf[kTSOffset + j*2] = fTSList[ent].track; + sctBuf[kTSOffset + j*2 +1] = fTSList[ent].sector; + ent++; + } + + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(fIndexList[i].track, + fIndexList[i].sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + } + + dierr = pDiskFS->SaveVolBitmap(); + if (dierr != kDIErrNone) { + /* + * This is awkward -- we wrote the first T/S list, so the file + * now appears to have content, but the blocks aren't marked used. + * We read the VTOC successfully though, so it's VERY unlikely + * that this will fail. If it does, it's likely that any attempt + * to mitigate the problem will also fail. (Maybe we could force + * the object into read-only mode?) + */ + goto bail; + } + + /* finish up */ + fOpenSectorsUsed = fIndexCount + fTSCount; + fOpenEOF = len; + fOffset += len; + fModified = true; + + if (!UpdateProgress(fOffset)) + dierr = kDIErrCancelled; + +bail: + pDiskFS->FreeVolBitmap(); + return dierr; +} + +/* + * Seek to the specified offset. + */ +DIError A2FDDOS::Seek(di_off_t offset, DIWhence whence) +{ + //di_off_t fileLength = fpFile->GetDataLength(); + + switch (whence) { + case kSeekSet: + if (offset < 0 || offset > fOpenEOF) + return kDIErrInvalidArg; + fOffset = offset; + break; + case kSeekEnd: + if (offset > 0 || offset < -fOpenEOF) + return kDIErrInvalidArg; + fOffset = fOpenEOF + offset; + break; + case kSeekCur: + if (offset < -fOffset || + offset >= (fOpenEOF - fOffset)) + { + return kDIErrInvalidArg; + } + fOffset += offset; + break; + default: + assert(false); + return kDIErrInvalidArg; + } + + assert(fOffset >= 0 && fOffset <= fOpenEOF); + return kDIErrNone; +} + +/* + * Return current offset. + */ +di_off_t A2FDDOS::Tell(void) +{ + return fOffset; +} + +/* + * 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 A2FDDOS::Close(void) +{ + DIError dierr = kDIErrNone; + + if (fModified) { + DiskFSDOS33* pDiskFS = (DiskFSDOS33*) fpFile->GetDiskFS(); + A2FileDOS* pFile = (A2FileDOS*) fpFile; + uint8_t sctBuf[kSctSize]; + uint8_t* pEntry; + + /* + * Fill in the length and address, if needed for this type of file. + */ + if (pFile->fFileType == A2FileDOS::kTypeInteger || + pFile->fFileType == A2FileDOS::kTypeApplesoft || + pFile->fFileType == A2FileDOS::kTypeBinary) + { + assert(fTSCount > 0); + assert(pFile->fDataOffset > 0); + //assert(fOpenEOF < 65536); + if (fOpenEOF > 65535) { + LOGW("WARNING: DOS Close trimming A/I/B file from %ld to 65535", + (long) fOpenEOF); + fOpenEOF = 65535; + } + dierr = pDiskFS->GetDiskImg()->ReadTrackSector(fTSList[0].track, + fTSList[0].sector, sctBuf); + if (dierr != kDIErrNone) { + LOGW("DOS Close: unable to get first sector of file"); + goto bail; + } + + if (pFile->fFileType == A2FileDOS::kTypeInteger || + pFile->fFileType == A2FileDOS::kTypeApplesoft) + { + PutShortLE(&sctBuf[0x00], (uint16_t) fOpenEOF); + } else { + PutShortLE(&sctBuf[0x00], pFile->fAuxType); + PutShortLE(&sctBuf[0x02], (uint16_t) fOpenEOF); + } + + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(fTSList[0].track, + fTSList[0].sector, sctBuf); + if (dierr != kDIErrNone) { + LOGW("DOS Close: unable to write first sector of file"); + goto bail; + } + } else if (pFile->fFileType == A2FileDOS::kTypeText) { + /* + * The length of text files can be determined by looking for the + * first $00. A file of exactly 256 bytes occupies only one + * sector though, so running out of sectors also works -- the + * last $00 is not mandatory. + * + * Bottom line is that the value we just wrote for fOpenEOF is + * *probably* recoverable, so we can stuff it into "fLength" + * with some assurance that it will be there when we reopen the + * file. + */ + } else { + /* + * The remaining file types have a length based solely on + * sector count. We need to round off our length value. + */ + fOpenEOF = ((fOpenEOF + kSctSize-1) / kSctSize) * kSctSize; + } + + /* + * Update our internal copies of stuff. + */ + pFile->fLength = fOpenEOF; + pFile->fSparseLength = pFile->fLength; + pFile->fLengthInSectors = (uint16_t) fOpenSectorsUsed; + + /* + * Update the sector count in the directory entry. + */ + dierr = pDiskFS->GetDiskImg()->ReadTrackSector(pFile->fCatTS.track, + pFile->fCatTS.sector, sctBuf); + if (dierr != kDIErrNone) + goto bail; + + pEntry = GetCatalogEntryPtr(sctBuf, pFile->fCatEntryNum); + assert(GetShortLE(&pEntry[0x21]) == 1); // holds for new file + PutShortLE(&pEntry[0x21], pFile->fLengthInSectors); + dierr = pDiskFS->GetDiskImg()->WriteTrackSector(pFile->fCatTS.track, + pFile->fCatTS.sector, sctBuf); + } + +bail: + fpFile->CloseDescr(this); + return dierr; +} + + +/* + * Return the #of sectors/blocks in the file. + */ +long A2FDDOS::GetSectorCount(void) const +{ + return fTSCount; +} + +long A2FDDOS::GetBlockCount(void) const +{ + return (fTSCount+1)/2; +} + +/* + * Return the Nth track/sector in this file. + * + * Returns (0,0) for a sparse sector. + */ +DIError A2FDDOS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const +{ + if (sectorIdx < 0 || sectorIdx >= fTSCount) + return kDIErrInvalidIndex; + + *pTrack = fTSList[sectorIdx].track; + *pSector = fTSList[sectorIdx].sector; + return kDIErrNone; +} +/* + * Return the Nth 512-byte block in this file. Since things aren't stored + * in 512-byte blocks, we're reduced to finding storage at (tsIndex*2) and + * converting it to a block number. + */ +DIError A2FDDOS::GetStorage(long blockIdx, long* pBlock) const +{ + long sectorIdx = blockIdx * 2; + if (sectorIdx < 0 || sectorIdx >= fTSCount) + return kDIErrInvalidIndex; + + bool dummy; + TrackSectorToBlock(fTSList[sectorIdx].track, + fTSList[sectorIdx].sector, pBlock, &dummy); + assert(*pBlock < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks()); + return kDIErrNone; +} + + +/* + * Dump the T/S list for an open file. + */ +void A2FDDOS::DumpTSList(void) const +{ + //A2FileDOS* pFile = (A2FileDOS*) fpFile; + LOGI(" DOS T/S list for '%s' (count=%d)", + ((A2FileDOS*)fpFile)->fFileName, fTSCount); + + int i; + for (i = 0; i <= fTSCount; i++) { + LOGI(" %3d: T=%-2d S=%d", i, fTSList[i].track, fTSList[i].sector); + } +} diff --git a/diskimg/DOSImage.cpp b/diskimg/DOSImage.cpp new file mode 100644 index 0000000..73c2962 --- /dev/null +++ b/diskimg/DOSImage.cpp @@ -0,0 +1,2904 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * DOS images. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + +namespace DiskImgLib { + +/* + * Three 16-sector tracks, in DOS order (i.e. track 0 sector 0 followed + * by track 0 sector 1). + * + * Obtained from tracks 0-2 of a newly-formatted disk created + * by "INIT HELLO" after booting the DOS 3.3 system master. + */ +/*static*/ const uint8_t DiskFSDOS33::gDOS33Tracks[16 * 3 * 256] = { + 0x01, 0xa5, 0x27, 0xc9, 0x09, 0xd0, 0x18, 0xa5, + 0x2b, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, + 0x3f, 0xa9, 0x5c, 0x85, 0x3e, 0x18, 0xad, 0xfe, + 0x08, 0x6d, 0xff, 0x08, 0x8d, 0xfe, 0x08, 0xae, + 0xff, 0x08, 0x30, 0x15, 0xbd, 0x4d, 0x08, 0x85, + 0x3d, 0xce, 0xff, 0x08, 0xad, 0xfe, 0x08, 0x85, + 0x27, 0xce, 0xfe, 0x08, 0xa6, 0x2b, 0x6c, 0x3e, + 0x00, 0xee, 0xfe, 0x08, 0xee, 0xfe, 0x08, 0x20, + 0x89, 0xfe, 0x20, 0x93, 0xfe, 0x20, 0x2f, 0xfb, + 0xa6, 0x2b, 0x6c, 0xfd, 0x08, 0x00, 0x0d, 0x0b, + 0x09, 0x07, 0x05, 0x03, 0x01, 0x0e, 0x0c, 0x0a, + 0x08, 0x06, 0x04, 0x02, 0x0f, 0x00, 0x20, 0x64, + 0xa7, 0xb0, 0x08, 0xa9, 0x00, 0xa8, 0x8d, 0x5d, + 0xb6, 0x91, 0x40, 0xad, 0xc5, 0xb5, 0x4c, 0xd2, + 0xa6, 0xad, 0x5d, 0xb6, 0xf0, 0x08, 0xee, 0xbd, + 0xb5, 0xd0, 0x03, 0xee, 0xbe, 0xb5, 0xa9, 0x00, + 0x8d, 0x5d, 0xb6, 0x4c, 0x46, 0xa5, 0x8d, 0xbc, + 0xb5, 0x20, 0xa8, 0xa6, 0x20, 0xea, 0xa2, 0x4c, + 0x7d, 0xa2, 0xa0, 0x13, 0xb1, 0x42, 0xd0, 0x14, + 0xc8, 0xc0, 0x17, 0xd0, 0xf7, 0xa0, 0x19, 0xb1, + 0x42, 0x99, 0xa4, 0xb5, 0xc8, 0xc0, 0x1d, 0xd0, + 0xf6, 0x4c, 0xbc, 0xa6, 0xa2, 0xff, 0x8e, 0x5d, + 0xb6, 0xd0, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x58, 0xfc, 0xa9, 0xc2, 0x20, 0xed, 0xfd, + 0xa9, 0x01, 0x20, 0xda, 0xfd, 0xa9, 0xad, 0x20, + 0xed, 0xfd, 0xa9, 0x00, 0x20, 0xda, 0xfd, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x09, + + 0x8e, 0xe9, 0xb7, 0x8e, 0xf7, 0xb7, 0xa9, 0x01, + 0x8d, 0xf8, 0xb7, 0x8d, 0xea, 0xb7, 0xad, 0xe0, + 0xb7, 0x8d, 0xe1, 0xb7, 0xa9, 0x02, 0x8d, 0xec, + 0xb7, 0xa9, 0x04, 0x8d, 0xed, 0xb7, 0xac, 0xe7, + 0xb7, 0x88, 0x8c, 0xf1, 0xb7, 0xa9, 0x01, 0x8d, + 0xf4, 0xb7, 0x8a, 0x4a, 0x4a, 0x4a, 0x4a, 0xaa, + 0xa9, 0x00, 0x9d, 0xf8, 0x04, 0x9d, 0x78, 0x04, + 0x20, 0x93, 0xb7, 0xa2, 0xff, 0x9a, 0x8e, 0xeb, + 0xb7, 0x4c, 0xc8, 0xbf, 0x20, 0x89, 0xfe, 0x4c, + 0x84, 0x9d, 0xad, 0xe7, 0xb7, 0x38, 0xed, 0xf1, + 0xb7, 0x8d, 0xe1, 0xb7, 0xad, 0xe7, 0xb7, 0x8d, + 0xf1, 0xb7, 0xce, 0xf1, 0xb7, 0xa9, 0x02, 0x8d, + 0xec, 0xb7, 0xa9, 0x04, 0x8d, 0xed, 0xb7, 0xa9, + 0x02, 0x8d, 0xf4, 0xb7, 0x20, 0x93, 0xb7, 0xad, + 0xe7, 0xb7, 0x8d, 0xfe, 0xb6, 0x18, 0x69, 0x09, + 0x8d, 0xf1, 0xb7, 0xa9, 0x0a, 0x8d, 0xe1, 0xb7, + 0x38, 0xe9, 0x01, 0x8d, 0xff, 0xb6, 0x8d, 0xed, + 0xb7, 0x20, 0x93, 0xb7, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xad, 0xe5, 0xb7, 0xac, 0xe4, + 0xb7, 0x20, 0xb5, 0xb7, 0xac, 0xed, 0xb7, 0x88, + 0x10, 0x07, 0xa0, 0x0f, 0xea, 0xea, 0xce, 0xec, + 0xb7, 0x8c, 0xed, 0xb7, 0xce, 0xf1, 0xb7, 0xce, + 0xe1, 0xb7, 0xd0, 0xdf, 0x60, 0x08, 0x78, 0x20, + 0x00, 0xbd, 0xb0, 0x03, 0x28, 0x18, 0x60, 0x28, + 0x38, 0x60, 0xad, 0xbc, 0xb5, 0x8d, 0xf1, 0xb7, + 0xa9, 0x00, 0x8d, 0xf0, 0xb7, 0xad, 0xf9, 0xb5, + 0x49, 0xff, 0x8d, 0xeb, 0xb7, 0x60, 0xa9, 0x00, + 0xa8, 0x91, 0x42, 0xc8, 0xd0, 0xfb, 0x60, 0x00, + 0x1b, 0x02, 0x0a, 0x1b, 0xe8, 0xb7, 0x00, 0xb6, + 0x01, 0x60, 0x02, 0xfe, 0x00, 0x01, 0xfb, 0xb7, + 0x00, 0xb7, 0x00, 0x00, 0x02, 0xeb, 0xfe, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x01, 0xef, 0xd8, 0x00, + + 0xa2, 0x00, 0xa0, 0x02, 0x88, 0xb1, 0x3e, 0x4a, + 0x3e, 0x00, 0xbc, 0x4a, 0x3e, 0x00, 0xbc, 0x99, + 0x00, 0xbb, 0xe8, 0xe0, 0x56, 0x90, 0xed, 0xa2, + 0x00, 0x98, 0xd0, 0xe8, 0xa2, 0x55, 0xbd, 0x00, + 0xbc, 0x29, 0x3f, 0x9d, 0x00, 0xbc, 0xca, 0x10, + 0xf5, 0x60, 0x38, 0x86, 0x27, 0x8e, 0x78, 0x06, + 0xbd, 0x8d, 0xc0, 0xbd, 0x8e, 0xc0, 0x30, 0x7c, + 0xad, 0x00, 0xbc, 0x85, 0x26, 0xa9, 0xff, 0x9d, + 0x8f, 0xc0, 0x1d, 0x8c, 0xc0, 0x48, 0x68, 0xea, + 0xa0, 0x04, 0x48, 0x68, 0x20, 0xb9, 0xb8, 0x88, + 0xd0, 0xf8, 0xa9, 0xd5, 0x20, 0xb8, 0xb8, 0xa9, + 0xaa, 0x20, 0xb8, 0xb8, 0xa9, 0xad, 0x20, 0xb8, + 0xb8, 0x98, 0xa0, 0x56, 0xd0, 0x03, 0xb9, 0x00, + 0xbc, 0x59, 0xff, 0xbb, 0xaa, 0xbd, 0x29, 0xba, + 0xa6, 0x27, 0x9d, 0x8d, 0xc0, 0xbd, 0x8c, 0xc0, + 0x88, 0xd0, 0xeb, 0xa5, 0x26, 0xea, 0x59, 0x00, + 0xbb, 0xaa, 0xbd, 0x29, 0xba, 0xae, 0x78, 0x06, + 0x9d, 0x8d, 0xc0, 0xbd, 0x8c, 0xc0, 0xb9, 0x00, + 0xbb, 0xc8, 0xd0, 0xea, 0xaa, 0xbd, 0x29, 0xba, + 0xa6, 0x27, 0x20, 0xbb, 0xb8, 0xa9, 0xde, 0x20, + 0xb8, 0xb8, 0xa9, 0xaa, 0x20, 0xb8, 0xb8, 0xa9, + 0xeb, 0x20, 0xb8, 0xb8, 0xa9, 0xff, 0x20, 0xb8, + 0xb8, 0xbd, 0x8e, 0xc0, 0xbd, 0x8c, 0xc0, 0x60, + 0x18, 0x48, 0x68, 0x9d, 0x8d, 0xc0, 0x1d, 0x8c, + 0xc0, 0x60, 0xa0, 0x00, 0xa2, 0x56, 0xca, 0x30, + 0xfb, 0xb9, 0x00, 0xbb, 0x5e, 0x00, 0xbc, 0x2a, + 0x5e, 0x00, 0xbc, 0x2a, 0x91, 0x3e, 0xc8, 0xc4, + 0x26, 0xd0, 0xeb, 0x60, 0xa0, 0x20, 0x88, 0xf0, + 0x61, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x49, 0xd5, + 0xd0, 0xf4, 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, + 0xc9, 0xaa, 0xd0, 0xf2, 0xa0, 0x56, 0xbd, 0x8c, + 0xc0, 0x10, 0xfb, 0xc9, 0xad, 0xd0, 0xe7, 0xa9, + + 0x00, 0x88, 0x84, 0x26, 0xbc, 0x8c, 0xc0, 0x10, + 0xfb, 0x59, 0x00, 0xba, 0xa4, 0x26, 0x99, 0x00, + 0xbc, 0xd0, 0xee, 0x84, 0x26, 0xbc, 0x8c, 0xc0, + 0x10, 0xfb, 0x59, 0x00, 0xba, 0xa4, 0x26, 0x99, + 0x00, 0xbb, 0xc8, 0xd0, 0xee, 0xbc, 0x8c, 0xc0, + 0x10, 0xfb, 0xd9, 0x00, 0xba, 0xd0, 0x13, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xde, 0xd0, 0x0a, + 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xaa, + 0xf0, 0x5c, 0x38, 0x60, 0xa0, 0xfc, 0x84, 0x26, + 0xc8, 0xd0, 0x04, 0xe6, 0x26, 0xf0, 0xf3, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xd5, 0xd0, 0xf0, + 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xaa, + 0xd0, 0xf2, 0xa0, 0x03, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0xc9, 0x96, 0xd0, 0xe7, 0xa9, 0x00, 0x85, + 0x27, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x2a, 0x85, + 0x26, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x25, 0x26, + 0x99, 0x2c, 0x00, 0x45, 0x27, 0x88, 0x10, 0xe7, + 0xa8, 0xd0, 0xb7, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, + 0xc9, 0xde, 0xd0, 0xae, 0xea, 0xbd, 0x8c, 0xc0, + 0x10, 0xfb, 0xc9, 0xaa, 0xd0, 0xa4, 0x18, 0x60, + 0x86, 0x2b, 0x85, 0x2a, 0xcd, 0x78, 0x04, 0xf0, + 0x53, 0xa9, 0x00, 0x85, 0x26, 0xad, 0x78, 0x04, + 0x85, 0x27, 0x38, 0xe5, 0x2a, 0xf0, 0x33, 0xb0, + 0x07, 0x49, 0xff, 0xee, 0x78, 0x04, 0x90, 0x05, + 0x69, 0xfe, 0xce, 0x78, 0x04, 0xc5, 0x26, 0x90, + 0x02, 0xa5, 0x26, 0xc9, 0x0c, 0xb0, 0x01, 0xa8, + 0x38, 0x20, 0xee, 0xb9, 0xb9, 0x11, 0xba, 0x20, + 0x00, 0xba, 0xa5, 0x27, 0x18, 0x20, 0xf1, 0xb9, + 0xb9, 0x1d, 0xba, 0x20, 0x00, 0xba, 0xe6, 0x26, + 0xd0, 0xc3, 0x20, 0x00, 0xba, 0x18, 0xad, 0x78, + 0x04, 0x29, 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, + 0x80, 0xc0, 0xa6, 0x2b, 0x60, 0xaa, 0xa0, 0xa0, + + 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe6, 0x46, 0xd0, + 0x02, 0xe6, 0x47, 0x38, 0xe9, 0x01, 0xd0, 0xf0, + 0x60, 0x01, 0x30, 0x28, 0x24, 0x20, 0x1e, 0x1d, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x70, 0x2c, 0x26, + 0x22, 0x1f, 0x1e, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, + 0xa6, 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, + 0xbc, 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, + 0xd3, 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, + 0xde, 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0xff, 0xb3, 0xb3, 0xa0, 0xe0, 0xb3, 0xc3, 0xc5, + 0xb3, 0xa0, 0xe0, 0xb3, 0xc3, 0xc5, 0xb3, 0xa0, + 0xe0, 0xb3, 0xb3, 0xc5, 0xaa, 0xa0, 0x82, 0xb3, + 0xb3, 0xc5, 0xaa, 0xa0, 0x82, 0xc5, 0xb3, 0xb3, + 0xaa, 0x88, 0x82, 0xc5, 0xb3, 0xb3, 0xaa, 0x88, + 0x82, 0xc5, 0xc4, 0xb3, 0xb0, 0x88, 0x00, 0x01, + 0x98, 0x99, 0x02, 0x03, 0x9c, 0x04, 0x05, 0x06, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0x07, 0x08, + 0xa8, 0xa9, 0xaa, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0xb0, 0xb1, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, + 0xb8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0x1b, 0xcc, 0x1c, 0x1d, 0x1e, + 0xd0, 0xd1, 0xd2, 0x1f, 0xd4, 0xd5, 0x20, 0x21, + 0xd8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0x29, 0x2a, 0x2b, + 0xe8, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0xf0, 0xf1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0xf8, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + + 0x01, 0x0a, 0x11, 0x0a, 0x08, 0x20, 0x20, 0x0e, + 0x18, 0x06, 0x02, 0x31, 0x02, 0x09, 0x08, 0x27, + 0x22, 0x00, 0x12, 0x0a, 0x0a, 0x04, 0x00, 0x00, + 0x03, 0x2a, 0x00, 0x04, 0x00, 0x00, 0x22, 0x08, + 0x10, 0x28, 0x12, 0x02, 0x00, 0x02, 0x08, 0x11, + 0x0a, 0x08, 0x02, 0x28, 0x11, 0x01, 0x39, 0x22, + 0x31, 0x01, 0x05, 0x18, 0x20, 0x28, 0x02, 0x10, + 0x06, 0x02, 0x09, 0x02, 0x05, 0x2c, 0x10, 0x00, + 0x08, 0x2e, 0x00, 0x05, 0x02, 0x28, 0x18, 0x02, + 0x30, 0x23, 0x02, 0x20, 0x32, 0x04, 0x11, 0x02, + 0x14, 0x02, 0x08, 0x09, 0x12, 0x20, 0x0e, 0x2f, + 0x23, 0x30, 0x2f, 0x23, 0x30, 0x0c, 0x17, 0x2a, + 0x3f, 0x27, 0x23, 0x30, 0x37, 0x23, 0x30, 0x12, + 0x1a, 0x08, 0x30, 0x2f, 0x08, 0x30, 0x2f, 0x27, + 0x23, 0x30, 0x37, 0x23, 0x30, 0x3a, 0x22, 0x34, + 0x3c, 0x2a, 0x35, 0x08, 0x35, 0x2f, 0x2a, 0x2a, + 0x08, 0x35, 0x2f, 0x2a, 0x25, 0x08, 0x35, 0x2f, + 0x29, 0x10, 0x08, 0x31, 0x2f, 0x29, 0x11, 0x08, + 0x31, 0x2f, 0x29, 0x0f, 0x08, 0x31, 0x2f, 0x29, + 0x10, 0x11, 0x11, 0x11, 0x0f, 0x12, 0x12, 0x01, + 0x0f, 0x27, 0x23, 0x30, 0x2f, 0x23, 0x30, 0x1a, + 0x02, 0x2a, 0x08, 0x35, 0x2f, 0x2a, 0x37, 0x08, + 0x35, 0x2f, 0x2a, 0x2a, 0x08, 0x35, 0x2f, 0x2a, + 0x3a, 0x08, 0x35, 0x2f, 0x06, 0x2f, 0x23, 0x30, + 0x2f, 0x23, 0x30, 0x18, 0x12, 0x12, 0x01, 0x0f, + 0x27, 0x23, 0x30, 0x37, 0x23, 0x30, 0x1a, 0x3a, + 0x3a, 0x3a, 0x02, 0x2a, 0x3a, 0x3a, 0x12, 0x1a, + 0x27, 0x23, 0x30, 0x37, 0x23, 0x30, 0x18, 0x22, + 0x29, 0x3a, 0x24, 0x28, 0x25, 0x22, 0x25, 0x3a, + 0x24, 0x28, 0x25, 0x22, 0x25, 0x24, 0x24, 0x32, + 0x25, 0x34, 0x25, 0x24, 0x24, 0x32, 0x25, 0x34, + 0x25, 0x24, 0x28, 0x32, 0x28, 0x29, 0x21, 0x29, + + 0x10, 0xa1, 0x45, 0x28, 0x21, 0x82, 0x80, 0x38, + 0x62, 0x19, 0x0b, 0xc5, 0x0b, 0x24, 0x21, 0x9c, + 0x88, 0x00, 0x48, 0x28, 0x2b, 0x10, 0x00, 0x03, + 0x0c, 0xa9, 0x01, 0x10, 0x01, 0x00, 0x88, 0x22, + 0x40, 0xa0, 0x48, 0x09, 0x01, 0x08, 0x21, 0x44, + 0x29, 0x22, 0x08, 0xa0, 0x45, 0x06, 0xe4, 0x8a, + 0xc4, 0x06, 0x16, 0x60, 0x80, 0xa0, 0x09, 0x40, + 0x18, 0x0a, 0x24, 0x0a, 0x16, 0xb0, 0x43, 0x00, + 0x20, 0xbb, 0x00, 0x14, 0x08, 0xa0, 0x60, 0x0a, + 0xc0, 0x8f, 0x0a, 0x83, 0xca, 0x11, 0x44, 0x08, + 0x51, 0x0a, 0x20, 0x26, 0x4a, 0x80, 0x38, 0xbd, + 0x8d, 0xc0, 0xbd, 0x8e, 0xc0, 0x30, 0x5e, 0xa9, + 0xff, 0x9d, 0x8f, 0xc0, 0xdd, 0x8c, 0xc0, 0x48, + 0x68, 0x20, 0xc3, 0xbc, 0x20, 0xc3, 0xbc, 0x9d, + 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, 0xea, 0x88, 0xd0, + 0xf0, 0xa9, 0xd5, 0x20, 0xd5, 0xbc, 0xa9, 0xaa, + 0x20, 0xd5, 0xbc, 0xa9, 0x96, 0x20, 0xd5, 0xbc, + 0xa5, 0x41, 0x20, 0xc4, 0xbc, 0xa5, 0x44, 0x20, + 0xc4, 0xbc, 0xa5, 0x3f, 0x20, 0xc4, 0xbc, 0xa5, + 0x41, 0x45, 0x44, 0x45, 0x3f, 0x48, 0x4a, 0x05, + 0x3e, 0x9d, 0x8d, 0xc0, 0xbd, 0x8c, 0xc0, 0x68, + 0x09, 0xaa, 0x20, 0xd4, 0xbc, 0xa9, 0xde, 0x20, + 0xd5, 0xbc, 0xa9, 0xaa, 0x20, 0xd5, 0xbc, 0xa9, + 0xeb, 0x20, 0xd5, 0xbc, 0x18, 0xbd, 0x8e, 0xc0, + 0xbd, 0x8c, 0xc0, 0x60, 0x48, 0x4a, 0x05, 0x3e, + 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, 0x68, 0xea, + 0xea, 0xea, 0x09, 0xaa, 0xea, 0xea, 0x48, 0x68, + 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, 0x60, 0x88, + 0xa5, 0xe8, 0x91, 0xa0, 0x94, 0x88, 0x96, 0xe8, + 0x91, 0xa0, 0x94, 0x88, 0x96, 0x91, 0x91, 0xc8, + 0x94, 0xd0, 0x96, 0x91, 0x91, 0xc8, 0x94, 0xd0, + 0x96, 0x91, 0xa3, 0xc8, 0xa0, 0xa5, 0x85, 0xa4, + + 0x84, 0x48, 0x85, 0x49, 0xa0, 0x02, 0x8c, 0xf8, + 0x06, 0xa0, 0x04, 0x8c, 0xf8, 0x04, 0xa0, 0x01, + 0xb1, 0x48, 0xaa, 0xa0, 0x0f, 0xd1, 0x48, 0xf0, + 0x1b, 0x8a, 0x48, 0xb1, 0x48, 0xaa, 0x68, 0x48, + 0x91, 0x48, 0xbd, 0x8e, 0xc0, 0xa0, 0x08, 0xbd, + 0x8c, 0xc0, 0xdd, 0x8c, 0xc0, 0xd0, 0xf6, 0x88, + 0xd0, 0xf8, 0x68, 0xaa, 0xbd, 0x8e, 0xc0, 0xbd, + 0x8c, 0xc0, 0xa0, 0x08, 0xbd, 0x8c, 0xc0, 0x48, + 0x68, 0x48, 0x68, 0x8e, 0xf8, 0x05, 0xdd, 0x8c, + 0xc0, 0xd0, 0x03, 0x88, 0xd0, 0xee, 0x08, 0xbd, + 0x89, 0xc0, 0xa0, 0x06, 0xb1, 0x48, 0x99, 0x36, + 0x00, 0xc8, 0xc0, 0x0a, 0xd0, 0xf6, 0xa0, 0x03, + 0xb1, 0x3c, 0x85, 0x47, 0xa0, 0x02, 0xb1, 0x48, + 0xa0, 0x10, 0xd1, 0x48, 0xf0, 0x06, 0x91, 0x48, + 0x28, 0xa0, 0x00, 0x08, 0x6a, 0x90, 0x05, 0xbd, + 0x8a, 0xc0, 0xb0, 0x03, 0xbd, 0x8b, 0xc0, 0x66, + 0x35, 0x28, 0x08, 0xd0, 0x0b, 0xa0, 0x07, 0x20, + 0x00, 0xba, 0x88, 0xd0, 0xfa, 0xae, 0xf8, 0x05, + 0xa0, 0x04, 0xb1, 0x48, 0x20, 0x5a, 0xbe, 0x28, + 0xd0, 0x11, 0xa4, 0x47, 0x10, 0x0d, 0xa0, 0x12, + 0x88, 0xd0, 0xfd, 0xe6, 0x46, 0xd0, 0xf7, 0xe6, + 0x47, 0xd0, 0xf3, 0xa0, 0x0c, 0xb1, 0x48, 0xf0, + 0x5a, 0xc9, 0x04, 0xf0, 0x58, 0x6a, 0x08, 0xb0, + 0x03, 0x20, 0x00, 0xb8, 0xa0, 0x30, 0x8c, 0x78, + 0x05, 0xae, 0xf8, 0x05, 0x20, 0x44, 0xb9, 0x90, + 0x24, 0xce, 0x78, 0x05, 0x10, 0xf3, 0xad, 0x78, + 0x04, 0x48, 0xa9, 0x60, 0x20, 0x95, 0xbe, 0xce, + 0xf8, 0x06, 0xf0, 0x28, 0xa9, 0x04, 0x8d, 0xf8, + 0x04, 0xa9, 0x00, 0x20, 0x5a, 0xbe, 0x68, 0x20, + 0x5a, 0xbe, 0x4c, 0xbc, 0xbd, 0xa4, 0x2e, 0xcc, + 0x78, 0x04, 0xf0, 0x1c, 0xad, 0x78, 0x04, 0x48, + 0x98, 0x20, 0x95, 0xbe, 0x68, 0xce, 0xf8, 0x04, + + 0xd0, 0xe5, 0xf0, 0xca, 0x68, 0xa9, 0x40, 0x28, + 0x4c, 0x48, 0xbe, 0xf0, 0x39, 0x4c, 0xaf, 0xbe, + 0xa0, 0x03, 0xb1, 0x48, 0x48, 0xa5, 0x2f, 0xa0, + 0x0e, 0x91, 0x48, 0x68, 0xf0, 0x08, 0xc5, 0x2f, + 0xf0, 0x04, 0xa9, 0x20, 0xd0, 0xe1, 0xa0, 0x05, + 0xb1, 0x48, 0xa8, 0xb9, 0xb8, 0xbf, 0xc5, 0x2d, + 0xd0, 0x97, 0x28, 0x90, 0x1c, 0x20, 0xdc, 0xb8, + 0x08, 0xb0, 0x8e, 0x28, 0xa2, 0x00, 0x86, 0x26, + 0x20, 0xc2, 0xb8, 0xae, 0xf8, 0x05, 0x18, 0x24, + 0x38, 0xa0, 0x0d, 0x91, 0x48, 0xbd, 0x88, 0xc0, + 0x60, 0x20, 0x2a, 0xb8, 0x90, 0xf0, 0xa9, 0x10, + 0xb0, 0xee, 0x48, 0xa0, 0x01, 0xb1, 0x3c, 0x6a, + 0x68, 0x90, 0x08, 0x0a, 0x20, 0x6b, 0xbe, 0x4e, + 0x78, 0x04, 0x60, 0x85, 0x2a, 0x20, 0x8e, 0xbe, + 0xb9, 0x78, 0x04, 0x24, 0x35, 0x30, 0x03, 0xb9, + 0xf8, 0x04, 0x8d, 0x78, 0x04, 0xa5, 0x2a, 0x24, + 0x35, 0x30, 0x05, 0x99, 0xf8, 0x04, 0x10, 0x03, + 0x99, 0x78, 0x04, 0x4c, 0xa0, 0xb9, 0x8a, 0x4a, + 0x4a, 0x4a, 0x4a, 0xa8, 0x60, 0x48, 0xa0, 0x02, + 0xb1, 0x48, 0x6a, 0x66, 0x35, 0x20, 0x8e, 0xbe, + 0x68, 0x0a, 0x24, 0x35, 0x30, 0x05, 0x99, 0xf8, + 0x04, 0x10, 0x03, 0x99, 0x78, 0x04, 0x60, 0xa0, + 0x03, 0xb1, 0x48, 0x85, 0x41, 0xa9, 0xaa, 0x85, + 0x3e, 0xa0, 0x56, 0xa9, 0x00, 0x85, 0x44, 0x99, + 0xff, 0xbb, 0x88, 0xd0, 0xfa, 0x99, 0x00, 0xbb, + 0x88, 0xd0, 0xfa, 0xa9, 0x50, 0x20, 0x95, 0xbe, + 0xa9, 0x28, 0x85, 0x45, 0xa5, 0x44, 0x20, 0x5a, + 0xbe, 0x20, 0x0d, 0xbf, 0xa9, 0x08, 0xb0, 0x24, + 0xa9, 0x30, 0x8d, 0x78, 0x05, 0x38, 0xce, 0x78, + 0x05, 0xf0, 0x19, 0x20, 0x44, 0xb9, 0xb0, 0xf5, + 0xa5, 0x2d, 0xd0, 0xf1, 0x20, 0xdc, 0xb8, 0xb0, + 0xec, 0xe6, 0x44, 0xa5, 0x44, 0xc9, 0x23, 0x90, + + 0xd3, 0x18, 0x90, 0x05, 0xa0, 0x0d, 0x91, 0x48, + 0x38, 0xbd, 0x88, 0xc0, 0x60, 0xa9, 0x00, 0x85, + 0x3f, 0xa0, 0x80, 0xd0, 0x02, 0xa4, 0x45, 0x20, + 0x56, 0xbc, 0xb0, 0x6b, 0x20, 0x2a, 0xb8, 0xb0, + 0x66, 0xe6, 0x3f, 0xa5, 0x3f, 0xc9, 0x10, 0x90, + 0xec, 0xa0, 0x0f, 0x84, 0x3f, 0xa9, 0x30, 0x8d, + 0x78, 0x05, 0x99, 0xa8, 0xbf, 0x88, 0x10, 0xfa, + 0xa4, 0x45, 0x20, 0x87, 0xbf, 0x20, 0x87, 0xbf, + 0x20, 0x87, 0xbf, 0x48, 0x68, 0xea, 0x88, 0xd0, + 0xf1, 0x20, 0x44, 0xb9, 0xb0, 0x23, 0xa5, 0x2d, + 0xf0, 0x15, 0xa9, 0x10, 0xc5, 0x45, 0xa5, 0x45, + 0xe9, 0x01, 0x85, 0x45, 0xc9, 0x05, 0xb0, 0x11, + 0x38, 0x60, 0x20, 0x44, 0xb9, 0xb0, 0x05, 0x20, + 0xdc, 0xb8, 0x90, 0x1c, 0xce, 0x78, 0x05, 0xd0, + 0xf1, 0x20, 0x44, 0xb9, 0xb0, 0x0b, 0xa5, 0x2d, + 0xc9, 0x0f, 0xd0, 0x05, 0x20, 0xdc, 0xb8, 0x90, + 0x8c, 0xce, 0x78, 0x05, 0xd0, 0xeb, 0x38, 0x60, + 0xa4, 0x2d, 0xb9, 0xa8, 0xbf, 0x30, 0xdd, 0xa9, + 0xff, 0x99, 0xa8, 0xbf, 0xc6, 0x3f, 0x10, 0xca, + 0xa5, 0x44, 0xd0, 0x0a, 0xa5, 0x45, 0xc9, 0x10, + 0x90, 0xe5, 0xc6, 0x45, 0xc6, 0x45, 0x18, 0x60, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0d, 0x0b, 0x09, 0x07, 0x05, 0x03, 0x01, + 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x0f, + 0x20, 0x93, 0xfe, 0xad, 0x81, 0xc0, 0xad, 0x81, + 0xc0, 0xa9, 0x00, 0x8d, 0x00, 0xe0, 0x4c, 0x44, + 0xb7, 0x00, 0x00, 0x00, 0x8d, 0x63, 0xaa, 0x8d, + 0x70, 0xaa, 0x8d, 0x71, 0xaa, 0x60, 0x20, 0x5b, + 0xa7, 0x8c, 0xb7, 0xaa, 0x60, 0x20, 0x7e, 0xae, + 0xae, 0x9b, 0xb3, 0x9a, 0x20, 0x16, 0xa3, 0xba, + 0x8e, 0x9b, 0xb3, 0xa9, 0x09, 0x4c, 0x85, 0xb3, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0xd3, 0x9c, 0x81, 0x9e, 0xbd, 0x9e, 0x75, 0xaa, + 0x93, 0xaa, 0x60, 0xaa, 0x00, 0x9d, 0xbb, 0xb5, + 0xea, 0x9e, 0x11, 0x9f, 0x22, 0x9f, 0x2e, 0x9f, + 0x51, 0x9f, 0x60, 0x9f, 0x70, 0x9f, 0x4e, 0xa5, + 0x12, 0xa4, 0x96, 0xa3, 0xd0, 0xa4, 0xef, 0xa4, + 0x62, 0xa2, 0x70, 0xa2, 0x74, 0xa2, 0xe9, 0xa2, + 0x1a, 0xa5, 0xc5, 0xa5, 0x0f, 0xa5, 0xdc, 0xa5, + 0xa2, 0xa2, 0x97, 0xa2, 0x80, 0xa2, 0x6d, 0xa5, + 0x32, 0xa2, 0x3c, 0xa2, 0x28, 0xa2, 0x2d, 0xa2, + 0x50, 0xa2, 0x79, 0xa5, 0x9d, 0xa5, 0x30, 0xa3, + 0x5c, 0xa3, 0x8d, 0xa3, 0x7c, 0xa2, 0xfc, 0xa4, + 0xfc, 0xa4, 0x65, 0xd8, 0x00, 0xe0, 0x3c, 0xd4, + 0xf2, 0xd4, 0x36, 0xe8, 0xe5, 0xa4, 0xe3, 0xe3, + 0x00, 0xe0, 0x03, 0xe0, 0xfc, 0xa4, 0xfc, 0xa4, + 0x65, 0xd8, 0x00, 0xe0, 0x3c, 0xd4, 0xf2, 0xd4, + 0x06, 0xa5, 0x06, 0xa5, 0x67, 0x10, 0x84, 0x9d, + 0x3c, 0x0c, 0xf2, 0x0c, 0xad, 0xe9, 0xb7, 0x4a, + 0x4a, 0x4a, 0x4a, 0x8d, 0x6a, 0xaa, 0xad, 0xea, + 0xb7, 0x8d, 0x68, 0xaa, 0xad, 0x00, 0xe0, 0x49, + 0x20, 0xd0, 0x11, 0x8d, 0xb6, 0xaa, 0xa2, 0x0a, + 0xbd, 0x61, 0x9d, 0x9d, 0x55, 0x9d, 0xca, 0xd0, + 0xf7, 0x4c, 0xbc, 0x9d, 0xa9, 0x40, 0x8d, 0xb6, + 0xaa, 0xa2, 0x0c, 0xbd, 0x6b, 0x9d, 0x9d, 0x55, + 0x9d, 0xca, 0xd0, 0xf7, 0x38, 0xb0, 0x12, 0xad, + 0xb6, 0xaa, 0xd0, 0x04, 0xa9, 0x20, 0xd0, 0x05, + 0x0a, 0x10, 0x05, 0xa9, 0x4c, 0x20, 0xb2, 0xa5, + 0x18, 0x08, 0x20, 0x51, 0xa8, 0xa9, 0x00, 0x8d, + 0x5e, 0xaa, 0x8d, 0x52, 0xaa, 0x28, 0x6a, 0x8d, + 0x51, 0xaa, 0x30, 0x03, 0x6c, 0x5e, 0x9d, 0x6c, + 0x5c, 0x9d, 0x0a, 0x10, 0x19, 0x8d, 0xb6, 0xaa, + 0xa2, 0x0c, 0xbd, 0x77, 0x9d, 0x9d, 0x55, 0x9d, + 0xca, 0xd0, 0xf7, 0xa2, 0x1d, 0xbd, 0x93, 0xaa, + + 0x9d, 0x75, 0xaa, 0xca, 0x10, 0xf7, 0xad, 0xb1, + 0xaa, 0x8d, 0x57, 0xaa, 0x20, 0xd4, 0xa7, 0xad, + 0xb3, 0xaa, 0xf0, 0x09, 0x48, 0x20, 0x9d, 0xa6, + 0x68, 0xa0, 0x00, 0x91, 0x40, 0x20, 0x5b, 0xa7, + 0xad, 0x5f, 0xaa, 0xd0, 0x20, 0xa2, 0x2f, 0xbd, + 0x51, 0x9e, 0x9d, 0xd0, 0x03, 0xca, 0x10, 0xf7, + 0xad, 0x53, 0x9e, 0x8d, 0xf3, 0x03, 0x49, 0xa5, + 0x8d, 0xf4, 0x03, 0xad, 0x52, 0x9e, 0x8d, 0xf2, + 0x03, 0xa9, 0x06, 0xd0, 0x05, 0xad, 0x62, 0xaa, + 0xf0, 0x06, 0x8d, 0x5f, 0xaa, 0x4c, 0x80, 0xa1, + 0x60, 0x4c, 0xbf, 0x9d, 0x4c, 0x84, 0x9d, 0x4c, + 0xfd, 0xaa, 0x4c, 0xb5, 0xb7, 0xad, 0x0f, 0x9d, + 0xac, 0x0e, 0x9d, 0x60, 0xad, 0xc2, 0xaa, 0xac, + 0xc1, 0xaa, 0x60, 0x4c, 0x51, 0xa8, 0xea, 0xea, + 0x4c, 0x59, 0xfa, 0x4c, 0x65, 0xff, 0x4c, 0x58, + 0xff, 0x4c, 0x65, 0xff, 0x4c, 0x65, 0xff, 0x65, + 0xff, 0x20, 0xd1, 0x9e, 0xad, 0x51, 0xaa, 0xf0, + 0x15, 0x48, 0xad, 0x5c, 0xaa, 0x91, 0x28, 0x68, + 0x30, 0x03, 0x4c, 0x26, 0xa6, 0x20, 0xea, 0x9d, + 0xa4, 0x24, 0xa9, 0x60, 0x91, 0x28, 0xad, 0xb3, + 0xaa, 0xf0, 0x03, 0x20, 0x82, 0xa6, 0xa9, 0x03, + 0x8d, 0x52, 0xaa, 0x20, 0xba, 0x9f, 0x20, 0xba, + 0x9e, 0x8d, 0x5c, 0xaa, 0x8e, 0x5a, 0xaa, 0x4c, + 0xb3, 0x9f, 0x6c, 0x38, 0x00, 0x20, 0xd1, 0x9e, + 0xad, 0x52, 0xaa, 0x0a, 0xaa, 0xbd, 0x11, 0x9d, + 0x48, 0xbd, 0x10, 0x9d, 0x48, 0xad, 0x5c, 0xaa, + 0x60, 0x8d, 0x5c, 0xaa, 0x8e, 0x5a, 0xaa, 0x8c, + 0x5b, 0xaa, 0xba, 0xe8, 0xe8, 0x8e, 0x59, 0xaa, + 0xa2, 0x03, 0xbd, 0x53, 0xaa, 0x95, 0x36, 0xca, + 0x10, 0xf8, 0x60, 0xae, 0xb7, 0xaa, 0xf0, 0x03, + 0x4c, 0x78, 0x9f, 0xae, 0x51, 0xaa, 0xf0, 0x08, + 0xc9, 0xbf, 0xf0, 0x75, 0xc5, 0x33, 0xf0, 0x27, + + 0xa2, 0x02, 0x8e, 0x52, 0xaa, 0xcd, 0xb2, 0xaa, + 0xd0, 0x19, 0xca, 0x8e, 0x52, 0xaa, 0xca, 0x8e, + 0x5d, 0xaa, 0xae, 0x5d, 0xaa, 0x9d, 0x00, 0x02, + 0xe8, 0x8e, 0x5d, 0xaa, 0xc9, 0x8d, 0xd0, 0x75, + 0x4c, 0xcd, 0x9f, 0xc9, 0x8d, 0xd0, 0x7d, 0xa2, + 0x00, 0x8e, 0x52, 0xaa, 0x4c, 0xa4, 0x9f, 0xa2, + 0x00, 0x8e, 0x52, 0xaa, 0xc9, 0x8d, 0xf0, 0x07, + 0xad, 0xb3, 0xaa, 0xf0, 0x67, 0xd0, 0x5e, 0x48, + 0x38, 0xad, 0xb3, 0xaa, 0xd0, 0x03, 0x20, 0x5e, + 0xa6, 0x68, 0x90, 0xec, 0xae, 0x5a, 0xaa, 0x4c, + 0x15, 0x9f, 0xc9, 0x8d, 0xd0, 0x05, 0xa9, 0x05, + 0x8d, 0x52, 0xaa, 0x20, 0x0e, 0xa6, 0x4c, 0x99, + 0x9f, 0xcd, 0xb2, 0xaa, 0xf0, 0x85, 0xc9, 0x8a, + 0xf0, 0xf1, 0xa2, 0x04, 0x8e, 0x52, 0xaa, 0xd0, + 0xe1, 0xa9, 0x00, 0x8d, 0x52, 0xaa, 0xf0, 0x25, + 0xa9, 0x00, 0x8d, 0xb7, 0xaa, 0x20, 0x51, 0xa8, + 0x4c, 0xdc, 0xa4, 0xad, 0x00, 0x02, 0xcd, 0xb2, + 0xaa, 0xf0, 0x0a, 0xa9, 0x8d, 0x8d, 0x00, 0x02, + 0xa2, 0x00, 0x8e, 0x5a, 0xaa, 0xa9, 0x40, 0xd0, + 0x06, 0xa9, 0x10, 0xd0, 0x02, 0xa9, 0x20, 0x2d, + 0x5e, 0xaa, 0xf0, 0x0f, 0x20, 0xba, 0x9f, 0x20, + 0xc5, 0x9f, 0x8d, 0x5c, 0xaa, 0x8c, 0x5b, 0xaa, + 0x8e, 0x5a, 0xaa, 0x20, 0x51, 0xa8, 0xae, 0x59, + 0xaa, 0x9a, 0xad, 0x5c, 0xaa, 0xac, 0x5b, 0xaa, + 0xae, 0x5a, 0xaa, 0x38, 0x60, 0x6c, 0x36, 0x00, + 0xa9, 0x8d, 0x4c, 0xc5, 0x9f, 0xa0, 0xff, 0x8c, + 0x5f, 0xaa, 0xc8, 0x8c, 0x62, 0xaa, 0xee, 0x5f, + 0xaa, 0xa2, 0x00, 0x08, 0xbd, 0x00, 0x02, 0xcd, + 0xb2, 0xaa, 0xd0, 0x01, 0xe8, 0x8e, 0x5d, 0xaa, + 0x20, 0xa4, 0xa1, 0x29, 0x7f, 0x59, 0x84, 0xa8, + 0xc8, 0x0a, 0xf0, 0x02, 0x68, 0x08, 0x90, 0xf0, + 0x28, 0xf0, 0x20, 0xb9, 0x84, 0xa8, 0xd0, 0xd6, + + 0xad, 0x00, 0x02, 0xcd, 0xb2, 0xaa, 0xf0, 0x03, + 0x4c, 0xa4, 0x9f, 0xad, 0x01, 0x02, 0xc9, 0x8d, + 0xd0, 0x06, 0x20, 0x5b, 0xa7, 0x4c, 0x95, 0x9f, + 0x4c, 0xc4, 0xa6, 0x0e, 0x5f, 0xaa, 0xac, 0x5f, + 0xaa, 0x20, 0x5e, 0xa6, 0x90, 0x0c, 0xa9, 0x02, + 0x39, 0x09, 0xa9, 0xf0, 0x05, 0xa9, 0x0f, 0x4c, + 0xd2, 0xa6, 0xc0, 0x06, 0xd0, 0x02, 0x84, 0x33, + 0xa9, 0x20, 0x39, 0x09, 0xa9, 0xf0, 0x61, 0x20, + 0x95, 0xa0, 0x08, 0x20, 0xa4, 0xa1, 0xf0, 0x1e, + 0x0a, 0x90, 0x05, 0x30, 0x03, 0x4c, 0x00, 0xa0, + 0x6a, 0x4c, 0x59, 0xa0, 0x20, 0x93, 0xa1, 0xf0, + 0x0d, 0x99, 0x75, 0xaa, 0xc8, 0xc0, 0x3c, 0x90, + 0xf3, 0x20, 0x93, 0xa1, 0xd0, 0xfb, 0x28, 0xd0, + 0x0f, 0xac, 0x5f, 0xaa, 0xa9, 0x10, 0x39, 0x09, + 0xa9, 0xf0, 0x0c, 0xa0, 0x1e, 0x08, 0xd0, 0xcb, + 0xad, 0x93, 0xaa, 0xc9, 0xa0, 0xf0, 0x13, 0xad, + 0x75, 0xaa, 0xc9, 0xa0, 0xd0, 0x4b, 0xac, 0x5f, + 0xaa, 0xa9, 0xc0, 0x39, 0x09, 0xa9, 0xf0, 0x02, + 0x10, 0x3f, 0x4c, 0x00, 0xa0, 0xa0, 0x3c, 0xa9, + 0xa0, 0x99, 0x74, 0xaa, 0x88, 0xd0, 0xfa, 0x60, + 0x8d, 0x75, 0xaa, 0xa9, 0x0c, 0x39, 0x09, 0xa9, + 0xf0, 0x27, 0x20, 0xb9, 0xa1, 0xb0, 0x1f, 0xa8, + 0xd0, 0x17, 0xe0, 0x11, 0xb0, 0x13, 0xac, 0x5f, + 0xaa, 0xa9, 0x08, 0x39, 0x09, 0xa9, 0xf0, 0x06, + 0xe0, 0x08, 0xb0, 0xce, 0x90, 0x0b, 0x8a, 0xd0, + 0x08, 0xa9, 0x02, 0x4c, 0xd2, 0xa6, 0x4c, 0xc4, + 0xa6, 0xa9, 0x00, 0x8d, 0x65, 0xaa, 0x8d, 0x74, + 0xaa, 0x8d, 0x66, 0xaa, 0x8d, 0x6c, 0xaa, 0x8d, + 0x6d, 0xaa, 0x20, 0xdc, 0xbf, 0xad, 0x5d, 0xaa, + 0x20, 0xa4, 0xa1, 0xd0, 0x1f, 0xc9, 0x8d, 0xd0, + 0xf7, 0xae, 0x5f, 0xaa, 0xad, 0x65, 0xaa, 0x1d, + 0x0a, 0xa9, 0x5d, 0x0a, 0xa9, 0xd0, 0x93, 0xae, + + 0x63, 0xaa, 0xf0, 0x76, 0x8d, 0x63, 0xaa, 0x8e, + 0x5d, 0xaa, 0xd0, 0xdc, 0xa2, 0x0a, 0xdd, 0x40, + 0xa9, 0xf0, 0x05, 0xca, 0xd0, 0xf8, 0xf0, 0xb6, + 0xbd, 0x4a, 0xa9, 0x30, 0x47, 0x0d, 0x65, 0xaa, + 0x8d, 0x65, 0xaa, 0xca, 0x8e, 0x64, 0xaa, 0x20, + 0xb9, 0xa1, 0xb0, 0xa2, 0xad, 0x64, 0xaa, 0x0a, + 0x0a, 0xa8, 0xa5, 0x45, 0xd0, 0x09, 0xa5, 0x44, + 0xd9, 0x55, 0xa9, 0x90, 0x8c, 0xa5, 0x45, 0xd9, + 0x58, 0xa9, 0x90, 0x0b, 0xd0, 0x83, 0xa5, 0x44, + 0xd9, 0x57, 0xa9, 0x90, 0x02, 0xd0, 0xf5, 0xad, + 0x63, 0xaa, 0xd0, 0x94, 0x98, 0x4a, 0xa8, 0xa5, + 0x45, 0x99, 0x67, 0xaa, 0xa5, 0x44, 0x99, 0x66, + 0xaa, 0x4c, 0xe8, 0xa0, 0x48, 0xa9, 0x80, 0x0d, + 0x65, 0xaa, 0x8d, 0x65, 0xaa, 0x68, 0x29, 0x7f, + 0x0d, 0x74, 0xaa, 0x8d, 0x74, 0xaa, 0xd0, 0xe9, + 0xf0, 0x9c, 0x20, 0x80, 0xa1, 0x4c, 0x83, 0x9f, + 0x20, 0x5b, 0xa7, 0x20, 0xae, 0xa1, 0xad, 0x5f, + 0xaa, 0xaa, 0xbd, 0x1f, 0x9d, 0x48, 0xbd, 0x1e, + 0x9d, 0x48, 0x60, 0xae, 0x5d, 0xaa, 0xbd, 0x00, + 0x02, 0xc9, 0x8d, 0xf0, 0x06, 0xe8, 0x8e, 0x5d, + 0xaa, 0xc9, 0xac, 0x60, 0x20, 0x93, 0xa1, 0xf0, + 0xfa, 0xc9, 0xa0, 0xf0, 0xf7, 0x60, 0xa9, 0x00, + 0xa0, 0x16, 0x99, 0xba, 0xb5, 0x88, 0xd0, 0xfa, + 0x60, 0xa9, 0x00, 0x85, 0x44, 0x85, 0x45, 0x20, + 0xa4, 0xa1, 0x08, 0xc9, 0xa4, 0xf0, 0x3c, 0x28, + 0x4c, 0xce, 0xa1, 0x20, 0xa4, 0xa1, 0xd0, 0x06, + 0xa6, 0x44, 0xa5, 0x45, 0x18, 0x60, 0x38, 0xe9, + 0xb0, 0x30, 0x21, 0xc9, 0x0a, 0xb0, 0x1d, 0x20, + 0xfe, 0xa1, 0x65, 0x44, 0xaa, 0xa9, 0x00, 0x65, + 0x45, 0xa8, 0x20, 0xfe, 0xa1, 0x20, 0xfe, 0xa1, + 0x8a, 0x65, 0x44, 0x85, 0x44, 0x98, 0x65, 0x45, + 0x85, 0x45, 0x90, 0xcf, 0x38, 0x60, 0x06, 0x44, + + 0x26, 0x45, 0x60, 0x28, 0x20, 0xa4, 0xa1, 0xf0, + 0xc5, 0x38, 0xe9, 0xb0, 0x30, 0xee, 0xc9, 0x0a, + 0x90, 0x08, 0xe9, 0x07, 0x30, 0xe6, 0xc9, 0x10, + 0xb0, 0xe2, 0xa2, 0x04, 0x20, 0xfe, 0xa1, 0xca, + 0xd0, 0xfa, 0x05, 0x44, 0x85, 0x44, 0x4c, 0x04, + 0xa2, 0xa5, 0x44, 0x4c, 0x95, 0xfe, 0xa5, 0x44, + 0x4c, 0x8b, 0xfe, 0xad, 0x5e, 0xaa, 0x0d, 0x74, + 0xaa, 0x8d, 0x5e, 0xaa, 0x60, 0x2c, 0x74, 0xaa, + 0x50, 0x03, 0x20, 0xc8, 0x9f, 0xa9, 0x70, 0x4d, + 0x74, 0xaa, 0x2d, 0x5e, 0xaa, 0x8d, 0x5e, 0xaa, + 0x60, 0xa9, 0x00, 0x8d, 0xb3, 0xaa, 0xa5, 0x44, + 0x48, 0x20, 0x16, 0xa3, 0x68, 0x8d, 0x57, 0xaa, + 0x4c, 0xd4, 0xa7, 0xa9, 0x05, 0x20, 0xaa, 0xa2, + 0x20, 0x64, 0xa7, 0xa0, 0x00, 0x98, 0x91, 0x40, + 0x60, 0xa9, 0x07, 0xd0, 0x02, 0xa9, 0x08, 0x20, + 0xaa, 0xa2, 0x4c, 0xea, 0xa2, 0xa9, 0x0c, 0xd0, + 0xf6, 0xad, 0x08, 0x9d, 0x8d, 0xbd, 0xb5, 0xad, + 0x09, 0x9d, 0x8d, 0xbe, 0xb5, 0xa9, 0x09, 0x8d, + 0x63, 0xaa, 0x20, 0xc8, 0xa2, 0x4c, 0xea, 0xa2, + 0x20, 0xa3, 0xa2, 0x20, 0x8c, 0xa6, 0xd0, 0xfb, + 0x4c, 0x71, 0xb6, 0xa9, 0x00, 0x4c, 0xd5, 0xa3, + 0xa9, 0x01, 0x8d, 0x63, 0xaa, 0xad, 0x6c, 0xaa, + 0xd0, 0x0a, 0xad, 0x6d, 0xaa, 0xd0, 0x05, 0xa9, + 0x01, 0x8d, 0x6c, 0xaa, 0xad, 0x6c, 0xaa, 0x8d, + 0xbd, 0xb5, 0xad, 0x6d, 0xaa, 0x8d, 0xbe, 0xb5, + 0x20, 0xea, 0xa2, 0xa5, 0x45, 0xd0, 0x03, 0x4c, + 0xc8, 0xa6, 0x85, 0x41, 0xa5, 0x44, 0x85, 0x40, + 0x20, 0x43, 0xa7, 0x20, 0x4e, 0xa7, 0x20, 0x1a, + 0xa7, 0xad, 0x63, 0xaa, 0x8d, 0xbb, 0xb5, 0x4c, + 0xa8, 0xa6, 0xad, 0x75, 0xaa, 0xc9, 0xa0, 0xf0, + 0x25, 0x20, 0x64, 0xa7, 0xb0, 0x3a, 0x20, 0xfc, + 0xa2, 0x4c, 0xea, 0xa2, 0x20, 0xaf, 0xa7, 0xd0, + + 0x05, 0xa9, 0x00, 0x8d, 0xb3, 0xaa, 0xa0, 0x00, + 0x98, 0x91, 0x40, 0x20, 0x4e, 0xa7, 0xa9, 0x02, + 0x8d, 0xbb, 0xb5, 0x4c, 0xa8, 0xa6, 0x20, 0x92, + 0xa7, 0xd0, 0x05, 0x20, 0x9a, 0xa7, 0xf0, 0x10, + 0x20, 0xaf, 0xa7, 0xf0, 0xf6, 0x20, 0xaa, 0xa7, + 0xf0, 0xf1, 0x20, 0xfc, 0xa2, 0x4c, 0x16, 0xa3, + 0x60, 0xa9, 0x09, 0x2d, 0x65, 0xaa, 0xc9, 0x09, + 0xf0, 0x03, 0x4c, 0x00, 0xa0, 0xa9, 0x04, 0x20, + 0xd5, 0xa3, 0xad, 0x73, 0xaa, 0xac, 0x72, 0xaa, + 0x20, 0xe0, 0xa3, 0xad, 0x6d, 0xaa, 0xac, 0x6c, + 0xaa, 0x20, 0xe0, 0xa3, 0xad, 0x73, 0xaa, 0xac, + 0x72, 0xaa, 0x4c, 0xff, 0xa3, 0x20, 0xa8, 0xa2, + 0xa9, 0x7f, 0x2d, 0xc2, 0xb5, 0xc9, 0x04, 0xf0, + 0x03, 0x4c, 0xd0, 0xa6, 0xa9, 0x04, 0x20, 0xd5, + 0xa3, 0x20, 0x7a, 0xa4, 0xaa, 0xad, 0x65, 0xaa, + 0x29, 0x01, 0xd0, 0x06, 0x8e, 0x72, 0xaa, 0x8c, + 0x73, 0xaa, 0x20, 0x7a, 0xa4, 0xae, 0x72, 0xaa, + 0xac, 0x73, 0xaa, 0x4c, 0x71, 0xa4, 0x20, 0x5d, + 0xa3, 0x20, 0x51, 0xa8, 0x6c, 0x72, 0xaa, 0xad, + 0xb6, 0xaa, 0xf0, 0x20, 0xa5, 0xd6, 0x10, 0x03, + 0x4c, 0xcc, 0xa6, 0xa9, 0x02, 0x20, 0xd5, 0xa3, + 0x38, 0xa5, 0xaf, 0xe5, 0x67, 0xa8, 0xa5, 0xb0, + 0xe5, 0x68, 0x20, 0xe0, 0xa3, 0xa5, 0x68, 0xa4, + 0x67, 0x4c, 0xff, 0xa3, 0xa9, 0x01, 0x20, 0xd5, + 0xa3, 0x38, 0xa5, 0x4c, 0xe5, 0xca, 0xa8, 0xa5, + 0x4d, 0xe5, 0xcb, 0x20, 0xe0, 0xa3, 0xa5, 0xcb, + 0xa4, 0xca, 0x4c, 0xff, 0xa3, 0x8d, 0xc2, 0xb5, + 0x48, 0x20, 0xa8, 0xa2, 0x68, 0x4c, 0xc4, 0xa7, + 0x8c, 0xc1, 0xb5, 0x8c, 0xc3, 0xb5, 0x8d, 0xc2, + 0xb5, 0xa9, 0x04, 0x8d, 0xbb, 0xb5, 0xa9, 0x01, + 0x8d, 0xbc, 0xb5, 0x20, 0xa8, 0xa6, 0xad, 0xc2, + 0xb5, 0x8d, 0xc3, 0xb5, 0x4c, 0xa8, 0xa6, 0x8c, + + 0xc3, 0xb5, 0x8d, 0xc4, 0xb5, 0xa9, 0x02, 0x4c, + 0x86, 0xb6, 0x20, 0xa8, 0xa6, 0x4c, 0xea, 0xa2, + 0x4c, 0xd0, 0xa6, 0x20, 0x16, 0xa3, 0x20, 0xa8, + 0xa2, 0xa9, 0x23, 0x2d, 0xc2, 0xb5, 0xf0, 0xf0, + 0x8d, 0xc2, 0xb5, 0xad, 0xb6, 0xaa, 0xf0, 0x28, + 0xa9, 0x02, 0x20, 0xb1, 0xa4, 0x20, 0x7a, 0xa4, + 0x18, 0x65, 0x67, 0xaa, 0x98, 0x65, 0x68, 0xc5, + 0x74, 0xb0, 0x70, 0x85, 0xb0, 0x85, 0x6a, 0x86, + 0xaf, 0x86, 0x69, 0xa6, 0x67, 0xa4, 0x68, 0x20, + 0x71, 0xa4, 0x20, 0x51, 0xa8, 0x6c, 0x60, 0x9d, + 0xa9, 0x01, 0x20, 0xb1, 0xa4, 0x20, 0x7a, 0xa4, + 0x38, 0xa5, 0x4c, 0xed, 0x60, 0xaa, 0xaa, 0xa5, + 0x4d, 0xed, 0x61, 0xaa, 0x90, 0x45, 0xa8, 0xc4, + 0x4b, 0x90, 0x40, 0xf0, 0x3e, 0x84, 0xcb, 0x86, + 0xca, 0x8e, 0xc3, 0xb5, 0x8c, 0xc4, 0xb5, 0x4c, + 0x0a, 0xa4, 0xad, 0x0a, 0x9d, 0x8d, 0xc3, 0xb5, + 0xad, 0x0b, 0x9d, 0x8d, 0xc4, 0xb5, 0xa9, 0x00, + 0x8d, 0xc2, 0xb5, 0xa9, 0x02, 0x8d, 0xc1, 0xb5, + 0xa9, 0x03, 0x8d, 0xbb, 0xb5, 0xa9, 0x02, 0x8d, + 0xbc, 0xb5, 0x20, 0xa8, 0xa6, 0xad, 0x61, 0xaa, + 0x8d, 0xc2, 0xb5, 0xa8, 0xad, 0x60, 0xaa, 0x8d, + 0xc1, 0xb5, 0x60, 0x20, 0xea, 0xa2, 0x4c, 0xcc, + 0xa6, 0xcd, 0xc2, 0xb5, 0xf0, 0x1a, 0xae, 0x5f, + 0xaa, 0x8e, 0x62, 0xaa, 0x4a, 0xf0, 0x03, 0x4c, + 0x9e, 0xa5, 0xa2, 0x1d, 0xbd, 0x75, 0xaa, 0x9d, + 0x93, 0xaa, 0xca, 0x10, 0xf7, 0x4c, 0x7a, 0xa5, + 0x60, 0xad, 0xb6, 0xaa, 0xf0, 0x03, 0x8d, 0xb7, + 0xaa, 0x20, 0x13, 0xa4, 0x20, 0xc8, 0x9f, 0x20, + 0x51, 0xa8, 0x6c, 0x58, 0x9d, 0xa5, 0x4a, 0x85, + 0xcc, 0xa5, 0x4b, 0x85, 0xcd, 0x6c, 0x56, 0x9d, + 0x20, 0x16, 0xa4, 0x20, 0xc8, 0x9f, 0x20, 0x51, + 0xa8, 0x6c, 0x56, 0x9d, 0x20, 0x65, 0xd6, 0x85, + + 0x33, 0x85, 0xd8, 0x4c, 0xd2, 0xd7, 0x20, 0x65, + 0x0e, 0x85, 0x33, 0x85, 0xd8, 0x4c, 0xd4, 0x0f, + 0x20, 0x26, 0xa5, 0xa9, 0x05, 0x8d, 0x52, 0xaa, + 0x4c, 0x83, 0x9f, 0x20, 0x26, 0xa5, 0xa9, 0x01, + 0x8d, 0x51, 0xaa, 0x4c, 0x83, 0x9f, 0x20, 0x64, + 0xa7, 0x90, 0x06, 0x20, 0xa3, 0xa2, 0x4c, 0x34, + 0xa5, 0x20, 0x4e, 0xa7, 0xad, 0x65, 0xaa, 0x29, + 0x06, 0xf0, 0x13, 0xa2, 0x03, 0xbd, 0x6e, 0xaa, + 0x9d, 0xbd, 0xb5, 0xca, 0x10, 0xf7, 0xa9, 0x0a, + 0x8d, 0xbb, 0xb5, 0x20, 0xa8, 0xa6, 0x60, 0xa9, + 0x40, 0x2d, 0x65, 0xaa, 0xf0, 0x05, 0xad, 0x66, + 0xaa, 0xd0, 0x05, 0xa9, 0xfe, 0x8d, 0x66, 0xaa, + 0xad, 0x0d, 0x9d, 0x8d, 0xbc, 0xb5, 0xa9, 0x0b, + 0x20, 0xaa, 0xa2, 0x4c, 0x97, 0xa3, 0xa9, 0x06, + 0x20, 0xaa, 0xa2, 0xad, 0xbf, 0xb5, 0x8d, 0x66, + 0xaa, 0x60, 0xa9, 0x4c, 0x20, 0xb2, 0xa5, 0xf0, + 0x2e, 0xa9, 0x00, 0x8d, 0xb6, 0xaa, 0xa0, 0x1e, + 0x20, 0x97, 0xa0, 0xa2, 0x09, 0xbd, 0xb7, 0xaa, + 0x9d, 0x74, 0xaa, 0xca, 0xd0, 0xf7, 0xa9, 0xc0, + 0x8d, 0x51, 0xaa, 0x4c, 0xd1, 0xa4, 0xa9, 0x20, + 0x20, 0xb2, 0xa5, 0xf0, 0x05, 0xa9, 0x01, 0x4c, + 0xd2, 0xa6, 0xa9, 0x00, 0x8d, 0xb7, 0xaa, 0x4c, + 0x84, 0x9d, 0xcd, 0x00, 0xe0, 0xf0, 0x0e, 0x8d, + 0x80, 0xc0, 0xcd, 0x00, 0xe0, 0xf0, 0x06, 0x8d, + 0x81, 0xc0, 0xcd, 0x00, 0xe0, 0x60, 0x20, 0xa3, + 0xa2, 0xad, 0x4f, 0xaa, 0x8d, 0xb4, 0xaa, 0xad, + 0x50, 0xaa, 0x8d, 0xb5, 0xaa, 0xad, 0x75, 0xaa, + 0x8d, 0xb3, 0xaa, 0xd0, 0x0e, 0x20, 0x64, 0xa7, + 0x90, 0x06, 0x20, 0xa3, 0xa2, 0x4c, 0xeb, 0xa5, + 0x20, 0x4e, 0xa7, 0xad, 0x65, 0xaa, 0x29, 0x04, + 0xf0, 0x1b, 0xad, 0x6e, 0xaa, 0xd0, 0x08, 0xae, + 0x6f, 0xaa, 0xf0, 0x11, 0xce, 0x6f, 0xaa, 0xce, + + 0x6e, 0xaa, 0x20, 0x8c, 0xa6, 0xf0, 0x38, 0xc9, + 0x8d, 0xd0, 0xf7, 0xf0, 0xe5, 0x60, 0x20, 0x5e, + 0xa6, 0xb0, 0x66, 0xad, 0x5c, 0xaa, 0x8d, 0xc3, + 0xb5, 0xa9, 0x04, 0x8d, 0xbb, 0xb5, 0xa9, 0x01, + 0x8d, 0xbc, 0xb5, 0x4c, 0xa8, 0xa6, 0x20, 0x5e, + 0xa6, 0xb0, 0x4e, 0xa9, 0x06, 0x8d, 0x52, 0xaa, + 0x20, 0x8c, 0xa6, 0xd0, 0x0f, 0x20, 0xfc, 0xa2, + 0xa9, 0x03, 0xcd, 0x52, 0xaa, 0xf0, 0xce, 0xa9, + 0x05, 0x4c, 0xd2, 0xa6, 0xc9, 0xe0, 0x90, 0x02, + 0x29, 0x7f, 0x8d, 0x5c, 0xaa, 0xae, 0x5a, 0xaa, + 0xf0, 0x09, 0xca, 0xbd, 0x00, 0x02, 0x09, 0x80, + 0x9d, 0x00, 0x02, 0x4c, 0xb3, 0x9f, 0x48, 0xad, + 0xb6, 0xaa, 0xf0, 0x0e, 0xa6, 0x76, 0xe8, 0xf0, + 0x0d, 0xa6, 0x33, 0xe0, 0xdd, 0xf0, 0x07, 0x68, + 0x18, 0x60, 0xa5, 0xd9, 0x30, 0xf9, 0x68, 0x38, + 0x60, 0x20, 0xfc, 0xa2, 0x20, 0x5b, 0xa7, 0x4c, + 0xb3, 0x9f, 0x20, 0x9d, 0xa6, 0x20, 0x4e, 0xa7, + 0xa9, 0x03, 0xd0, 0xa1, 0xa9, 0x03, 0x8d, 0xbb, + 0xb5, 0xa9, 0x01, 0x8d, 0xbc, 0xb5, 0x20, 0xa8, + 0xa6, 0xad, 0xc3, 0xb5, 0x60, 0xad, 0xb5, 0xaa, + 0x85, 0x41, 0xad, 0xb4, 0xaa, 0x85, 0x40, 0x60, + 0x20, 0x06, 0xab, 0x90, 0x16, 0xad, 0xc5, 0xb5, + 0xc9, 0x05, 0xf0, 0x03, 0x4c, 0x5e, 0xb6, 0x4c, + 0x92, 0xb6, 0xea, 0xea, 0xea, 0xea, 0xa2, 0x00, + 0x8e, 0xc3, 0xb5, 0x60, 0xa9, 0x0b, 0xd0, 0x0a, + 0xa9, 0x0c, 0xd0, 0x06, 0xa9, 0x0e, 0xd0, 0x02, + 0xa9, 0x0d, 0x8d, 0x5c, 0xaa, 0x20, 0xe6, 0xbf, + 0xad, 0xb6, 0xaa, 0xf0, 0x04, 0xa5, 0xd8, 0x30, + 0x0e, 0xa2, 0x00, 0x20, 0x02, 0xa7, 0xae, 0x5c, + 0xaa, 0x20, 0x02, 0xa7, 0x20, 0xc8, 0x9f, 0x20, + 0x51, 0xa8, 0x20, 0x5e, 0xa6, 0xae, 0x5c, 0xaa, + 0xa9, 0x03, 0xb0, 0x03, 0x6c, 0x5a, 0x9d, 0x6c, + + 0x5e, 0x9d, 0xbd, 0x3f, 0xaa, 0xaa, 0x8e, 0x63, + 0xaa, 0xbd, 0x71, 0xa9, 0x48, 0x09, 0x80, 0x20, + 0xc5, 0x9f, 0xae, 0x63, 0xaa, 0xe8, 0x68, 0x10, + 0xed, 0x60, 0xad, 0x66, 0xaa, 0x8d, 0xbf, 0xb5, + 0xad, 0x68, 0xaa, 0x8d, 0xc0, 0xb5, 0xad, 0x6a, + 0xaa, 0x8d, 0xc1, 0xb5, 0xad, 0x06, 0x9d, 0x8d, + 0xc3, 0xb5, 0xad, 0x07, 0x9d, 0x8d, 0xc4, 0xb5, + 0xa5, 0x40, 0x8d, 0x4f, 0xaa, 0xa5, 0x41, 0x8d, + 0x50, 0xaa, 0x60, 0xa0, 0x1d, 0xb9, 0x75, 0xaa, + 0x91, 0x40, 0x88, 0x10, 0xf8, 0x60, 0xa0, 0x1e, + 0xb1, 0x40, 0x99, 0xa9, 0xb5, 0xc8, 0xc0, 0x26, + 0xd0, 0xf6, 0x60, 0xa0, 0x00, 0x8c, 0x51, 0xaa, + 0x8c, 0x52, 0xaa, 0x60, 0xa9, 0x00, 0x85, 0x45, + 0x20, 0x92, 0xa7, 0x4c, 0x73, 0xa7, 0x20, 0x9a, + 0xa7, 0xf0, 0x1d, 0x20, 0xaa, 0xa7, 0xd0, 0x0a, + 0xa5, 0x40, 0x85, 0x44, 0xa5, 0x41, 0x85, 0x45, + 0xd0, 0xec, 0xa0, 0x1d, 0xb1, 0x40, 0xd9, 0x75, + 0xaa, 0xd0, 0xe3, 0x88, 0x10, 0xf6, 0x18, 0x60, + 0x38, 0x60, 0xad, 0x00, 0x9d, 0xae, 0x01, 0x9d, + 0xd0, 0x0a, 0xa0, 0x25, 0xb1, 0x40, 0xf0, 0x09, + 0xaa, 0x88, 0xb1, 0x40, 0x86, 0x41, 0x85, 0x40, + 0x8a, 0x60, 0xa0, 0x00, 0xb1, 0x40, 0x60, 0xad, + 0xb3, 0xaa, 0xf0, 0x0e, 0xad, 0xb4, 0xaa, 0xc5, + 0x40, 0xd0, 0x08, 0xad, 0xb5, 0xaa, 0xc5, 0x41, + 0xf0, 0x01, 0xca, 0x60, 0x4d, 0xc2, 0xb5, 0xf0, + 0x0a, 0x29, 0x7f, 0xf0, 0x06, 0x20, 0xea, 0xa2, + 0x4c, 0xd0, 0xa6, 0x60, 0x38, 0xad, 0x00, 0x9d, + 0x85, 0x40, 0xad, 0x01, 0x9d, 0x85, 0x41, 0xad, + 0x57, 0xaa, 0x8d, 0x63, 0xaa, 0xa0, 0x00, 0x98, + 0x91, 0x40, 0xa0, 0x1e, 0x38, 0xa5, 0x40, 0xe9, + 0x2d, 0x91, 0x40, 0x48, 0xa5, 0x41, 0xe9, 0x00, + 0xc8, 0x91, 0x40, 0xaa, 0xca, 0x68, 0x48, 0xc8, + + 0x91, 0x40, 0x8a, 0xc8, 0x91, 0x40, 0xaa, 0xca, + 0x68, 0x48, 0xc8, 0x91, 0x40, 0xc8, 0x8a, 0x91, + 0x40, 0xce, 0x63, 0xaa, 0xf0, 0x17, 0xaa, 0x68, + 0x38, 0xe9, 0x26, 0xc8, 0x91, 0x40, 0x48, 0x8a, + 0xe9, 0x00, 0xc8, 0x91, 0x40, 0x85, 0x41, 0x68, + 0x85, 0x40, 0x4c, 0xe5, 0xa7, 0x48, 0xa9, 0x00, + 0xc8, 0x91, 0x40, 0xc8, 0x91, 0x40, 0xad, 0xb6, + 0xaa, 0xf0, 0x0b, 0x68, 0x85, 0x74, 0x85, 0x70, + 0x68, 0x85, 0x73, 0x85, 0x6f, 0x60, 0x68, 0x85, + 0x4d, 0x85, 0xcb, 0x68, 0x85, 0x4c, 0x85, 0xca, + 0x60, 0xa5, 0x39, 0xcd, 0x03, 0x9d, 0xf0, 0x12, + 0x8d, 0x56, 0xaa, 0xa5, 0x38, 0x8d, 0x55, 0xaa, + 0xad, 0x02, 0x9d, 0x85, 0x38, 0xad, 0x03, 0x9d, + 0x85, 0x39, 0xa5, 0x37, 0xcd, 0x05, 0x9d, 0xf0, + 0x12, 0x8d, 0x54, 0xaa, 0xa5, 0x36, 0x8d, 0x53, + 0xaa, 0xad, 0x04, 0x9d, 0x85, 0x36, 0xad, 0x05, + 0x9d, 0x85, 0x37, 0x60, 0x49, 0x4e, 0x49, 0xd4, + 0x4c, 0x4f, 0x41, 0xc4, 0x53, 0x41, 0x56, 0xc5, + 0x52, 0x55, 0xce, 0x43, 0x48, 0x41, 0x49, 0xce, + 0x44, 0x45, 0x4c, 0x45, 0x54, 0xc5, 0x4c, 0x4f, + 0x43, 0xcb, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0xcb, + 0x43, 0x4c, 0x4f, 0x53, 0xc5, 0x52, 0x45, 0x41, + 0xc4, 0x45, 0x58, 0x45, 0xc3, 0x57, 0x52, 0x49, + 0x54, 0xc5, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, + 0x4f, 0xce, 0x4f, 0x50, 0x45, 0xce, 0x41, 0x50, + 0x50, 0x45, 0x4e, 0xc4, 0x52, 0x45, 0x4e, 0x41, + 0x4d, 0xc5, 0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, + 0xc7, 0x4d, 0x4f, 0xce, 0x4e, 0x4f, 0x4d, 0x4f, + 0xce, 0x50, 0x52, 0xa3, 0x49, 0x4e, 0xa3, 0x4d, + 0x41, 0x58, 0x46, 0x49, 0x4c, 0x45, 0xd3, 0x46, + 0xd0, 0x49, 0x4e, 0xd4, 0x42, 0x53, 0x41, 0x56, + 0xc5, 0x42, 0x4c, 0x4f, 0x41, 0xc4, 0x42, 0x52, + + 0x55, 0xce, 0x56, 0x45, 0x52, 0x49, 0x46, 0xd9, + 0x00, 0x21, 0x70, 0xa0, 0x70, 0xa1, 0x70, 0xa0, + 0x70, 0x20, 0x70, 0x20, 0x70, 0x20, 0x70, 0x20, + 0x70, 0x60, 0x00, 0x22, 0x06, 0x20, 0x74, 0x22, + 0x06, 0x22, 0x04, 0x23, 0x78, 0x22, 0x70, 0x30, + 0x70, 0x40, 0x70, 0x40, 0x80, 0x40, 0x80, 0x08, + 0x00, 0x08, 0x00, 0x04, 0x00, 0x40, 0x70, 0x40, + 0x00, 0x21, 0x79, 0x20, 0x71, 0x20, 0x71, 0x20, + 0x70, 0xd6, 0xc4, 0xd3, 0xcc, 0xd2, 0xc2, 0xc1, + 0xc3, 0xc9, 0xcf, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0xc0, 0xa0, 0x90, 0x00, 0x00, 0xfe, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, + 0x00, 0x01, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xff, + 0x7f, 0x00, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xff, + 0xff, 0x0d, 0x07, 0x8d, 0x4c, 0x41, 0x4e, 0x47, + 0x55, 0x41, 0x47, 0x45, 0x20, 0x4e, 0x4f, 0x54, + 0x20, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, + 0x4c, 0xc5, 0x52, 0x41, 0x4e, 0x47, 0x45, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x57, 0x52, 0x49, + 0x54, 0x45, 0x20, 0x50, 0x52, 0x4f, 0x54, 0x45, + 0x43, 0x54, 0x45, 0xc4, 0x45, 0x4e, 0x44, 0x20, + 0x4f, 0x46, 0x20, 0x44, 0x41, 0x54, 0xc1, 0x46, + 0x49, 0x4c, 0x45, 0x20, 0x4e, 0x4f, 0x54, 0x20, + 0x46, 0x4f, 0x55, 0x4e, 0xc4, 0x56, 0x4f, 0x4c, + 0x55, 0x4d, 0x45, 0x20, 0x4d, 0x49, 0x53, 0x4d, + 0x41, 0x54, 0x43, 0xc8, 0x49, 0x2f, 0x4f, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x44, 0x49, 0x53, + 0x4b, 0x20, 0x46, 0x55, 0x4c, 0xcc, 0x46, 0x49, + 0x4c, 0x45, 0x20, 0x4c, 0x4f, 0x43, 0x4b, 0x45, + 0xc4, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x4e, 0x4f, 0x20, + 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x53, 0x20, + + 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, + 0xc5, 0x46, 0x49, 0x4c, 0x45, 0x20, 0x54, 0x59, + 0x50, 0x45, 0x20, 0x4d, 0x49, 0x53, 0x4d, 0x41, + 0x54, 0x43, 0xc8, 0x50, 0x52, 0x4f, 0x47, 0x52, + 0x41, 0x4d, 0x20, 0x54, 0x4f, 0x4f, 0x20, 0x4c, + 0x41, 0x52, 0x47, 0xc5, 0x4e, 0x4f, 0x54, 0x20, + 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20, 0x43, + 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0xc4, 0x8d, 0x00, + 0x03, 0x19, 0x19, 0x24, 0x33, 0x3e, 0x4c, 0x5b, + 0x64, 0x6d, 0x78, 0x84, 0x98, 0xaa, 0xbb, 0x2d, + 0x98, 0x00, 0x00, 0xf0, 0xfd, 0x1b, 0xfd, 0x03, + 0x03, 0xf4, 0x0d, 0x28, 0x8d, 0x0d, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x0b, 0x01, 0x20, 0xfe, 0x00, + 0x02, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xd0, 0x00, 0xc8, 0xc5, 0xcc, + 0xcc, 0xcf, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0x03, 0x84, 0x00, 0x00, 0x00, 0x40, 0x00, + 0xc1, 0xd0, 0xd0, 0xcc, 0xc5, 0xd3, 0xcf, 0xc6, + 0xd4, 0xe8, 0xb7, 0xbb, 0xb3, 0xbb, 0xb4, 0x00, + 0xc0, 0x7e, 0xb3, 0x21, 0xab, 0x05, 0xac, 0x57, + 0xac, 0x6f, 0xac, 0x2a, 0xad, 0x97, 0xad, 0xee, + 0xac, 0xf5, 0xac, 0x39, 0xac, 0x11, 0xad, 0x8d, + 0xae, 0x17, 0xad, 0x7e, 0xb3, 0x7e, 0xb3, 0x89, + 0xac, 0x95, 0xac, 0x86, 0xac, 0x92, 0xac, 0x7e, + 0xb3, 0x7e, 0xb3, 0xbd, 0xac, 0xc9, 0xac, 0xba, + 0xac, 0xc6, 0xac, 0x7e, 0xb3, 0xe0, 0x00, 0xf0, + + 0x02, 0xa2, 0x02, 0x8e, 0x5f, 0xaa, 0xba, 0x8e, + 0x9b, 0xb3, 0x20, 0x6a, 0xae, 0xad, 0xbb, 0xb5, + 0xc9, 0x0d, 0xb0, 0x0b, 0x0a, 0xaa, 0xbd, 0xca, + 0xaa, 0x48, 0xbd, 0xc9, 0xaa, 0x48, 0x60, 0x4c, + 0x63, 0xb3, 0x20, 0x28, 0xab, 0x4c, 0x7f, 0xb3, + 0x20, 0xdc, 0xab, 0xa9, 0x01, 0x8d, 0xe3, 0xb5, + 0xae, 0xbe, 0xb5, 0xad, 0xbd, 0xb5, 0xd0, 0x05, + 0xe0, 0x00, 0xd0, 0x01, 0xe8, 0x8d, 0xe8, 0xb5, + 0x8e, 0xe9, 0xb5, 0x20, 0xc9, 0xb1, 0x90, 0x5e, + 0x8e, 0x9c, 0xb3, 0xae, 0x5f, 0xaa, 0xbd, 0x09, + 0xa9, 0xae, 0x9c, 0xb3, 0x4a, 0xb0, 0x0d, 0xad, + 0x51, 0xaa, 0xc9, 0xc0, 0xd0, 0x03, 0x4c, 0x5f, + 0xb3, 0x4c, 0x73, 0xb3, 0xa9, 0x00, 0x9d, 0xe8, + 0xb4, 0xa9, 0x01, 0x9d, 0xe7, 0xb4, 0x8e, 0x9c, + 0xb3, 0x20, 0x44, 0xb2, 0xae, 0x9c, 0xb3, 0x9d, + 0xc7, 0xb4, 0x8d, 0xd2, 0xb5, 0x8d, 0xd4, 0xb5, + 0xad, 0xf1, 0xb5, 0x9d, 0xc6, 0xb4, 0x8d, 0xd1, + 0xb5, 0x8d, 0xd3, 0xb5, 0xad, 0xc2, 0xb5, 0x9d, + 0xc8, 0xb4, 0x20, 0x37, 0xb0, 0x20, 0x0c, 0xaf, + 0x20, 0xd6, 0xb7, 0x20, 0x3a, 0xaf, 0xae, 0x9c, + 0xb3, 0xa9, 0x06, 0x8d, 0xc5, 0xb5, 0xbd, 0xc6, + 0xb4, 0x8d, 0xd1, 0xb5, 0xbd, 0xc7, 0xb4, 0x8d, + 0xd2, 0xb5, 0xbd, 0xc8, 0xb4, 0x8d, 0xc2, 0xb5, + 0x8d, 0xf6, 0xb5, 0xbd, 0xe7, 0xb4, 0x8d, 0xee, + 0xb5, 0xbd, 0xe8, 0xb4, 0x8d, 0xef, 0xb5, 0x8e, + 0xd9, 0xb5, 0xa9, 0xff, 0x8d, 0xe0, 0xb5, 0x8d, + 0xe1, 0xb5, 0xad, 0xe2, 0xb3, 0x8d, 0xda, 0xb5, + 0x18, 0x4c, 0x5e, 0xaf, 0xa9, 0x00, 0xaa, 0x9d, + 0xd1, 0xb5, 0xe8, 0xe0, 0x2d, 0xd0, 0xf8, 0xad, + 0xbf, 0xb5, 0x49, 0xff, 0x8d, 0xf9, 0xb5, 0xad, + 0xc0, 0xb5, 0x8d, 0xf8, 0xb5, 0xad, 0xc1, 0xb5, + 0x0a, 0x0a, 0x0a, 0x0a, 0xaa, 0x8e, 0xf7, 0xb5, + + 0xa9, 0x11, 0x8d, 0xfa, 0xb5, 0x60, 0x20, 0x1d, + 0xaf, 0x20, 0x34, 0xaf, 0x20, 0xc3, 0xb2, 0xa9, + 0x02, 0x2d, 0xd5, 0xb5, 0xf0, 0x21, 0x20, 0xf7, + 0xaf, 0xa9, 0x00, 0x18, 0x20, 0x11, 0xb0, 0x38, + 0xce, 0xd8, 0xb5, 0xd0, 0xf7, 0xae, 0xd9, 0xb5, + 0xad, 0xee, 0xb5, 0x9d, 0xe7, 0xb4, 0xad, 0xef, + 0xb5, 0x9d, 0xe8, 0xb4, 0x20, 0x37, 0xb0, 0x4c, + 0x7f, 0xb3, 0x20, 0x28, 0xab, 0xad, 0xf6, 0xb5, + 0x30, 0x2b, 0xad, 0xbd, 0xb5, 0x85, 0x42, 0xad, + 0xbe, 0xb5, 0x85, 0x43, 0xae, 0x9c, 0xb3, 0x20, + 0x1c, 0xb2, 0x20, 0x37, 0xb0, 0x4c, 0x7f, 0xb3, + 0xad, 0xbc, 0xb5, 0xc9, 0x05, 0xb0, 0x0b, 0x0a, + 0xaa, 0xbd, 0xe6, 0xaa, 0x48, 0xbd, 0xe5, 0xaa, + 0x48, 0x60, 0x4c, 0x67, 0xb3, 0x4c, 0x7b, 0xb3, + 0xad, 0xf6, 0xb5, 0x30, 0xf8, 0xad, 0xbc, 0xb5, + 0xc9, 0x05, 0xb0, 0xee, 0x0a, 0xaa, 0xbd, 0xf2, + 0xaa, 0x48, 0xbd, 0xf1, 0xaa, 0x48, 0x60, 0x20, + 0x00, 0xb3, 0x20, 0xa8, 0xac, 0x8d, 0xc3, 0xb5, + 0x4c, 0x7f, 0xb3, 0x20, 0x00, 0xb3, 0x20, 0xb5, + 0xb1, 0x20, 0xa8, 0xac, 0x48, 0x20, 0xa2, 0xb1, + 0xa0, 0x00, 0x68, 0x91, 0x42, 0x4c, 0x96, 0xac, + 0x20, 0xb6, 0xb0, 0xb0, 0x0b, 0xb1, 0x42, 0x48, + 0x20, 0x5b, 0xb1, 0x20, 0x94, 0xb1, 0x68, 0x60, + 0x4c, 0x6f, 0xb3, 0x20, 0x00, 0xb3, 0xad, 0xc3, + 0xb5, 0x20, 0xda, 0xac, 0x4c, 0x7f, 0xb3, 0x20, + 0x00, 0xb3, 0x20, 0xa2, 0xb1, 0xa0, 0x00, 0xb1, + 0x42, 0x20, 0xda, 0xac, 0x20, 0xb5, 0xb1, 0x4c, + 0xca, 0xac, 0x48, 0x20, 0xb6, 0xb0, 0x68, 0x91, + 0x42, 0xa9, 0x40, 0x0d, 0xd5, 0xb5, 0x8d, 0xd5, + 0xb5, 0x20, 0x5b, 0xb1, 0x4c, 0x94, 0xb1, 0xa9, + 0x80, 0x8d, 0x9e, 0xb3, 0xd0, 0x05, 0xa9, 0x00, + 0x8d, 0x9e, 0xb3, 0x20, 0x28, 0xab, 0xae, 0x9c, + + 0xb3, 0xbd, 0xc8, 0xb4, 0x29, 0x7f, 0x0d, 0x9e, + 0xb3, 0x9d, 0xc8, 0xb4, 0x20, 0x37, 0xb0, 0x4c, + 0x7f, 0xb3, 0x20, 0x00, 0xb3, 0x4c, 0x7f, 0xb3, + 0x20, 0x28, 0xab, 0x20, 0xb6, 0xb0, 0xb0, 0xef, + 0xee, 0xe4, 0xb5, 0xd0, 0xf6, 0xee, 0xe5, 0xb5, + 0x4c, 0x1b, 0xad, 0x20, 0x28, 0xab, 0xae, 0x9c, + 0xb3, 0xbd, 0xc8, 0xb4, 0x10, 0x03, 0x4c, 0x7b, + 0xb3, 0xae, 0x9c, 0xb3, 0xbd, 0xc6, 0xb4, 0x8d, + 0xd1, 0xb5, 0x9d, 0xe6, 0xb4, 0xa9, 0xff, 0x9d, + 0xc6, 0xb4, 0xbc, 0xc7, 0xb4, 0x8c, 0xd2, 0xb5, + 0x20, 0x37, 0xb0, 0x18, 0x20, 0x5e, 0xaf, 0xb0, + 0x2a, 0x20, 0x0c, 0xaf, 0xa0, 0x0c, 0x8c, 0x9c, + 0xb3, 0xb1, 0x42, 0x30, 0x0b, 0xf0, 0x09, 0x48, + 0xc8, 0xb1, 0x42, 0xa8, 0x68, 0x20, 0x89, 0xad, + 0xac, 0x9c, 0xb3, 0xc8, 0xc8, 0xd0, 0xe7, 0xad, + 0xd3, 0xb5, 0xac, 0xd4, 0xb5, 0x20, 0x89, 0xad, + 0x38, 0xb0, 0xd1, 0x20, 0xfb, 0xaf, 0x4c, 0x7f, + 0xb3, 0x38, 0x20, 0xdd, 0xb2, 0xa9, 0x00, 0xa2, + 0x05, 0x9d, 0xf0, 0xb5, 0xca, 0x10, 0xfa, 0x60, + 0x20, 0xdc, 0xab, 0xa9, 0xff, 0x8d, 0xf9, 0xb5, + 0x20, 0xf7, 0xaf, 0xa9, 0x16, 0x8d, 0x9d, 0xb3, + 0x20, 0x2f, 0xae, 0x20, 0x2f, 0xae, 0xa2, 0x0b, + 0xbd, 0xaf, 0xb3, 0x20, 0xed, 0xfd, 0xca, 0x10, + 0xf7, 0x86, 0x45, 0xad, 0xf6, 0xb7, 0x85, 0x44, + 0x20, 0x42, 0xae, 0x20, 0x2f, 0xae, 0x20, 0x2f, + 0xae, 0x18, 0x20, 0x11, 0xb0, 0xb0, 0x5d, 0xa2, + 0x00, 0x8e, 0x9c, 0xb3, 0xbd, 0xc6, 0xb4, 0xf0, + 0x53, 0x30, 0x4a, 0xa0, 0xa0, 0xbd, 0xc8, 0xb4, + 0x10, 0x02, 0xa0, 0xaa, 0x98, 0x20, 0xed, 0xfd, + 0xbd, 0xc8, 0xb4, 0x29, 0x7f, 0xa0, 0x07, 0x0a, + 0x0a, 0xb0, 0x03, 0x88, 0xd0, 0xfa, 0xb9, 0xa7, + 0xb3, 0x20, 0xed, 0xfd, 0xa9, 0xa0, 0x20, 0xed, + + 0xfd, 0xbd, 0xe7, 0xb4, 0x85, 0x44, 0xbd, 0xe8, + 0xb4, 0x85, 0x45, 0x20, 0x42, 0xae, 0xa9, 0xa0, + 0x20, 0xed, 0xfd, 0xe8, 0xe8, 0xe8, 0xa0, 0x1d, + 0xbd, 0xc6, 0xb4, 0x20, 0xed, 0xfd, 0xe8, 0x88, + 0x10, 0xf6, 0x20, 0x2f, 0xae, 0x20, 0x30, 0xb2, + 0x90, 0xa7, 0xb0, 0x9e, 0x4c, 0x7f, 0xb3, 0xa9, + 0x8d, 0x20, 0xed, 0xfd, 0xce, 0x9d, 0xb3, 0xd0, + 0x08, 0x20, 0x0c, 0xfd, 0xa9, 0x15, 0x8d, 0x9d, + 0xb3, 0x60, 0xa0, 0x02, 0xa9, 0x00, 0x48, 0xa5, + 0x44, 0xd9, 0xa4, 0xb3, 0x90, 0x12, 0xf9, 0xa4, + 0xb3, 0x85, 0x44, 0xa5, 0x45, 0xe9, 0x00, 0x85, + 0x45, 0x68, 0x69, 0x00, 0x48, 0x4c, 0x47, 0xae, + 0x68, 0x09, 0xb0, 0x20, 0xed, 0xfd, 0x88, 0x10, + 0xdb, 0x60, 0x20, 0x08, 0xaf, 0xa0, 0x00, 0x8c, + 0xc5, 0xb5, 0xb1, 0x42, 0x99, 0xd1, 0xb5, 0xc8, + 0xc0, 0x2d, 0xd0, 0xf6, 0x18, 0x60, 0x20, 0x08, + 0xaf, 0xa0, 0x00, 0xb9, 0xd1, 0xb5, 0x91, 0x42, + 0xc8, 0xc0, 0x2d, 0xd0, 0xf6, 0x60, 0x20, 0xdc, + 0xab, 0xa9, 0x04, 0x20, 0x58, 0xb0, 0xad, 0xf9, + 0xb5, 0x49, 0xff, 0x8d, 0xc1, 0xb3, 0xa9, 0x11, + 0x8d, 0xeb, 0xb3, 0xa9, 0x01, 0x8d, 0xec, 0xb3, + 0xa2, 0x38, 0xa9, 0x00, 0x9d, 0xbb, 0xb3, 0xe8, + 0xd0, 0xfa, 0xa2, 0x0c, 0xe0, 0x8c, 0xf0, 0x14, + 0xa0, 0x03, 0xb9, 0xa0, 0xb3, 0x9d, 0xf3, 0xb3, + 0xe8, 0x88, 0x10, 0xf6, 0xe0, 0x44, 0xd0, 0xec, + 0xa2, 0x48, 0xd0, 0xe8, 0x20, 0xfb, 0xaf, 0xa2, + 0x00, 0x8a, 0x9d, 0xbb, 0xb4, 0xe8, 0xd0, 0xfa, + 0x20, 0x45, 0xb0, 0xa9, 0x11, 0xac, 0xf0, 0xb3, + 0x88, 0x88, 0x8d, 0xec, 0xb7, 0x8d, 0xbc, 0xb4, + 0x8c, 0xbd, 0xb4, 0xc8, 0x8c, 0xed, 0xb7, 0xa9, + 0x02, 0x20, 0x58, 0xb0, 0xac, 0xbd, 0xb4, 0x88, + 0x30, 0x05, 0xd0, 0xec, 0x98, 0xf0, 0xe6, 0x20, + + 0xc2, 0xb7, 0x20, 0x4a, 0xb7, 0x4c, 0x7f, 0xb3, + 0xa2, 0x00, 0xf0, 0x06, 0xa2, 0x02, 0xd0, 0x02, + 0xa2, 0x04, 0xbd, 0xc7, 0xb5, 0x85, 0x42, 0xbd, + 0xc8, 0xb5, 0x85, 0x43, 0x60, 0x2c, 0xd5, 0xb5, + 0x70, 0x01, 0x60, 0x20, 0xe4, 0xaf, 0xa9, 0x02, + 0x20, 0x52, 0xb0, 0xa9, 0xbf, 0x2d, 0xd5, 0xb5, + 0x8d, 0xd5, 0xb5, 0x60, 0xad, 0xd5, 0xb5, 0x30, + 0x01, 0x60, 0x20, 0x4b, 0xaf, 0xa9, 0x02, 0x20, + 0x52, 0xb0, 0xa9, 0x7f, 0x2d, 0xd5, 0xb5, 0x8d, + 0xd5, 0xb5, 0x60, 0xad, 0xc9, 0xb5, 0x8d, 0xf0, + 0xb7, 0xad, 0xca, 0xb5, 0x8d, 0xf1, 0xb7, 0xae, + 0xd3, 0xb5, 0xac, 0xd4, 0xb5, 0x60, 0x08, 0x20, + 0x34, 0xaf, 0x20, 0x4b, 0xaf, 0x20, 0x0c, 0xaf, + 0x28, 0xb0, 0x09, 0xae, 0xd1, 0xb5, 0xac, 0xd2, + 0xb5, 0x4c, 0xb5, 0xaf, 0xa0, 0x01, 0xb1, 0x42, + 0xf0, 0x08, 0xaa, 0xc8, 0xb1, 0x42, 0xa8, 0x4c, + 0xb5, 0xaf, 0xad, 0xbb, 0xb5, 0xc9, 0x04, 0xf0, + 0x02, 0x38, 0x60, 0x20, 0x44, 0xb2, 0xa0, 0x02, + 0x91, 0x42, 0x48, 0x88, 0xad, 0xf1, 0xb5, 0x91, + 0x42, 0x48, 0x20, 0x3a, 0xaf, 0x20, 0xd6, 0xb7, + 0xa0, 0x05, 0xad, 0xde, 0xb5, 0x91, 0x42, 0xc8, + 0xad, 0xdf, 0xb5, 0x91, 0x42, 0x68, 0xaa, 0x68, + 0xa8, 0xa9, 0x02, 0xd0, 0x02, 0xa9, 0x01, 0x8e, + 0xd3, 0xb5, 0x8c, 0xd4, 0xb5, 0x20, 0x52, 0xb0, + 0xa0, 0x05, 0xb1, 0x42, 0x8d, 0xdc, 0xb5, 0x18, + 0x6d, 0xda, 0xb5, 0x8d, 0xde, 0xb5, 0xc8, 0xb1, + 0x42, 0x8d, 0xdd, 0xb5, 0x6d, 0xdb, 0xb5, 0x8d, + 0xdf, 0xb5, 0x18, 0x60, 0x20, 0xe4, 0xaf, 0xa9, + 0x01, 0x4c, 0x52, 0xb0, 0xac, 0xcb, 0xb5, 0xad, + 0xcc, 0xb5, 0x8c, 0xf0, 0xb7, 0x8d, 0xf1, 0xb7, + 0xae, 0xd6, 0xb5, 0xac, 0xd7, 0xb5, 0x60, 0xa9, + 0x01, 0xd0, 0x02, 0xa9, 0x02, 0xac, 0xc3, 0xaa, + + 0x8c, 0xf0, 0xb7, 0xac, 0xc4, 0xaa, 0x8c, 0xf1, + 0xb7, 0xae, 0xfa, 0xb5, 0xa0, 0x00, 0x4c, 0x52, + 0xb0, 0x08, 0x20, 0x45, 0xb0, 0x28, 0xb0, 0x08, + 0xac, 0xbd, 0xb3, 0xae, 0xbc, 0xb3, 0xd0, 0x0a, + 0xae, 0xbc, 0xb4, 0xd0, 0x02, 0x38, 0x60, 0xac, + 0xbd, 0xb4, 0x8e, 0x97, 0xb3, 0x8c, 0x98, 0xb3, + 0xa9, 0x01, 0x20, 0x52, 0xb0, 0x18, 0x60, 0x20, + 0x45, 0xb0, 0xae, 0x97, 0xb3, 0xac, 0x98, 0xb3, + 0xa9, 0x02, 0x4c, 0x52, 0xb0, 0xad, 0xc5, 0xaa, + 0x8d, 0xf0, 0xb7, 0xad, 0xc6, 0xaa, 0x8d, 0xf1, + 0xb7, 0x60, 0x8e, 0xec, 0xb7, 0x8c, 0xed, 0xb7, + 0x8d, 0xf4, 0xb7, 0xc9, 0x02, 0xd0, 0x06, 0x0d, + 0xd5, 0xb5, 0x8d, 0xd5, 0xb5, 0xad, 0xf9, 0xb5, + 0x49, 0xff, 0x8d, 0xeb, 0xb7, 0xad, 0xf7, 0xb5, + 0x8d, 0xe9, 0xb7, 0xad, 0xf8, 0xb5, 0x8d, 0xea, + 0xb7, 0xad, 0xe2, 0xb5, 0x8d, 0xf2, 0xb7, 0xad, + 0xe3, 0xb5, 0x8d, 0xf3, 0xb7, 0xa9, 0x01, 0x8d, + 0xe8, 0xb7, 0xac, 0xc1, 0xaa, 0xad, 0xc2, 0xaa, + 0x20, 0xb5, 0xb7, 0xad, 0xf6, 0xb7, 0x8d, 0xbf, + 0xb5, 0xa9, 0xff, 0x8d, 0xeb, 0xb7, 0xb0, 0x01, + 0x60, 0xad, 0xf5, 0xb7, 0xa0, 0x07, 0xc9, 0x20, + 0xf0, 0x08, 0xa0, 0x04, 0xc9, 0x10, 0xf0, 0x02, + 0xa0, 0x08, 0x98, 0x4c, 0x85, 0xb3, 0xad, 0xe4, + 0xb5, 0xcd, 0xe0, 0xb5, 0xd0, 0x08, 0xad, 0xe5, + 0xb5, 0xcd, 0xe1, 0xb5, 0xf0, 0x66, 0x20, 0x1d, + 0xaf, 0xad, 0xe5, 0xb5, 0xcd, 0xdd, 0xb5, 0x90, + 0x1c, 0xd0, 0x08, 0xad, 0xe4, 0xb5, 0xcd, 0xdc, + 0xb5, 0x90, 0x12, 0xad, 0xe5, 0xb5, 0xcd, 0xdf, + 0xb5, 0x90, 0x10, 0xd0, 0x08, 0xad, 0xe4, 0xb5, + 0xcd, 0xde, 0xb5, 0x90, 0x06, 0x20, 0x5e, 0xaf, + 0x90, 0xd7, 0x60, 0x38, 0xad, 0xe4, 0xb5, 0xed, + 0xdc, 0xb5, 0x0a, 0x69, 0x0c, 0xa8, 0x20, 0x0c, + + 0xaf, 0xb1, 0x42, 0xd0, 0x0f, 0xad, 0xbb, 0xb5, + 0xc9, 0x04, 0xf0, 0x02, 0x38, 0x60, 0x20, 0x34, + 0xb1, 0x4c, 0x20, 0xb1, 0x8d, 0xd6, 0xb5, 0xc8, + 0xb1, 0x42, 0x8d, 0xd7, 0xb5, 0x20, 0xdc, 0xaf, + 0xad, 0xe4, 0xb5, 0x8d, 0xe0, 0xb5, 0xad, 0xe5, + 0xb5, 0x8d, 0xe1, 0xb5, 0x20, 0x10, 0xaf, 0xac, + 0xe6, 0xb5, 0x18, 0x60, 0x8c, 0x9d, 0xb3, 0x20, + 0x44, 0xb2, 0xac, 0x9d, 0xb3, 0xc8, 0x91, 0x42, + 0x8d, 0xd7, 0xb5, 0x88, 0xad, 0xf1, 0xb5, 0x91, + 0x42, 0x8d, 0xd6, 0xb5, 0x20, 0x10, 0xaf, 0x20, + 0xd6, 0xb7, 0xa9, 0xc0, 0x0d, 0xd5, 0xb5, 0x8d, + 0xd5, 0xb5, 0x60, 0xae, 0xea, 0xb5, 0x8e, 0xbd, + 0xb5, 0xae, 0xeb, 0xb5, 0x8e, 0xbe, 0xb5, 0xae, + 0xec, 0xb5, 0xac, 0xed, 0xb5, 0x8e, 0xbf, 0xb5, + 0x8c, 0xc0, 0xb5, 0xe8, 0xd0, 0x01, 0xc8, 0xcc, + 0xe9, 0xb5, 0xd0, 0x11, 0xec, 0xe8, 0xb5, 0xd0, + 0x0c, 0xa2, 0x00, 0xa0, 0x00, 0xee, 0xea, 0xb5, + 0xd0, 0x03, 0xee, 0xeb, 0xb5, 0x8e, 0xec, 0xb5, + 0x8c, 0xed, 0xb5, 0x60, 0xee, 0xe6, 0xb5, 0xd0, + 0x08, 0xee, 0xe4, 0xb5, 0xd0, 0x03, 0xee, 0xe5, + 0xb5, 0x60, 0xac, 0xc3, 0xb5, 0xae, 0xc4, 0xb5, + 0x84, 0x42, 0x86, 0x43, 0xee, 0xc3, 0xb5, 0xd0, + 0x03, 0xee, 0xc4, 0xb5, 0x60, 0xac, 0xc1, 0xb5, + 0xd0, 0x08, 0xae, 0xc2, 0xb5, 0xf0, 0x07, 0xce, + 0xc2, 0xb5, 0xce, 0xc1, 0xb5, 0x60, 0x4c, 0x7f, + 0xb3, 0x20, 0xf7, 0xaf, 0xad, 0xc3, 0xb5, 0x85, + 0x42, 0xad, 0xc4, 0xb5, 0x85, 0x43, 0xa9, 0x01, + 0x8d, 0x9d, 0xb3, 0xa9, 0x00, 0x8d, 0xd8, 0xb5, + 0x18, 0xee, 0xd8, 0xb5, 0x20, 0x11, 0xb0, 0xb0, + 0x51, 0xa2, 0x00, 0x8e, 0x9c, 0xb3, 0xbd, 0xc6, + 0xb4, 0xf0, 0x1f, 0x30, 0x22, 0xa0, 0x00, 0xe8, + 0xe8, 0xe8, 0xb1, 0x42, 0xdd, 0xc6, 0xb4, 0xd0, + + 0x0a, 0xc8, 0xc0, 0x1e, 0xd0, 0xf3, 0xae, 0x9c, + 0xb3, 0x18, 0x60, 0x20, 0x30, 0xb2, 0x90, 0xdb, + 0xb0, 0xcf, 0xac, 0x9d, 0xb3, 0xd0, 0xc1, 0xac, + 0x9d, 0xb3, 0xd0, 0xef, 0xa0, 0x00, 0xe8, 0xe8, + 0xe8, 0xb1, 0x42, 0x9d, 0xc6, 0xb4, 0xc8, 0xc0, + 0x1e, 0xd0, 0xf5, 0xae, 0x9c, 0xb3, 0x38, 0x60, + 0x18, 0xad, 0x9c, 0xb3, 0x69, 0x23, 0xaa, 0xe0, + 0xf5, 0x60, 0xa9, 0x00, 0xac, 0x9d, 0xb3, 0xd0, + 0x97, 0x4c, 0x77, 0xb3, 0xad, 0xf1, 0xb5, 0xf0, + 0x21, 0xce, 0xf0, 0xb5, 0x30, 0x17, 0x18, 0xa2, + 0x04, 0x3e, 0xf1, 0xb5, 0xca, 0xd0, 0xfa, 0x90, + 0xf0, 0xee, 0xee, 0xb5, 0xd0, 0x03, 0xee, 0xef, + 0xb5, 0xad, 0xf0, 0xb5, 0x60, 0xa9, 0x00, 0x8d, + 0xf1, 0xb5, 0xa9, 0x00, 0x8d, 0x9e, 0xb3, 0x20, + 0xf7, 0xaf, 0x18, 0xad, 0xeb, 0xb3, 0x6d, 0xec, + 0xb3, 0xf0, 0x09, 0xcd, 0xef, 0xb3, 0x90, 0x14, + 0xa9, 0xff, 0xd0, 0x0a, 0xad, 0x9e, 0xb3, 0xd0, + 0x37, 0xa9, 0x01, 0x8d, 0x9e, 0xb3, 0x8d, 0xec, + 0xb3, 0x18, 0x69, 0x11, 0x8d, 0xeb, 0xb3, 0x8d, + 0xf1, 0xb5, 0xa8, 0x0a, 0x0a, 0xa8, 0xa2, 0x04, + 0x18, 0xb9, 0xf6, 0xb3, 0x9d, 0xf1, 0xb5, 0xf0, + 0x06, 0x38, 0xa9, 0x00, 0x99, 0xf6, 0xb3, 0x88, + 0xca, 0xd0, 0xee, 0x90, 0xbd, 0x20, 0xfb, 0xaf, + 0xad, 0xf0, 0xb3, 0x8d, 0xf0, 0xb5, 0xd0, 0x89, + 0x4c, 0x77, 0xb3, 0xad, 0xf1, 0xb5, 0xd0, 0x01, + 0x60, 0x48, 0x20, 0xf7, 0xaf, 0xac, 0xf0, 0xb5, + 0x68, 0x18, 0x20, 0xdd, 0xb2, 0xa9, 0x00, 0x8d, + 0xf1, 0xb5, 0x4c, 0xfb, 0xaf, 0xa2, 0xfc, 0x7e, + 0xf6, 0xb4, 0xe8, 0xd0, 0xfa, 0xc8, 0xcc, 0xf0, + 0xb3, 0xd0, 0xf2, 0x0a, 0x0a, 0xa8, 0xf0, 0x0f, + 0xa2, 0x04, 0xbd, 0xf1, 0xb5, 0x19, 0xf6, 0xb3, + 0x99, 0xf6, 0xb3, 0x88, 0xca, 0xd0, 0xf3, 0x60, + + 0xad, 0xbd, 0xb5, 0x8d, 0xe6, 0xb5, 0x8d, 0xea, + 0xb5, 0xad, 0xbe, 0xb5, 0x8d, 0xe4, 0xb5, 0x8d, + 0xeb, 0xb5, 0xa9, 0x00, 0x8d, 0xe5, 0xb5, 0xa0, + 0x10, 0xaa, 0xad, 0xe6, 0xb5, 0x4a, 0xb0, 0x03, + 0x8a, 0x90, 0x0e, 0x18, 0xad, 0xe5, 0xb5, 0x6d, + 0xe8, 0xb5, 0x8d, 0xe5, 0xb5, 0x8a, 0x6d, 0xe9, + 0xb5, 0x6a, 0x6e, 0xe5, 0xb5, 0x6e, 0xe4, 0xb5, + 0x6e, 0xe6, 0xb5, 0x88, 0xd0, 0xdb, 0xad, 0xbf, + 0xb5, 0x8d, 0xec, 0xb5, 0x6d, 0xe6, 0xb5, 0x8d, + 0xe6, 0xb5, 0xad, 0xc0, 0xb5, 0x8d, 0xed, 0xb5, + 0x6d, 0xe4, 0xb5, 0x8d, 0xe4, 0xb5, 0xa9, 0x00, + 0x6d, 0xe5, 0xb5, 0x8d, 0xe5, 0xb5, 0x60, 0xa9, + 0x01, 0xd0, 0x22, 0xa9, 0x02, 0xd0, 0x1e, 0xa9, + 0x03, 0xd0, 0x1a, 0xa9, 0x04, 0xd0, 0x16, 0xa9, + 0x05, 0xd0, 0x12, 0xa9, 0x06, 0xd0, 0x0e, 0x4c, + 0xed, 0xbf, 0xea, 0xa9, 0x0a, 0xd0, 0x06, 0xad, + 0xc5, 0xb5, 0x18, 0x90, 0x01, 0x38, 0x08, 0x8d, + 0xc5, 0xb5, 0xa9, 0x00, 0x85, 0x48, 0x20, 0x7e, + 0xae, 0x28, 0xae, 0x9b, 0xb3, 0x9a, 0x60, 0x11, + 0x0d, 0x00, 0x00, 0xee, 0x69, 0x01, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x01, 0x0a, 0x64, 0xd4, + 0xc9, 0xc1, 0xc2, 0xd3, 0xd2, 0xc1, 0xc2, 0xa0, + 0xc5, 0xcd, 0xd5, 0xcc, 0xcf, 0xd6, 0xa0, 0xcb, + 0xd3, 0xc9, 0xc4, 0x04, 0x11, 0x0f, 0x03, 0x00, + 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x23, + 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x9d, 0x01, 0x00, 0xfe, + 0x02, 0x06, 0x00, 0x75, 0xaa, 0x00, 0x00, 0x00, + 0x98, 0x00, 0x97, 0x00, 0x96, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, + 0x02, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +/* + * Three 13-sector tracks, in DOS order (i.e. track 0 sector 0 followed + * by track 0 sector 1). + * + * Obtained from the DOS 3.2.1 system master. The last two sectors of + * track 2 were unreadable, and have been zeroed out here. + */ +/*static*/ const uint8_t DiskFSDOS33::gDOS32Tracks[13 * 3 * 256] = { + 0xf0, 0x4a, 0x99, 0xff, 0xff, 0x03, 0x3c, 0xad, + 0xff, 0xff, 0xff, 0x26, 0xb3, 0xff, 0xff, 0x4d, + 0x4a, 0x10, 0xff, 0xff, 0x3d, 0x4a, 0xca, 0xff, + 0xff, 0xa5, 0x4a, 0xc8, 0xff, 0xff, 0x03, 0x4a, + 0x40, 0xff, 0xff, 0x46, 0x08, 0x91, 0xff, 0xff, + 0x20, 0x33, 0x09, 0xff, 0xff, 0x03, 0xbd, 0xcc, + 0xff, 0xff, 0x43, 0xc8, 0x1d, 0xff, 0xff, 0x20, + 0x40, 0x07, 0xff, 0xff, 0x3e, 0x91, 0x29, 0xff, + 0xff, 0x85, 0x09, 0x3c, 0xff, 0xff, 0x5d, 0x00, + 0xa5, 0xff, 0xff, 0xa9, 0x1d, 0xc8, 0xff, 0xff, + 0x3f, 0x4a, 0x40, 0xff, 0xff, 0x85, 0x2a, 0x91, + 0xff, 0xff, 0xc0, 0x85, 0x09, 0xff, 0xff, 0x09, + 0x4a, 0x99, 0xff, 0xff, 0x4a, 0x3c, 0x1d, 0xff, + 0xff, 0x4a, 0x85, 0x07, 0xff, 0xff, 0x4a, 0x4a, + 0x29, 0xff, 0xff, 0x4a, 0x4a, 0x2a, 0xff, 0xff, + 0x8a, 0x4a, 0xa5, 0xff, 0xff, 0x40, 0x08, 0xc8, + 0xff, 0xff, 0x84, 0x00, 0x40, 0xff, 0xff, 0x41, + 0xbd, 0x91, 0xff, 0xff, 0x85, 0x00, 0x09, 0xff, + 0xff, 0x03, 0xa0, 0x66, 0xff, 0xff, 0xcc, 0x32, + 0x1d, 0xff, 0xff, 0xad, 0xa2, 0x2a, 0xff, 0xff, + 0x27, 0x00, 0x26, 0xff, 0xff, 0x85, 0x3e, 0x4a, + 0xff, 0xff, 0x09, 0x6c, 0x3c, 0xff, 0xff, 0xa9, + 0x3f, 0x26, 0xff, 0xff, 0x2b, 0xe6, 0x4a, 0xff, + 0xff, 0xa6, 0x3f, 0x4a, 0xff, 0xff, 0xf4, 0x85, + 0x4a, 0xff, 0xff, 0xd0, 0x03, 0x4a, 0x60, 0xff, + 0xc8, 0xcc, 0x08, 0x2b, 0xff, 0x08, 0xad, 0x66, + 0xa6, 0xff, 0x00, 0x3e, 0xbd, 0x40, 0xff, 0x99, + 0x85, 0xc8, 0x91, 0xff, 0x0a, 0xed, 0x40, 0x09, + 0xff, 0x0a, 0xd0, 0x91, 0xff, 0xff, 0x0a, 0x3d, + 0x09, 0x0d, 0xff, 0x08, 0xe6, 0x33, 0x4a, 0xff, + 0x00, 0x41, 0x1d, 0x4a, 0xff, 0xb9, 0xe6, 0x2a, + 0x4a, 0xff, 0x99, 0x06, 0x26, 0x08, 0x36, 0x48, + + 0x8e, 0xe9, 0x37, 0x8e, 0xf7, 0x37, 0xa9, 0x01, + 0x8d, 0xf8, 0x37, 0x8d, 0xea, 0x37, 0xad, 0xe0, + 0x37, 0x8d, 0xe1, 0x37, 0xa9, 0x00, 0x8d, 0xec, + 0x37, 0xad, 0xe2, 0x37, 0x8d, 0xed, 0x37, 0xad, + 0xe3, 0x37, 0x8d, 0xf1, 0x37, 0xa9, 0x01, 0x8d, + 0xf4, 0x37, 0x8a, 0x4a, 0x4a, 0x4a, 0x4a, 0xaa, + 0xa9, 0x00, 0x9d, 0xf8, 0x04, 0x9d, 0x78, 0x04, + 0x20, 0x93, 0x37, 0xa2, 0xff, 0x9a, 0x8e, 0xeb, + 0x37, 0x20, 0x93, 0xfe, 0x20, 0x89, 0xfe, 0x4c, + 0x03, 0x1b, 0xad, 0xf1, 0x37, 0x8d, 0xe3, 0x37, + 0x38, 0xad, 0xe7, 0x37, 0xed, 0xe3, 0x37, 0x8d, + 0xe0, 0x37, 0xa9, 0x00, 0x8d, 0xec, 0x37, 0x8d, + 0xed, 0x37, 0x8d, 0xf0, 0x37, 0xad, 0xe7, 0x37, + 0x8d, 0xf1, 0x37, 0x8d, 0xfe, 0x36, 0xa9, 0x0a, + 0x8d, 0xe1, 0x37, 0x8d, 0xe2, 0x37, 0xa9, 0x48, + 0x8d, 0xff, 0x36, 0xa9, 0x02, 0x8d, 0xf4, 0x37, + 0x20, 0x93, 0x37, 0xad, 0xe3, 0x37, 0x8d, 0xf1, + 0x37, 0xad, 0xe0, 0x37, 0x8d, 0xe1, 0x37, 0x20, + 0x93, 0x37, 0x60, 0xad, 0xe5, 0x37, 0xac, 0xe4, + 0x37, 0x20, 0xb5, 0x37, 0xac, 0xed, 0x37, 0xc8, + 0xc0, 0x0d, 0xd0, 0x05, 0xa0, 0x00, 0xee, 0xec, + 0x37, 0x8c, 0xed, 0x37, 0xee, 0xf1, 0x37, 0xce, + 0xe1, 0x37, 0xd0, 0xdf, 0x60, 0x08, 0x78, 0x20, + 0x00, 0x3d, 0xb0, 0x03, 0x28, 0x18, 0x60, 0x28, + 0x38, 0x60, 0xad, 0xbc, 0x35, 0x8d, 0xf1, 0x37, + 0xa9, 0x00, 0x8d, 0xf0, 0x37, 0xad, 0xf9, 0x35, + 0x49, 0xff, 0x8d, 0xeb, 0x37, 0x60, 0xa9, 0x00, + 0xa8, 0x91, 0x42, 0xc8, 0xd0, 0xfb, 0x60, 0x00, + 0x1b, 0x09, 0x0a, 0x1b, 0xe8, 0x37, 0x00, 0x36, + 0x01, 0x60, 0x01, 0x00, 0x00, 0x01, 0xfb, 0x37, + 0x00, 0x37, 0x00, 0x00, 0x02, 0x07, 0xfe, 0x60, + 0x01, 0x00, 0x00, 0x00, 0x01, 0xef, 0xd8, 0x00, + + 0xa2, 0x32, 0xa0, 0x00, 0xb1, 0x3e, 0x85, 0x26, + 0x4a, 0x4a, 0x4a, 0x9d, 0x00, 0x3b, 0xc8, 0xb1, + 0x3e, 0x85, 0x27, 0x4a, 0x4a, 0x4a, 0x9d, 0x33, + 0x3b, 0xc8, 0xb1, 0x3e, 0x85, 0x2a, 0x4a, 0x4a, + 0x4a, 0x9d, 0x66, 0x3b, 0xc8, 0xb1, 0x3e, 0x4a, + 0x26, 0x2a, 0x4a, 0x26, 0x27, 0x4a, 0x26, 0x26, + 0x9d, 0x99, 0x3b, 0xc8, 0xb1, 0x3e, 0x4a, 0x26, + 0x2a, 0x4a, 0x26, 0x27, 0x4a, 0x9d, 0xcc, 0x3b, + 0xa5, 0x26, 0x2a, 0x29, 0x1f, 0x9d, 0x00, 0x3c, + 0xa5, 0x27, 0x29, 0x1f, 0x9d, 0x33, 0x3c, 0xa5, + 0x2a, 0x29, 0x1f, 0x9d, 0x66, 0x3c, 0xc8, 0xca, + 0x10, 0xaa, 0xb1, 0x3e, 0xaa, 0x29, 0x07, 0x8d, + 0x99, 0x3c, 0x8a, 0x4a, 0x4a, 0x4a, 0x8d, 0xff, + 0x3b, 0x60, 0x38, 0xbd, 0x8d, 0xc0, 0xbd, 0x8e, + 0xc0, 0x30, 0x7c, 0x86, 0x27, 0x8e, 0x78, 0x06, + 0xad, 0x00, 0x3c, 0x85, 0x26, 0xa9, 0xff, 0x9d, + 0x8f, 0xc0, 0x1d, 0x8c, 0xc0, 0x48, 0x68, 0xea, + 0xa0, 0x0a, 0x05, 0x26, 0x20, 0xf4, 0x38, 0x88, + 0xd0, 0xf8, 0xa9, 0xd5, 0x20, 0xf3, 0x38, 0xa9, + 0xaa, 0x20, 0xf3, 0x38, 0xa9, 0xad, 0x20, 0xf3, + 0x38, 0x98, 0xa0, 0x9a, 0xd0, 0x03, 0xb9, 0x00, + 0x3c, 0x59, 0xff, 0x3b, 0xaa, 0xbd, 0x9a, 0x3c, + 0xa6, 0x27, 0x9d, 0x8d, 0xc0, 0xbd, 0x8c, 0xc0, + 0x88, 0xd0, 0xeb, 0xa5, 0x26, 0xea, 0x59, 0x00, + 0x3b, 0xaa, 0xbd, 0x9a, 0x3c, 0xae, 0x78, 0x06, + 0x9d, 0x8d, 0xc0, 0xbd, 0x8c, 0xc0, 0xb9, 0x00, + 0x3b, 0xc8, 0xd0, 0xea, 0xaa, 0xbd, 0x9a, 0x3c, + 0xa6, 0x27, 0x20, 0xf6, 0x38, 0xa9, 0xde, 0x20, + 0xf3, 0x38, 0xa9, 0xaa, 0x20, 0xf3, 0x38, 0xa9, + 0xeb, 0x20, 0xf3, 0x38, 0xbd, 0x8e, 0xc0, 0xbd, + 0x8c, 0xc0, 0x60, 0x18, 0x48, 0x68, 0x9d, 0x8d, + 0xc0, 0x1d, 0x8c, 0xc0, 0x60, 0xa0, 0x20, 0x88, + + 0xf0, 0x61, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x49, + 0xd5, 0xd0, 0xf4, 0xea, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0xc9, 0xaa, 0xd0, 0xf2, 0xa0, 0x9a, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xad, 0xd0, 0xe7, + 0xa9, 0x00, 0x88, 0x84, 0x26, 0xbc, 0x8c, 0xc0, + 0x10, 0xfb, 0x59, 0x00, 0x3a, 0xa4, 0x26, 0x99, + 0x00, 0x3c, 0xd0, 0xee, 0x84, 0x26, 0xbc, 0x8c, + 0xc0, 0x10, 0xfb, 0x59, 0x00, 0x3a, 0xa4, 0x26, + 0x99, 0x00, 0x3b, 0xc8, 0xd0, 0xee, 0xbc, 0x8c, + 0xc0, 0x10, 0xfb, 0xd9, 0x00, 0x3a, 0xd0, 0x13, + 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xde, 0xd0, + 0x0a, 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, + 0xaa, 0xf0, 0x5c, 0x38, 0x60, 0xa0, 0xf8, 0x84, + 0x26, 0xc8, 0xd0, 0x04, 0xe6, 0x26, 0xf0, 0xf3, + 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xd5, 0xd0, + 0xf0, 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, + 0xaa, 0xd0, 0xf2, 0xa0, 0x03, 0xbd, 0x8c, 0xc0, + 0x10, 0xfb, 0xc9, 0xb5, 0xd0, 0xe7, 0xa9, 0x00, + 0x85, 0x27, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x2a, + 0x85, 0x26, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x25, + 0x26, 0x99, 0x2c, 0x00, 0x45, 0x27, 0x88, 0x10, + 0xe7, 0xa8, 0xd0, 0xb7, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0xc9, 0xde, 0xd0, 0xae, 0xea, 0xbd, 0x8c, + 0xc0, 0x10, 0xfb, 0xc9, 0xaa, 0xd0, 0xa4, 0x18, + 0x60, 0xa2, 0x32, 0xa0, 0x00, 0xbd, 0x00, 0x3c, + 0x4a, 0x4a, 0x4a, 0x85, 0x27, 0x4a, 0x85, 0x26, + 0x4a, 0x1d, 0x00, 0x3b, 0x91, 0x3e, 0xc8, 0xbd, + 0x33, 0x3c, 0x4a, 0x4a, 0x4a, 0x4a, 0x26, 0x27, + 0x4a, 0x26, 0x26, 0x1d, 0x33, 0x3b, 0x91, 0x3e, + 0xc8, 0xbd, 0x66, 0x3c, 0x4a, 0x4a, 0x4a, 0x4a, + 0x26, 0x27, 0x4a, 0x26, 0x26, 0x1d, 0x66, 0x3b, + 0x91, 0x3e, 0xc8, 0xa5, 0x26, 0x29, 0x07, 0x1d, + + 0x99, 0x3b, 0x91, 0x3e, 0xc8, 0xa5, 0x27, 0x29, + 0x07, 0x1d, 0xcc, 0x3b, 0x91, 0x3e, 0xc8, 0xca, + 0x10, 0xb3, 0xad, 0x99, 0x3c, 0x4a, 0x4a, 0x4a, + 0x0d, 0xff, 0x3b, 0x91, 0x3e, 0x60, 0x86, 0x2b, + 0x85, 0x2a, 0xcd, 0x78, 0x04, 0xf0, 0x53, 0xa9, + 0x00, 0x85, 0x26, 0xad, 0x78, 0x04, 0x85, 0x27, + 0x38, 0xe5, 0x2a, 0xf0, 0x33, 0xb0, 0x07, 0x49, + 0xff, 0xee, 0x78, 0x04, 0x90, 0x05, 0x69, 0xfe, + 0xce, 0x78, 0x04, 0xc5, 0x26, 0x90, 0x02, 0xa5, + 0x26, 0xc9, 0x0c, 0xb0, 0x01, 0xa8, 0x38, 0x20, + 0x6c, 0x3a, 0xb9, 0x8c, 0x3a, 0x20, 0x7b, 0x3a, + 0xa5, 0x27, 0x18, 0x20, 0x6f, 0x3a, 0xb9, 0x98, + 0x3a, 0x20, 0x7b, 0x3a, 0xe6, 0x26, 0xd0, 0xc3, + 0x20, 0x7b, 0x3a, 0x18, 0xad, 0x78, 0x04, 0x29, + 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, 0x80, 0xc0, + 0xa6, 0x2b, 0x60, 0xa2, 0x11, 0xca, 0xd0, 0xfd, + 0xe6, 0x46, 0xd0, 0x02, 0xe6, 0x47, 0x38, 0xe9, + 0x01, 0xd0, 0xf0, 0x60, 0x01, 0x30, 0x28, 0x24, + 0x20, 0x1e, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x70, 0x2c, 0x26, 0x22, 0x1f, 0x1e, 0x1d, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x10, 0x18, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x20, 0x28, 0x30, + 0x07, 0x09, 0x38, 0x40, 0x0a, 0x48, 0x50, 0x58, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x19, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1e, 0x21, 0x22, 0x23, 0x24, 0x60, 0x68, + 0x25, 0x26, 0x70, 0x78, 0x27, 0x80, 0x88, 0x90, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x31, + 0x32, 0x33, 0x98, 0xa0, 0x34, 0xa8, 0xb0, 0xb8, + 0x35, 0x36, 0x37, 0x39, 0x3a, 0xc0, 0xc8, 0xd0, + 0x3b, 0x3c, 0xd8, 0xe0, 0x3e, 0xe8, 0xf0, 0xf8, + + 0x1b, 0x18, 0x06, 0x14, 0x05, 0x05, 0x04, 0x0d, + 0x04, 0x03, 0x02, 0x01, 0x0a, 0x01, 0x00, 0x03, + 0x00, 0x03, 0x03, 0x03, 0x03, 0x06, 0x00, 0x00, + 0x1a, 0x05, 0x1f, 0x1c, 0x13, 0x15, 0x05, 0x04, + 0x10, 0x01, 0x15, 0x12, 0x00, 0x0f, 0x12, 0x09, + 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x1c, 0x19, 0x06, 0x06, 0x06, + 0x05, 0x10, 0x04, 0x04, 0x03, 0x02, 0x01, 0x0b, + 0x07, 0x04, 0x00, 0x00, 0x03, 0x03, 0x0e, 0x03, + 0x05, 0x1a, 0x1c, 0x1f, 0x0c, 0x04, 0x04, 0x07, + 0x17, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x1a, + 0x06, 0x15, 0x06, 0x05, 0x11, 0x04, 0x04, 0x03, + 0x02, 0x02, 0x01, 0x08, 0x05, 0x00, 0x00, 0x03, + 0x03, 0x05, 0x03, 0x04, 0x1e, 0x08, 0x1c, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x01, + 0x00, 0x1d, 0x07, 0x07, 0x16, 0x06, 0x05, 0x12, + 0x0e, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x06, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x1e, 0x07, 0x07, 0x17, + 0x13, 0x05, 0x05, 0x0f, 0x0c, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x1f, + + 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x14, 0x1d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x18, 0x1e, 0x07, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x10, 0x15, 0x01, 0x00, + 0x00, 0x00, 0x1c, 0x1f, 0x19, 0x10, 0x0d, 0x1b, + 0x0f, 0x00, 0x14, 0x1e, 0x05, 0x01, 0x1c, 0x01, + 0x04, 0x03, 0x11, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x1d, 0x1e, 0x03, 0x0a, 0x04, + 0x01, 0x02, 0x00, 0x02, 0x04, 0x13, 0x19, 0x05, + 0x08, 0x08, 0x04, 0x00, 0x10, 0x15, 0x15, 0x0e, + 0x18, 0x0f, 0x0c, 0x0c, 0x04, 0x1c, 0x01, 0x14, + 0x01, 0x0c, 0x00, 0x00, 0x11, 0x02, 0x05, 0x00, + 0x1c, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x1f, + 0x00, 0x00, 0x00, 0x0d, 0x0a, 0x00, 0x02, 0x19, + 0x0c, 0x1a, 0x03, 0x0c, 0x0c, 0x00, 0x00, 0x10, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x02, 0x05, 0x12, + 0x10, 0x16, 0x00, 0x09, 0x09, 0x09, 0x0c, 0x05, + 0x11, 0x03, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, + 0x10, 0x07, 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, + 0xb7, 0xba, 0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, + 0xda, 0xdb, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, + 0xee, 0xef, 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, + 0xfe, 0xff, 0x1c, 0x1c, 0x1c, 0x00, 0x00, 0x00, + 0xa4, 0x2d, 0xb9, 0xd0, 0x3c, 0xa0, 0x05, 0x4c, + 0x0a, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x0a, 0x02, 0x07, 0x0c, 0x04, 0x09, + 0x01, 0x06, 0x0b, 0x03, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x84, 0x48, 0x85, 0x49, 0xa0, 0x02, 0x8c, 0xf8, + 0x06, 0xa0, 0x04, 0x8c, 0xf8, 0x04, 0xa0, 0x01, + 0xb1, 0x48, 0xaa, 0xa0, 0x0f, 0xd1, 0x48, 0xf0, + 0x1b, 0x8a, 0x48, 0xb1, 0x48, 0xaa, 0x68, 0x48, + 0x91, 0x48, 0xbd, 0x8e, 0xc0, 0xa0, 0x08, 0xbd, + 0x8c, 0xc0, 0xdd, 0x8c, 0xc0, 0xd0, 0xf6, 0x88, + 0xd0, 0xf8, 0x68, 0xaa, 0xbd, 0x8e, 0xc0, 0xbd, + 0x8c, 0xc0, 0xbd, 0x8c, 0xc0, 0x48, 0x68, 0x8e, + 0xf8, 0x05, 0xdd, 0x8c, 0xc0, 0x08, 0xbd, 0x89, + 0xc0, 0xa0, 0x06, 0xb1, 0x48, 0x99, 0x36, 0x00, + 0xc8, 0xc0, 0x0a, 0xd0, 0xf6, 0xa0, 0x03, 0xb1, + 0x3c, 0x85, 0x47, 0xa0, 0x02, 0xb1, 0x48, 0xa0, + 0x10, 0xd1, 0x48, 0xf0, 0x06, 0x91, 0x48, 0x28, + 0xa0, 0x00, 0x08, 0x6a, 0x90, 0x05, 0xbd, 0x8a, + 0xc0, 0xb0, 0x03, 0xbd, 0x8b, 0xc0, 0x66, 0x35, + 0x28, 0x08, 0xd0, 0x0b, 0xa0, 0x07, 0x20, 0x7f, + 0x3a, 0x88, 0xd0, 0xfa, 0xae, 0xf8, 0x05, 0xa0, + 0x04, 0xb1, 0x48, 0x20, 0x4b, 0x3e, 0x28, 0xd0, + 0x0d, 0xa0, 0x12, 0x88, 0xd0, 0xfd, 0xe6, 0x46, + 0xd0, 0xf7, 0xe6, 0x47, 0xd0, 0xf3, 0xa0, 0x0c, + 0xb1, 0x48, 0xf0, 0x5a, 0xc9, 0x04, 0xf0, 0x58, + 0x6a, 0x08, 0xb0, 0x03, 0x20, 0x00, 0x38, 0xa0, + 0x30, 0x8c, 0x78, 0x05, 0xae, 0xf8, 0x05, 0x20, + 0x65, 0x39, 0x90, 0x24, 0xce, 0x78, 0x05, 0x10, + 0xf3, 0xad, 0x78, 0x04, 0x48, 0xa9, 0x60, 0x20, + 0x86, 0x3e, 0xce, 0xf8, 0x06, 0xf0, 0x28, 0xa9, + 0x04, 0x8d, 0xf8, 0x04, 0xa9, 0x00, 0x20, 0x4b, + 0x3e, 0x68, 0x20, 0x4b, 0x3e, 0x4c, 0xaf, 0x3d, + 0xa4, 0x2e, 0xcc, 0x78, 0x04, 0xf0, 0x22, 0xad, + 0x78, 0x04, 0x48, 0x98, 0x20, 0x86, 0x3e, 0x68, + 0xce, 0xf8, 0x04, 0xd0, 0xe5, 0xf0, 0xca, 0x68, + 0xa9, 0x40, 0x28, 0x4c, 0x39, 0x3e, 0xf0, 0x37, + + 0xa0, 0x03, 0xb1, 0x48, 0x85, 0x2f, 0x4c, 0xa0, + 0x3e, 0xa0, 0x03, 0xb1, 0x48, 0x48, 0xa5, 0x2f, + 0xa0, 0x0e, 0x91, 0x48, 0x68, 0xf0, 0x08, 0xc5, + 0x2f, 0xf0, 0x04, 0xa9, 0x20, 0xd0, 0xdb, 0xa0, + 0x05, 0xa5, 0x2d, 0xd1, 0x48, 0xd0, 0x95, 0x28, + 0x90, 0x18, 0x20, 0xfd, 0x38, 0x08, 0xb0, 0x8c, + 0x28, 0x20, 0xc1, 0x39, 0xae, 0xf8, 0x05, 0x18, + 0x24, 0x38, 0xa0, 0x0d, 0x91, 0x48, 0xbd, 0x88, + 0xc0, 0x60, 0x20, 0x6a, 0x38, 0x90, 0xf0, 0xa9, + 0x10, 0xb0, 0xee, 0x48, 0xa0, 0x01, 0xb1, 0x3c, + 0x6a, 0x68, 0x90, 0x08, 0x0a, 0x20, 0x5c, 0x3e, + 0x4e, 0x78, 0x04, 0x60, 0x85, 0x2e, 0x20, 0x7f, + 0x3e, 0xb9, 0x78, 0x04, 0x24, 0x35, 0x30, 0x03, + 0xb9, 0xf8, 0x04, 0x8d, 0x78, 0x04, 0xa5, 0x2e, + 0x24, 0x35, 0x30, 0x05, 0x99, 0xf8, 0x04, 0x10, + 0x03, 0x99, 0x78, 0x04, 0x4c, 0x1e, 0x3a, 0x8a, + 0x4a, 0x4a, 0x4a, 0x4a, 0xa8, 0x60, 0x48, 0xa0, + 0x02, 0xb1, 0x48, 0x6a, 0x66, 0x35, 0x20, 0x7f, + 0x3e, 0x68, 0x0a, 0x24, 0x35, 0x30, 0x05, 0x99, + 0xf8, 0x04, 0x10, 0x03, 0x99, 0x78, 0x04, 0x60, + 0xa9, 0x80, 0x8d, 0x78, 0x04, 0xa9, 0x00, 0x85, + 0x41, 0x20, 0x1e, 0x3a, 0xa9, 0xaa, 0x85, 0x4a, + 0xa0, 0x50, 0x84, 0x47, 0xa9, 0x27, 0x85, 0x4b, + 0xbd, 0x8d, 0xc0, 0xbd, 0x8e, 0xc0, 0xa9, 0xff, + 0x9d, 0x8f, 0xc0, 0xdd, 0x8c, 0xc0, 0x24, 0x00, + 0x88, 0xf0, 0x0f, 0x48, 0x68, 0xea, 0x48, 0x68, + 0xea, 0xea, 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, + 0xb0, 0xee, 0xc6, 0x4b, 0xd0, 0xf0, 0xa4, 0x47, + 0xea, 0xea, 0xd0, 0x06, 0x48, 0x68, 0x48, 0x68, + 0xc1, 0x00, 0xea, 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, + 0xc0, 0x88, 0xd0, 0xf0, 0xa9, 0xd5, 0x20, 0xcc, + 0x3f, 0xa9, 0xaa, 0x20, 0xcd, 0x3f, 0xa9, 0xb5, + + 0x20, 0xcd, 0x3f, 0xa5, 0x2f, 0x20, 0xbd, 0x3f, + 0xa5, 0x41, 0x20, 0xbd, 0x3f, 0xa5, 0x4b, 0x20, + 0xbd, 0x3f, 0xa5, 0x2f, 0x45, 0x41, 0x45, 0x4b, + 0x48, 0x4a, 0x05, 0x4a, 0x9d, 0x8d, 0xc0, 0xdd, + 0x8c, 0xc0, 0x68, 0x09, 0xaa, 0x20, 0xcc, 0x3f, + 0xa9, 0xde, 0x20, 0xcd, 0x3f, 0xa9, 0xaa, 0x20, + 0xcd, 0x3f, 0xa9, 0xeb, 0x20, 0xcd, 0x3f, 0xa9, + 0xff, 0x20, 0xcd, 0x3f, 0xa0, 0x02, 0x84, 0x46, + 0xa0, 0xad, 0xd0, 0x06, 0x88, 0xf0, 0x0d, 0x48, + 0x68, 0xea, 0x48, 0x68, 0x9d, 0x8d, 0xc0, 0xdd, + 0x8c, 0xc0, 0xb0, 0xf0, 0xc6, 0x46, 0xd0, 0xf2, + 0xa4, 0x47, 0x18, 0x24, 0x00, 0x9d, 0x8d, 0xc0, + 0xbd, 0x8c, 0xc0, 0xa5, 0x4b, 0x69, 0x0a, 0x85, + 0x4b, 0xe9, 0x0c, 0xf0, 0x0a, 0xb0, 0x01, 0x2c, + 0x85, 0x4b, 0xa9, 0xff, 0x4c, 0xeb, 0x3e, 0x48, + 0x68, 0xa4, 0x47, 0xbd, 0x8d, 0xc0, 0xbd, 0x8e, + 0xc0, 0x30, 0x32, 0x88, 0x48, 0x68, 0x48, 0x68, + 0x48, 0x68, 0x88, 0xd0, 0xf7, 0x20, 0x65, 0x39, + 0xb0, 0x04, 0xa5, 0x2d, 0xf0, 0x0a, 0xa4, 0x47, + 0x88, 0xc0, 0x10, 0x90, 0x18, 0x4c, 0xb2, 0x3e, + 0xe6, 0x41, 0xa5, 0x41, 0xc9, 0x23, 0xb0, 0x12, + 0x0a, 0x20, 0x1e, 0x3a, 0xa4, 0x47, 0xc8, 0xc8, + 0x84, 0x47, 0x4c, 0xb2, 0x3e, 0xa9, 0x40, 0x4c, + 0x39, 0x3e, 0x4c, 0x37, 0x3e, 0x48, 0x4a, 0x05, + 0x4a, 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, 0x68, + 0xc1, 0x00, 0x09, 0xaa, 0xea, 0x48, 0x68, 0xea, + 0x9d, 0x8d, 0xc0, 0xdd, 0x8c, 0xc0, 0x60, 0x01, + 0x60, 0x4c, 0xdd, 0x25, 0x8d, 0x63, 0x2a, 0x8d, + 0x70, 0x2a, 0x8d, 0x71, 0x2a, 0x60, 0x20, 0x5b, + 0x27, 0x8c, 0xb7, 0x2a, 0x60, 0x20, 0x7e, 0x2e, + 0xae, 0x9b, 0x33, 0x9a, 0x20, 0x16, 0x23, 0xba, + 0x8e, 0x9b, 0x33, 0xa9, 0x09, 0x4c, 0x85, 0x33, + + 0x4c, 0x84, 0x1d, 0xa9, 0xbf, 0x85, 0x41, 0xa2, + 0x00, 0x86, 0x40, 0xa0, 0x00, 0xa1, 0x40, 0x85, + 0x26, 0x98, 0x45, 0x26, 0x85, 0x26, 0x98, 0x41, + 0x40, 0x81, 0x40, 0xc5, 0x26, 0xd0, 0x05, 0xc8, + 0xd0, 0xef, 0xf0, 0x04, 0xc6, 0x41, 0xd0, 0xe3, + 0xa5, 0x41, 0x29, 0xdf, 0x85, 0x43, 0x86, 0x42, + 0xa1, 0x42, 0x48, 0x85, 0x26, 0x98, 0x45, 0x26, + 0x85, 0x26, 0x98, 0x41, 0x40, 0x81, 0x42, 0xc5, + 0x26, 0xd0, 0x09, 0xc8, 0xd0, 0xef, 0xa4, 0x43, + 0x68, 0x4c, 0x51, 0x1b, 0x68, 0x81, 0x42, 0xa4, + 0x41, 0xc8, 0x8c, 0x79, 0x1c, 0x38, 0x98, 0xed, + 0x7a, 0x1c, 0x8d, 0x78, 0x1c, 0x38, 0xed, 0x76, + 0x1c, 0xf0, 0x9d, 0x8d, 0x7b, 0x1c, 0xad, 0x76, + 0x1c, 0x8d, 0x0d, 0x1d, 0xa9, 0x1d, 0x8d, 0x49, + 0x37, 0xa9, 0x84, 0x8d, 0x48, 0x37, 0xa2, 0x00, + 0x86, 0x40, 0xbd, 0x29, 0x1c, 0xa8, 0xbd, 0x2a, + 0x1c, 0x85, 0x41, 0x4c, 0x93, 0x1b, 0x18, 0xb1, + 0x40, 0x6d, 0x7b, 0x1c, 0x91, 0x40, 0xc8, 0xd0, + 0x02, 0xe6, 0x41, 0xc8, 0xd0, 0x02, 0xe6, 0x41, + 0xa5, 0x41, 0xdd, 0x2c, 0x1c, 0x90, 0xe7, 0x98, + 0xdd, 0x2b, 0x1c, 0x90, 0xe1, 0x8a, 0x18, 0x69, + 0x04, 0xaa, 0xec, 0x28, 0x1c, 0x90, 0xcb, 0xa2, + 0x00, 0x8e, 0x9c, 0x33, 0xbd, 0x5a, 0x1c, 0x85, + 0x40, 0xbd, 0x5b, 0x1c, 0x85, 0x41, 0xa2, 0x00, + 0xa1, 0x40, 0x20, 0x8e, 0xf8, 0xa4, 0x2f, 0xc0, + 0x02, 0xd0, 0x11, 0xb1, 0x40, 0xcd, 0x76, 0x1c, + 0x90, 0x0a, 0xcd, 0x77, 0x1c, 0xb0, 0x05, 0x6d, + 0x7b, 0x1c, 0x91, 0x40, 0x38, 0xa5, 0x2f, 0x65, + 0x40, 0x85, 0x40, 0xa9, 0x00, 0x65, 0x41, 0x85, + 0x41, 0xae, 0x9c, 0x33, 0xdd, 0x5d, 0x1c, 0x90, + 0xcd, 0xa5, 0x40, 0xdd, 0x5c, 0x1c, 0x90, 0xc6, + 0x8a, 0x18, 0x69, 0x04, 0xaa, 0xec, 0x59, 0x1c, + + 0x90, 0xaf, 0xa9, 0x3f, 0x85, 0x41, 0xac, 0x79, + 0x1c, 0x88, 0x84, 0x43, 0xa9, 0x00, 0x85, 0x40, + 0x85, 0x42, 0xa8, 0xb1, 0x40, 0x91, 0x42, 0xc8, + 0xd0, 0xf9, 0xce, 0x7c, 0x1c, 0xf0, 0x06, 0xc6, + 0x41, 0xc6, 0x43, 0xd0, 0xee, 0x4c, 0x54, 0x1e, + 0x24, 0x00, 0x1d, 0x56, 0x1d, 0x58, 0x1d, 0x5a, + 0x1d, 0x64, 0x1d, 0x66, 0x1d, 0x6c, 0x1d, 0x70, + 0x1d, 0x78, 0x1d, 0x7c, 0x1d, 0x7e, 0x1d, 0x80, + 0x1d, 0xc1, 0x2a, 0xfd, 0x2a, 0xe4, 0x37, 0xe8, + 0x37, 0xee, 0x37, 0xf0, 0x37, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x84, 0x1d, 0x84, 0x28, 0xfd, 0x2a, + 0x97, 0x33, 0x00, 0x37, 0xe0, 0x37, 0xfe, 0x35, + 0xfe, 0x35, 0x00, 0x38, 0x8f, 0x3a, 0x00, 0x3d, + 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x40, + 0x00, 0x00, 0x23, 0x00, 0x23, 0xa5, 0x74, 0x86, + 0x6f, 0x85, 0x70, 0xa0, 0x00, 0x84, 0x8b, 0xa5, + 0x6d, 0xa6, 0x6e, 0x85, 0x9b, 0x86, 0x9c, 0xa9, + 0x55, 0xa2, 0x00, 0x85, 0x5e, 0x86, 0x5f, 0xc5, + 0x52, 0xf0, 0x05, 0x20, 0x1a, 0x1d, 0xf0, 0xf7, + 0xa9, 0x07, 0x85, 0x8f, 0xa5, 0x69, 0xa6, 0x6a, + 0x85, 0x5e, 0x86, 0x5f, 0xe4, 0x6c, 0xd0, 0x04, + 0xc5, 0x6b, 0xf0, 0x05, 0x20, 0x10, 0x1d, 0xf0, + 0xf3, 0x85, 0x94, 0x86, 0x95, 0xa9, 0x03, 0x85, + 0x8f, 0xa5, 0x94, 0xa6, 0x95, 0xe4, 0x6e, 0xd0, + 0x07, 0xc5, 0x6d, 0xd0, 0x03, 0x4c, 0x59, 0x1d, + 0x85, 0x5e, 0x86, 0x00, 0xa0, 0x00, 0xb1, 0x5e, + 0xaa, 0xc8, 0xb1, 0x5e, 0x08, 0xc8, 0xb1, 0x5e, + 0x65, 0x94, 0x85, 0x94, 0xc8, 0xb1, 0x5e, 0x65, + 0x95, 0x85, 0x95, 0x28, 0x10, 0xd3, 0x8a, 0x30, + 0xd0, 0xa6, 0x1c, 0xa6, 0x1b, 0xa6, 0x1a, 0x80, + 0x1a, 0x65, 0x5e, 0x85, 0x5e, 0x90, 0x02, 0xe6, + + 0xd3, 0x1c, 0x81, 0x1e, 0xbd, 0x1e, 0x75, 0x2a, + 0x93, 0x2a, 0x60, 0x2a, 0x00, 0x1b, 0xbb, 0x35, + 0xea, 0x1e, 0x11, 0x1f, 0x22, 0x1f, 0x2e, 0x1f, + 0x51, 0x1f, 0x60, 0x1f, 0x70, 0x1f, 0x4e, 0x25, + 0x12, 0x24, 0x96, 0x23, 0xd0, 0x24, 0xef, 0x24, + 0x62, 0x22, 0x70, 0x22, 0x74, 0x22, 0xe9, 0x22, + 0x1a, 0x25, 0xc5, 0x25, 0x0f, 0x25, 0xdc, 0x25, + 0xa2, 0x22, 0x97, 0x22, 0x80, 0x22, 0x6d, 0x25, + 0x32, 0x22, 0x3c, 0x22, 0x28, 0x22, 0x2d, 0x22, + 0x50, 0x22, 0x79, 0x25, 0x9d, 0x25, 0x30, 0x23, + 0x5c, 0x23, 0x8d, 0x23, 0x7c, 0x22, 0x36, 0xe8, + 0xe5, 0x24, 0xe3, 0xe3, 0x00, 0xe0, 0x03, 0xe0, + 0x00, 0x00, 0x36, 0xe8, 0xe5, 0x24, 0xe3, 0xe3, + 0x00, 0xe0, 0x03, 0xe0, 0xfc, 0x24, 0xfc, 0x24, + 0x65, 0xd8, 0x00, 0xe0, 0x3c, 0xd4, 0xf2, 0xd4, + 0x06, 0x25, 0x06, 0x25, 0x67, 0x10, 0x84, 0x1d, + 0x3c, 0x0c, 0xf2, 0x0c, 0xad, 0xe9, 0x37, 0x4a, + 0x4a, 0x4a, 0x4a, 0x8d, 0x6a, 0x2a, 0xad, 0xea, + 0x37, 0x8d, 0x68, 0x2a, 0xad, 0x00, 0xe0, 0x49, + 0x20, 0xd0, 0x11, 0x8d, 0xb6, 0x2a, 0xa2, 0x0a, + 0xbd, 0x61, 0x1d, 0x9d, 0x55, 0x1d, 0xca, 0xd0, + 0xf7, 0x4c, 0xbc, 0x1d, 0xa9, 0x40, 0x8d, 0xb6, + 0x2a, 0xa2, 0x0c, 0xbd, 0x6b, 0x1d, 0x9d, 0x55, + 0x1d, 0xca, 0xd0, 0xf7, 0x38, 0xb0, 0x12, 0xad, + 0xb6, 0x2a, 0xd0, 0x04, 0xa9, 0x20, 0xd0, 0x05, + 0x0a, 0x10, 0x05, 0xa9, 0x4c, 0x20, 0xb2, 0x25, + 0x18, 0x08, 0x20, 0x51, 0x28, 0xa9, 0x00, 0x8d, + 0x5e, 0x2a, 0x8d, 0x52, 0x2a, 0x28, 0x6a, 0x8d, + 0x51, 0x2a, 0x30, 0x03, 0x6c, 0x5e, 0x1d, 0x6c, + 0x5c, 0x1d, 0x0a, 0x10, 0x19, 0x8d, 0xb6, 0x2a, + 0xa2, 0x0c, 0xbd, 0x77, 0x1d, 0x9d, 0x55, 0x1d, + 0xca, 0xd0, 0xf7, 0xa2, 0x1d, 0xbd, 0x93, 0x2a, + + 0x9d, 0x75, 0x2a, 0xca, 0x10, 0xf7, 0xad, 0xb1, + 0x2a, 0x8d, 0x57, 0x2a, 0x20, 0xd4, 0x27, 0xad, + 0xb3, 0x2a, 0xf0, 0x09, 0x48, 0x20, 0x9d, 0x26, + 0x68, 0xa0, 0x00, 0x91, 0x40, 0x20, 0x5b, 0x27, + 0xad, 0x5f, 0x2a, 0xd0, 0x20, 0xa2, 0x2f, 0xbd, + 0x51, 0x1e, 0x9d, 0xd0, 0x03, 0xca, 0x10, 0xf7, + 0xad, 0x53, 0x1e, 0x8d, 0xf3, 0x03, 0x49, 0xa5, + 0x8d, 0xf4, 0x03, 0xad, 0x52, 0x1e, 0x8d, 0xf2, + 0x03, 0xa9, 0x06, 0xd0, 0x05, 0xad, 0x62, 0x2a, + 0xf0, 0x06, 0x8d, 0x5f, 0x2a, 0x4c, 0x80, 0x21, + 0x60, 0x4c, 0xbf, 0x1d, 0x4c, 0x84, 0x1d, 0x4c, + 0xfd, 0x2a, 0x4c, 0xb5, 0x37, 0xad, 0x0f, 0x1d, + 0xac, 0x0e, 0x1d, 0x60, 0xad, 0xc2, 0x2a, 0xac, + 0xc1, 0x2a, 0x60, 0x4c, 0x51, 0x28, 0xea, 0xea, + 0x4c, 0x59, 0xfa, 0x4c, 0x65, 0xff, 0x4c, 0x58, + 0xff, 0x4c, 0x65, 0xff, 0x4c, 0x65, 0xff, 0x65, + 0xff, 0x20, 0xd1, 0x1e, 0xad, 0x51, 0x2a, 0xf0, + 0x15, 0x48, 0xad, 0x5c, 0x2a, 0x91, 0x28, 0x68, + 0x30, 0x03, 0x4c, 0x26, 0x26, 0x20, 0xea, 0x1d, + 0xa4, 0x24, 0xa9, 0x60, 0x91, 0x28, 0xad, 0xb3, + 0x2a, 0xf0, 0x03, 0x20, 0x82, 0x26, 0xa9, 0x03, + 0x8d, 0x52, 0x2a, 0x20, 0xba, 0x1f, 0x20, 0xba, + 0x1e, 0x8d, 0x5c, 0x2a, 0x8e, 0x5a, 0x2a, 0x4c, + 0xb3, 0x1f, 0x6c, 0x38, 0x00, 0x20, 0xd1, 0x1e, + 0xad, 0x52, 0x2a, 0x0a, 0xaa, 0xbd, 0x11, 0x1d, + 0x48, 0xbd, 0x10, 0x1d, 0x48, 0xad, 0x5c, 0x2a, + 0x60, 0x8d, 0x5c, 0x2a, 0x8e, 0x5a, 0x2a, 0x8c, + 0x5b, 0x2a, 0xba, 0xe8, 0xe8, 0x8e, 0x59, 0x2a, + 0xa2, 0x03, 0xbd, 0x53, 0x2a, 0x95, 0x36, 0xca, + 0x10, 0xf8, 0x60, 0xae, 0xb7, 0x2a, 0xf0, 0x03, + 0x4c, 0x78, 0x1f, 0xae, 0x51, 0x2a, 0xf0, 0x08, + 0xc9, 0xbf, 0xf0, 0x75, 0xc5, 0x33, 0xf0, 0x27, + + 0xa2, 0x02, 0x8e, 0x52, 0x2a, 0xcd, 0xb2, 0x2a, + 0xd0, 0x19, 0xca, 0x8e, 0x52, 0x2a, 0xca, 0x8e, + 0x5d, 0x2a, 0xae, 0x5d, 0x2a, 0x9d, 0x00, 0x02, + 0xe8, 0x8e, 0x5d, 0x2a, 0xc9, 0x8d, 0xd0, 0x75, + 0x4c, 0xcd, 0x1f, 0xc9, 0x8d, 0xd0, 0x7d, 0xa2, + 0x00, 0x8e, 0x52, 0x2a, 0x4c, 0xa4, 0x1f, 0xa2, + 0x00, 0x8e, 0x52, 0x2a, 0xc9, 0x8d, 0xf0, 0x07, + 0xad, 0xb3, 0x2a, 0xf0, 0x67, 0xd0, 0x5e, 0x48, + 0x38, 0xad, 0xb3, 0x2a, 0xd0, 0x03, 0x20, 0x5e, + 0x26, 0x68, 0x90, 0xec, 0xae, 0x5a, 0x2a, 0x4c, + 0x15, 0x1f, 0xc9, 0x8d, 0xd0, 0x05, 0xa9, 0x05, + 0x8d, 0x52, 0x2a, 0x20, 0x0e, 0x26, 0x4c, 0x99, + 0x1f, 0xcd, 0xb2, 0x2a, 0xf0, 0x85, 0xc9, 0x8a, + 0xf0, 0xf1, 0xa2, 0x04, 0x8e, 0x52, 0x2a, 0xd0, + 0xe1, 0xa9, 0x00, 0x8d, 0x52, 0x2a, 0xf0, 0x25, + 0xa9, 0x00, 0x8d, 0xb7, 0x2a, 0x20, 0x51, 0x28, + 0x4c, 0xdc, 0x24, 0xad, 0x00, 0x02, 0xcd, 0xb2, + 0x2a, 0xf0, 0x0a, 0xa9, 0x8d, 0x8d, 0x00, 0x02, + 0xa2, 0x00, 0x8e, 0x5a, 0x2a, 0xa9, 0x40, 0xd0, + 0x06, 0xa9, 0x10, 0xd0, 0x02, 0xa9, 0x20, 0x2d, + 0x5e, 0x2a, 0xf0, 0x0f, 0x20, 0xba, 0x1f, 0x20, + 0xc5, 0x1f, 0x8d, 0x5c, 0x2a, 0x8c, 0x5b, 0x2a, + 0x8e, 0x5a, 0x2a, 0x20, 0x51, 0x28, 0xae, 0x59, + 0x2a, 0x9a, 0xad, 0x5c, 0x2a, 0xac, 0x5b, 0x2a, + 0xae, 0x5a, 0x2a, 0x38, 0x60, 0x6c, 0x36, 0x00, + 0xa9, 0x8d, 0x4c, 0xc5, 0x1f, 0xa0, 0xff, 0x8c, + 0x5f, 0x2a, 0xc8, 0x8c, 0x62, 0x2a, 0xee, 0x5f, + 0x2a, 0xa2, 0x00, 0x08, 0xbd, 0x00, 0x02, 0xcd, + 0xb2, 0x2a, 0xd0, 0x01, 0xe8, 0x8e, 0x5d, 0x2a, + 0x20, 0xa4, 0x21, 0x29, 0x7f, 0x59, 0x84, 0x28, + 0xc8, 0x0a, 0xf0, 0x02, 0x68, 0x08, 0x90, 0xf0, + 0x28, 0xf0, 0x20, 0xb9, 0x84, 0x28, 0xd0, 0xd6, + + 0xad, 0x00, 0x02, 0xcd, 0xb2, 0x2a, 0xf0, 0x03, + 0x4c, 0xa4, 0x1f, 0xad, 0x01, 0x02, 0xc9, 0x8d, + 0xd0, 0x06, 0x20, 0x5b, 0x27, 0x4c, 0x95, 0x1f, + 0x4c, 0xc4, 0x26, 0x0e, 0x5f, 0x2a, 0xac, 0x5f, + 0x2a, 0x20, 0x5e, 0x26, 0x90, 0x0c, 0xa9, 0x02, + 0x39, 0x09, 0x29, 0xf0, 0x05, 0xa9, 0x0f, 0x4c, + 0xd2, 0x26, 0xc0, 0x06, 0xd0, 0x02, 0x84, 0x33, + 0xa9, 0x20, 0x39, 0x09, 0x29, 0xf0, 0x61, 0x20, + 0x95, 0x20, 0x08, 0x20, 0xa4, 0x21, 0xf0, 0x1e, + 0x0a, 0x90, 0x05, 0x30, 0x03, 0x4c, 0x00, 0x20, + 0x6a, 0x4c, 0x59, 0x20, 0x20, 0x93, 0x21, 0xf0, + 0x0d, 0x99, 0x75, 0x2a, 0xc8, 0xc0, 0x3c, 0x90, + 0xf3, 0x20, 0x93, 0x21, 0xd0, 0xfb, 0x28, 0xd0, + 0x0f, 0xac, 0x5f, 0x2a, 0xa9, 0x10, 0x39, 0x09, + 0x29, 0xf0, 0x0c, 0xa0, 0x1e, 0x08, 0xd0, 0xcb, + 0xad, 0x93, 0x2a, 0xc9, 0xa0, 0xf0, 0x13, 0xad, + 0x75, 0x2a, 0xc9, 0xa0, 0xd0, 0x4b, 0xac, 0x5f, + 0x2a, 0xa9, 0xc0, 0x39, 0x09, 0x29, 0xf0, 0x02, + 0x10, 0x3f, 0x4c, 0x00, 0x20, 0xa0, 0x3c, 0xa9, + 0xa0, 0x99, 0x74, 0x2a, 0x88, 0xd0, 0xfa, 0x60, + 0x8d, 0x75, 0x2a, 0xa9, 0x0c, 0x39, 0x09, 0x29, + 0xf0, 0x27, 0x20, 0xb9, 0x21, 0xb0, 0x1f, 0xa8, + 0xd0, 0x17, 0xe0, 0x11, 0xb0, 0x13, 0xac, 0x5f, + 0x2a, 0xa9, 0x08, 0x39, 0x09, 0x29, 0xf0, 0x06, + 0xe0, 0x08, 0xb0, 0xce, 0x90, 0x0b, 0x8a, 0xd0, + 0x08, 0xa9, 0x02, 0x4c, 0xd2, 0x26, 0x4c, 0xc4, + 0x26, 0xa9, 0x00, 0x8d, 0x65, 0x2a, 0x8d, 0x74, + 0x2a, 0x8d, 0x66, 0x2a, 0x8d, 0x6c, 0x2a, 0x8d, + 0x6d, 0x2a, 0x20, 0xdc, 0x3f, 0xad, 0x5d, 0x2a, + 0x20, 0xa4, 0x21, 0xd0, 0x1f, 0xc9, 0x8d, 0xd0, + 0xf7, 0xae, 0x5f, 0x2a, 0xad, 0x65, 0x2a, 0x1d, + 0x0a, 0x29, 0x5d, 0x0a, 0x29, 0xd0, 0x93, 0xae, + + 0x63, 0x2a, 0xf0, 0x76, 0x8d, 0x63, 0x2a, 0x8e, + 0x5d, 0x2a, 0xd0, 0xdc, 0xa2, 0x0a, 0xdd, 0x40, + 0x29, 0xf0, 0x05, 0xca, 0xd0, 0xf8, 0xf0, 0xb6, + 0xbd, 0x4a, 0x29, 0x30, 0x47, 0x0d, 0x65, 0x2a, + 0x8d, 0x65, 0x2a, 0xca, 0x8e, 0x64, 0x2a, 0x20, + 0xb9, 0x21, 0xb0, 0xa2, 0xad, 0x64, 0x2a, 0x0a, + 0x0a, 0xa8, 0xa5, 0x45, 0xd0, 0x09, 0xa5, 0x44, + 0xd9, 0x55, 0x29, 0x90, 0x8c, 0xa5, 0x45, 0xd9, + 0x58, 0x29, 0x90, 0x0b, 0xd0, 0x83, 0xa5, 0x44, + 0xd9, 0x57, 0x29, 0x90, 0x02, 0xd0, 0xf5, 0xad, + 0x63, 0x2a, 0xd0, 0x94, 0x98, 0x4a, 0xa8, 0xa5, + 0x45, 0x99, 0x67, 0x2a, 0xa5, 0x44, 0x99, 0x66, + 0x2a, 0x4c, 0xe8, 0x20, 0x48, 0xa9, 0x80, 0x0d, + 0x65, 0x2a, 0x8d, 0x65, 0x2a, 0x68, 0x29, 0x7f, + 0x0d, 0x74, 0x2a, 0x8d, 0x74, 0x2a, 0xd0, 0xe9, + 0xf0, 0x9c, 0x20, 0x80, 0x21, 0x4c, 0x83, 0x1f, + 0x20, 0x5b, 0x27, 0x20, 0xae, 0x21, 0xad, 0x5f, + 0x2a, 0xaa, 0xbd, 0x1f, 0x1d, 0x48, 0xbd, 0x1e, + 0x1d, 0x48, 0x60, 0xae, 0x5d, 0x2a, 0xbd, 0x00, + 0x02, 0xc9, 0x8d, 0xf0, 0x06, 0xe8, 0x8e, 0x5d, + 0x2a, 0xc9, 0xac, 0x60, 0x20, 0x93, 0x21, 0xf0, + 0xfa, 0xc9, 0xa0, 0xf0, 0xf7, 0x60, 0xa9, 0x00, + 0xa0, 0x16, 0x99, 0xba, 0x35, 0x88, 0xd0, 0xfa, + 0x60, 0xa9, 0x00, 0x85, 0x44, 0x85, 0x45, 0x20, + 0xa4, 0x21, 0x08, 0xc9, 0xa4, 0xf0, 0x3c, 0x28, + 0x4c, 0xce, 0x21, 0x20, 0xa4, 0x21, 0xd0, 0x06, + 0xa6, 0x44, 0xa5, 0x45, 0x18, 0x60, 0x38, 0xe9, + 0xb0, 0x30, 0x21, 0xc9, 0x0a, 0xb0, 0x1d, 0x20, + 0xfe, 0x21, 0x65, 0x44, 0xaa, 0xa9, 0x00, 0x65, + 0x45, 0xa8, 0x20, 0xfe, 0x21, 0x20, 0xfe, 0x21, + 0x8a, 0x65, 0x44, 0x85, 0x44, 0x98, 0x65, 0x45, + 0x85, 0x45, 0x90, 0xcf, 0x38, 0x60, 0x06, 0x44, + + 0x26, 0x45, 0x60, 0x28, 0x20, 0xa4, 0x21, 0xf0, + 0xc5, 0x38, 0xe9, 0xb0, 0x30, 0xee, 0xc9, 0x0a, + 0x90, 0x08, 0xe9, 0x07, 0x30, 0xe6, 0xc9, 0x10, + 0xb0, 0xe2, 0xa2, 0x04, 0x20, 0xfe, 0x21, 0xca, + 0xd0, 0xfa, 0x05, 0x44, 0x85, 0x44, 0x4c, 0x04, + 0x22, 0xa5, 0x44, 0x4c, 0x95, 0xfe, 0xa5, 0x44, + 0x4c, 0x8b, 0xfe, 0xad, 0x5e, 0x2a, 0x0d, 0x74, + 0x2a, 0x8d, 0x5e, 0x2a, 0x60, 0x2c, 0x74, 0x2a, + 0x50, 0x03, 0x20, 0xc8, 0x1f, 0xa9, 0x70, 0x4d, + 0x74, 0x2a, 0x2d, 0x5e, 0x2a, 0x8d, 0x5e, 0x2a, + 0x60, 0xa9, 0x00, 0x8d, 0xb3, 0x2a, 0xa5, 0x44, + 0x48, 0x20, 0x16, 0x23, 0x68, 0x8d, 0x57, 0x2a, + 0x4c, 0xd4, 0x27, 0xa9, 0x05, 0x20, 0xaa, 0x22, + 0x20, 0x64, 0x27, 0xa0, 0x00, 0x98, 0x91, 0x40, + 0x60, 0xa9, 0x07, 0xd0, 0x02, 0xa9, 0x08, 0x20, + 0xaa, 0x22, 0x4c, 0xea, 0x22, 0xa9, 0x0c, 0xd0, + 0xf6, 0xad, 0x08, 0x1d, 0x8d, 0xbd, 0x35, 0xad, + 0x09, 0x1d, 0x8d, 0xbe, 0x35, 0xa9, 0x09, 0x8d, + 0x63, 0x2a, 0x20, 0xc8, 0x22, 0x4c, 0xea, 0x22, + 0x20, 0xa3, 0x22, 0x20, 0x8c, 0x26, 0xd0, 0xfb, + 0x4c, 0x46, 0x25, 0xa9, 0x00, 0x4c, 0xd5, 0x23, + 0xa9, 0x01, 0x8d, 0x63, 0x2a, 0xad, 0x6c, 0x2a, + 0xd0, 0x0a, 0xad, 0x6d, 0x2a, 0xd0, 0x05, 0xa9, + 0x01, 0x8d, 0x6c, 0x2a, 0xad, 0x6c, 0x2a, 0x8d, + 0xbd, 0x35, 0xad, 0x6d, 0x2a, 0x8d, 0xbe, 0x35, + 0x20, 0xea, 0x22, 0xa5, 0x45, 0xd0, 0x03, 0x4c, + 0xc8, 0x26, 0x85, 0x41, 0xa5, 0x44, 0x85, 0x40, + 0x20, 0x43, 0x27, 0x20, 0x4e, 0x27, 0x20, 0x1a, + 0x27, 0xad, 0x63, 0x2a, 0x8d, 0xbb, 0x35, 0x4c, + 0xa8, 0x26, 0xad, 0x75, 0x2a, 0xc9, 0xa0, 0xf0, + 0x25, 0x20, 0x64, 0x27, 0xb0, 0x3a, 0x20, 0xfc, + 0x22, 0x4c, 0xea, 0x22, 0x20, 0xaf, 0x27, 0xd0, + + 0x05, 0xa9, 0x00, 0x8d, 0xb3, 0x2a, 0xa0, 0x00, + 0x98, 0x91, 0x40, 0x20, 0x4e, 0x27, 0xa9, 0x02, + 0x8d, 0xbb, 0x35, 0x4c, 0xa8, 0x26, 0x20, 0x92, + 0x27, 0xd0, 0x05, 0x20, 0x9a, 0x27, 0xf0, 0x10, + 0x20, 0xaf, 0x27, 0xf0, 0xf6, 0x20, 0xaa, 0x27, + 0xf0, 0xf1, 0x20, 0xfc, 0x22, 0x4c, 0x16, 0x23, + 0x60, 0xa9, 0x09, 0x2d, 0x65, 0x2a, 0xc9, 0x09, + 0xf0, 0x03, 0x4c, 0x00, 0x20, 0xa9, 0x04, 0x20, + 0xd5, 0x23, 0xad, 0x73, 0x2a, 0xac, 0x72, 0x2a, + 0x20, 0xe0, 0x23, 0xad, 0x6d, 0x2a, 0xac, 0x6c, + 0x2a, 0x20, 0xe0, 0x23, 0xad, 0x73, 0x2a, 0xac, + 0x72, 0x2a, 0x4c, 0xff, 0x23, 0x20, 0xa8, 0x22, + 0xa9, 0x7f, 0x2d, 0xc2, 0x35, 0xc9, 0x04, 0xf0, + 0x03, 0x4c, 0xd0, 0x26, 0xa9, 0x04, 0x20, 0xd5, + 0x23, 0x20, 0x7a, 0x24, 0xaa, 0xad, 0x65, 0x2a, + 0x29, 0x01, 0xd0, 0x06, 0x8e, 0x72, 0x2a, 0x8c, + 0x73, 0x2a, 0x20, 0x7a, 0x24, 0xae, 0x72, 0x2a, + 0xac, 0x73, 0x2a, 0x4c, 0x71, 0x24, 0x20, 0x5d, + 0x23, 0x20, 0x51, 0x28, 0x6c, 0x72, 0x2a, 0xad, + 0xb6, 0x2a, 0xf0, 0x20, 0xa5, 0xd6, 0x10, 0x03, + 0x4c, 0xcc, 0x26, 0xa9, 0x02, 0x20, 0xd5, 0x23, + 0x38, 0xa5, 0xaf, 0xe5, 0x67, 0xa8, 0xa5, 0xb0, + 0xe5, 0x68, 0x20, 0xe0, 0x23, 0xa5, 0x68, 0xa4, + 0x67, 0x4c, 0xff, 0x23, 0xa9, 0x01, 0x20, 0xd5, + 0x23, 0x38, 0xa5, 0x4c, 0xe5, 0xca, 0xa8, 0xa5, + 0x4d, 0xe5, 0xcb, 0x20, 0xe0, 0x23, 0xa5, 0xcb, + 0xa4, 0xca, 0x4c, 0xff, 0x23, 0x8d, 0xc2, 0x35, + 0x48, 0x20, 0xa8, 0x22, 0x68, 0x4c, 0xc4, 0x27, + 0x8c, 0xc1, 0x35, 0x8c, 0xc3, 0x35, 0x8d, 0xc2, + 0x35, 0xa9, 0x04, 0x8d, 0xbb, 0x35, 0xa9, 0x01, + 0x8d, 0xbc, 0x35, 0x20, 0xa8, 0x26, 0xad, 0xc2, + 0x35, 0x8d, 0xc3, 0x35, 0x4c, 0xa8, 0x26, 0x8c, + + 0xc3, 0x35, 0x8d, 0xc4, 0x35, 0xa9, 0x02, 0x8d, + 0xbc, 0x35, 0x20, 0xa8, 0x26, 0x4c, 0xea, 0x22, + 0x4c, 0xd0, 0x26, 0x20, 0x16, 0x23, 0x20, 0xa8, + 0x22, 0xa9, 0x23, 0x2d, 0xc2, 0x35, 0xf0, 0xf0, + 0x8d, 0xc2, 0x35, 0xad, 0xb6, 0x2a, 0xf0, 0x28, + 0xa9, 0x02, 0x20, 0xb1, 0x24, 0x20, 0x7a, 0x24, + 0x18, 0x65, 0x67, 0xaa, 0x98, 0x65, 0x68, 0xc5, + 0x74, 0xb0, 0x70, 0x85, 0xb0, 0x85, 0x6a, 0x86, + 0xaf, 0x86, 0x69, 0xa6, 0x67, 0xa4, 0x68, 0x20, + 0x71, 0x24, 0x20, 0x51, 0x28, 0x6c, 0x60, 0x1d, + 0xa9, 0x01, 0x20, 0xb1, 0x24, 0x20, 0x7a, 0x24, + 0x38, 0xa5, 0x4c, 0xed, 0x60, 0x2a, 0xaa, 0xa5, + 0x4d, 0xed, 0x61, 0x2a, 0x90, 0x45, 0xa8, 0xc4, + 0x4b, 0x90, 0x40, 0xf0, 0x3e, 0x84, 0xcb, 0x86, + 0xca, 0x8e, 0xc3, 0x35, 0x8c, 0xc4, 0x35, 0x4c, + 0x0a, 0x24, 0xad, 0x0a, 0x1d, 0x8d, 0xc3, 0x35, + 0xad, 0x0b, 0x1d, 0x8d, 0xc4, 0x35, 0xa9, 0x00, + 0x8d, 0xc2, 0x35, 0xa9, 0x02, 0x8d, 0xc1, 0x35, + 0xa9, 0x03, 0x8d, 0xbb, 0x35, 0xa9, 0x02, 0x8d, + 0xbc, 0x35, 0x20, 0xa8, 0x26, 0xad, 0x61, 0x2a, + 0x8d, 0xc2, 0x35, 0xa8, 0xad, 0x60, 0x2a, 0x8d, + 0xc1, 0x35, 0x60, 0x20, 0xea, 0x22, 0x4c, 0xcc, + 0x26, 0xcd, 0xc2, 0x35, 0xf0, 0x1a, 0xae, 0x5f, + 0x2a, 0x8e, 0x62, 0x2a, 0x4a, 0xf0, 0x03, 0x4c, + 0x9e, 0x25, 0xa2, 0x1d, 0xbd, 0x75, 0x2a, 0x9d, + 0x93, 0x2a, 0xca, 0x10, 0xf7, 0x4c, 0x7a, 0x25, + 0x60, 0xad, 0xb6, 0x2a, 0xf0, 0x03, 0x8d, 0xb7, + 0x2a, 0x20, 0x13, 0x24, 0x20, 0xc8, 0x1f, 0x20, + 0x51, 0x28, 0x6c, 0x58, 0x1d, 0xa5, 0x4a, 0x85, + 0xcc, 0xa5, 0x4b, 0x85, 0xcd, 0x6c, 0x56, 0x1d, + 0x20, 0x16, 0x24, 0x20, 0xc8, 0x1f, 0x20, 0x51, + 0x28, 0x6c, 0x56, 0x1d, 0x20, 0x65, 0xd6, 0x85, + + 0x33, 0x85, 0xd8, 0x4c, 0xd2, 0xd7, 0x20, 0x65, + 0x0e, 0x85, 0x33, 0x85, 0xd8, 0x4c, 0xd4, 0x0f, + 0x20, 0x26, 0x25, 0xa9, 0x05, 0x8d, 0x52, 0x2a, + 0x4c, 0x83, 0x1f, 0x20, 0x26, 0x25, 0xa9, 0x01, + 0x8d, 0x51, 0x2a, 0x4c, 0x83, 0x1f, 0x20, 0x64, + 0x27, 0x90, 0x06, 0x20, 0xa3, 0x22, 0x4c, 0x34, + 0x25, 0x20, 0x4e, 0x27, 0xad, 0x65, 0x2a, 0x29, + 0x06, 0xf0, 0x13, 0xa2, 0x03, 0xbd, 0x6e, 0x2a, + 0x9d, 0xbd, 0x35, 0xca, 0x10, 0xf7, 0xa9, 0x0a, + 0x8d, 0xbb, 0x35, 0x20, 0xa8, 0x26, 0x60, 0xa9, + 0x40, 0x2d, 0x65, 0x2a, 0xf0, 0x05, 0xad, 0x66, + 0x2a, 0xd0, 0x05, 0xa9, 0xfe, 0x8d, 0x66, 0x2a, + 0xad, 0x0d, 0x1d, 0x8d, 0xbc, 0x35, 0xa9, 0x0b, + 0x20, 0xaa, 0x22, 0x4c, 0x97, 0x23, 0xa9, 0x06, + 0x20, 0xaa, 0x22, 0xad, 0xbf, 0x35, 0x8d, 0x66, + 0x2a, 0x60, 0xa9, 0x4c, 0x20, 0xb2, 0x25, 0xf0, + 0x2e, 0xa9, 0x00, 0x8d, 0xb6, 0x2a, 0xa0, 0x1e, + 0x20, 0x97, 0x20, 0xa2, 0x09, 0xbd, 0xb7, 0x2a, + 0x9d, 0x74, 0x2a, 0xca, 0xd0, 0xf7, 0xa9, 0xc0, + 0x8d, 0x51, 0x2a, 0x4c, 0xd1, 0x24, 0xa9, 0x20, + 0x20, 0xb2, 0x25, 0xf0, 0x05, 0xa9, 0x01, 0x4c, + 0xd2, 0x26, 0xa9, 0x00, 0x8d, 0xb7, 0x2a, 0x4c, + 0x84, 0x1d, 0xcd, 0x00, 0xe0, 0xf0, 0x0e, 0x8d, + 0x80, 0xc0, 0xcd, 0x00, 0xe0, 0xf0, 0x06, 0x8d, + 0x81, 0xc0, 0xcd, 0x00, 0xe0, 0x60, 0x20, 0xa3, + 0x22, 0xad, 0x4f, 0x2a, 0x8d, 0xb4, 0x2a, 0xad, + 0x50, 0x2a, 0x8d, 0xb5, 0x2a, 0xad, 0x75, 0x2a, + 0x8d, 0xb3, 0x2a, 0xd0, 0x0e, 0x20, 0x64, 0x27, + 0x90, 0x06, 0x20, 0xa3, 0x22, 0x4c, 0xeb, 0x25, + 0x20, 0x4e, 0x27, 0xad, 0x65, 0x2a, 0x29, 0x04, + 0xf0, 0x1b, 0xad, 0x6e, 0x2a, 0xd0, 0x08, 0xae, + 0x6f, 0x2a, 0xf0, 0x11, 0xce, 0x6f, 0x2a, 0xce, + + 0x6e, 0x2a, 0x20, 0x8c, 0x26, 0xf0, 0x38, 0xc9, + 0x8d, 0xd0, 0xf7, 0xf0, 0xe5, 0x60, 0x20, 0x5e, + 0x26, 0xb0, 0x66, 0xad, 0x5c, 0x2a, 0x8d, 0xc3, + 0x35, 0xa9, 0x04, 0x8d, 0xbb, 0x35, 0xa9, 0x01, + 0x8d, 0xbc, 0x35, 0x4c, 0xa8, 0x26, 0x20, 0x5e, + 0x26, 0xb0, 0x4e, 0xa9, 0x06, 0x8d, 0x52, 0x2a, + 0x20, 0x8c, 0x26, 0xd0, 0x0f, 0x20, 0xfc, 0x22, + 0xa9, 0x03, 0xcd, 0x52, 0x2a, 0xf0, 0xce, 0xa9, + 0x05, 0x4c, 0xd2, 0x26, 0xc9, 0xe0, 0x90, 0x02, + 0x29, 0x7f, 0x8d, 0x5c, 0x2a, 0xae, 0x5a, 0x2a, + 0xf0, 0x09, 0xca, 0xbd, 0x00, 0x02, 0x09, 0x80, + 0x9d, 0x00, 0x02, 0x4c, 0xb3, 0x1f, 0x48, 0xad, + 0xb6, 0x2a, 0xf0, 0x0e, 0xa6, 0x76, 0xe8, 0xf0, + 0x0d, 0xa6, 0x33, 0xe0, 0xdd, 0xf0, 0x07, 0x68, + 0x18, 0x60, 0xa5, 0xd9, 0x30, 0xf9, 0x68, 0x38, + 0x60, 0x20, 0xfc, 0x22, 0x20, 0x5b, 0x27, 0x4c, + 0xb3, 0x1f, 0x20, 0x9d, 0x26, 0x20, 0x4e, 0x27, + 0xa9, 0x03, 0xd0, 0xa1, 0xa9, 0x03, 0x8d, 0xbb, + 0x35, 0xa9, 0x01, 0x8d, 0xbc, 0x35, 0x20, 0xa8, + 0x26, 0xad, 0xc3, 0x35, 0x60, 0xad, 0xb5, 0x2a, + 0x85, 0x41, 0xad, 0xb4, 0x2a, 0x85, 0x40, 0x60, + 0x20, 0x06, 0x2b, 0x90, 0x16, 0x20, 0x64, 0x27, + 0xb0, 0x05, 0xa9, 0x00, 0xa8, 0x91, 0x40, 0xad, + 0xc5, 0x35, 0xc9, 0x05, 0xd0, 0x14, 0xa2, 0x00, + 0x8e, 0xc3, 0x35, 0x60, 0xa9, 0x0b, 0xd0, 0x0a, + 0xa9, 0x0c, 0xd0, 0x06, 0xa9, 0x0e, 0xd0, 0x02, + 0xa9, 0x0d, 0x8d, 0x5c, 0x2a, 0x20, 0xe6, 0x3f, + 0xad, 0xb6, 0x2a, 0xf0, 0x04, 0xa5, 0xd8, 0x30, + 0x0e, 0xa2, 0x00, 0x20, 0x02, 0x27, 0xae, 0x5c, + 0x2a, 0x20, 0x02, 0x27, 0x20, 0xc8, 0x1f, 0x20, + 0x51, 0x28, 0x20, 0x5e, 0x26, 0xae, 0x5c, 0x2a, + 0xa9, 0x03, 0xb0, 0x03, 0x6c, 0x5a, 0x1d, 0x6c, + + 0x5e, 0x1d, 0xbd, 0x3f, 0x2a, 0xaa, 0x8e, 0x63, + 0x2a, 0xbd, 0x71, 0x29, 0x48, 0x09, 0x80, 0x20, + 0xc5, 0x1f, 0xae, 0x63, 0x2a, 0xe8, 0x68, 0x10, + 0xed, 0x60, 0xad, 0x66, 0x2a, 0x8d, 0xbf, 0x35, + 0xad, 0x68, 0x2a, 0x8d, 0xc0, 0x35, 0xad, 0x6a, + 0x2a, 0x8d, 0xc1, 0x35, 0xad, 0x06, 0x1d, 0x8d, + 0xc3, 0x35, 0xad, 0x07, 0x1d, 0x8d, 0xc4, 0x35, + 0xa5, 0x40, 0x8d, 0x4f, 0x2a, 0xa5, 0x41, 0x8d, + 0x50, 0x2a, 0x60, 0xa0, 0x1d, 0xb9, 0x75, 0x2a, + 0x91, 0x40, 0x88, 0x10, 0xf8, 0x60, 0xa0, 0x1e, + 0xb1, 0x40, 0x99, 0xa9, 0x35, 0xc8, 0xc0, 0x26, + 0xd0, 0xf6, 0x60, 0xa0, 0x00, 0x8c, 0x51, 0x2a, + 0x8c, 0x52, 0x2a, 0x60, 0xa9, 0x00, 0x85, 0x45, + 0x20, 0x92, 0x27, 0x4c, 0x73, 0x27, 0x20, 0x9a, + 0x27, 0xf0, 0x1d, 0x20, 0xaa, 0x27, 0xd0, 0x0a, + 0xa5, 0x40, 0x85, 0x44, 0xa5, 0x41, 0x85, 0x45, + 0xd0, 0xec, 0xa0, 0x1d, 0xb1, 0x40, 0xd9, 0x75, + 0x2a, 0xd0, 0xe3, 0x88, 0x10, 0xf6, 0x18, 0x60, + 0x38, 0x60, 0xad, 0x00, 0x1d, 0xae, 0x01, 0x1d, + 0xd0, 0x0a, 0xa0, 0x25, 0xb1, 0x40, 0xf0, 0x09, + 0xaa, 0x88, 0xb1, 0x40, 0x86, 0x41, 0x85, 0x40, + 0x8a, 0x60, 0xa0, 0x00, 0xb1, 0x40, 0x60, 0xad, + 0xb3, 0x2a, 0xf0, 0x0e, 0xad, 0xb4, 0x2a, 0xc5, + 0x40, 0xd0, 0x08, 0xad, 0xb5, 0x2a, 0xc5, 0x41, + 0xf0, 0x01, 0xca, 0x60, 0x4d, 0xc2, 0x35, 0xf0, + 0x0a, 0x29, 0x7f, 0xf0, 0x06, 0x20, 0xea, 0x22, + 0x4c, 0xd0, 0x26, 0x60, 0x38, 0xad, 0x00, 0x1d, + 0x85, 0x40, 0xad, 0x01, 0x1d, 0x85, 0x41, 0xad, + 0x57, 0x2a, 0x8d, 0x63, 0x2a, 0xa0, 0x00, 0x98, + 0x91, 0x40, 0xa0, 0x1e, 0x38, 0xa5, 0x40, 0xe9, + 0x2d, 0x91, 0x40, 0x48, 0xa5, 0x41, 0xe9, 0x00, + 0xc8, 0x91, 0x40, 0xaa, 0xca, 0x68, 0x48, 0xc8, + + 0x91, 0x40, 0x8a, 0xc8, 0x91, 0x40, 0xaa, 0xca, + 0x68, 0x48, 0xc8, 0x91, 0x40, 0xc8, 0x8a, 0x91, + 0x40, 0xce, 0x63, 0x2a, 0xf0, 0x17, 0xaa, 0x68, + 0x38, 0xe9, 0x26, 0xc8, 0x91, 0x40, 0x48, 0x8a, + 0xe9, 0x00, 0xc8, 0x91, 0x40, 0x85, 0x41, 0x68, + 0x85, 0x40, 0x4c, 0xe5, 0x27, 0x48, 0xa9, 0x00, + 0xc8, 0x91, 0x40, 0xc8, 0x91, 0x40, 0xad, 0xb6, + 0x2a, 0xf0, 0x0b, 0x68, 0x85, 0x74, 0x85, 0x70, + 0x68, 0x85, 0x73, 0x85, 0x6f, 0x60, 0x68, 0x85, + 0x4d, 0x85, 0xcb, 0x68, 0x85, 0x4c, 0x85, 0xca, + 0x60, 0xa5, 0x39, 0xcd, 0x03, 0x1d, 0xf0, 0x12, + 0x8d, 0x56, 0x2a, 0xa5, 0x38, 0x8d, 0x55, 0x2a, + 0xad, 0x02, 0x1d, 0x85, 0x38, 0xad, 0x03, 0x1d, + 0x85, 0x39, 0xa5, 0x37, 0xcd, 0x05, 0x1d, 0xf0, + 0x12, 0x8d, 0x54, 0x2a, 0xa5, 0x36, 0x8d, 0x53, + 0x2a, 0xad, 0x04, 0x1d, 0x85, 0x36, 0xad, 0x05, + 0x1d, 0x85, 0x37, 0x60, 0x49, 0x4e, 0x49, 0xd4, + 0x4c, 0x4f, 0x41, 0xc4, 0x53, 0x41, 0x56, 0xc5, + 0x52, 0x55, 0xce, 0x43, 0x48, 0x41, 0x49, 0xce, + 0x44, 0x45, 0x4c, 0x45, 0x54, 0xc5, 0x4c, 0x4f, + 0x43, 0xcb, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0xcb, + 0x43, 0x4c, 0x4f, 0x53, 0xc5, 0x52, 0x45, 0x41, + 0xc4, 0x45, 0x58, 0x45, 0xc3, 0x57, 0x52, 0x49, + 0x54, 0xc5, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, + 0x4f, 0xce, 0x4f, 0x50, 0x45, 0xce, 0x41, 0x50, + 0x50, 0x45, 0x4e, 0xc4, 0x52, 0x45, 0x4e, 0x41, + 0x4d, 0xc5, 0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, + 0xc7, 0x4d, 0x4f, 0xce, 0x4e, 0x4f, 0x4d, 0x4f, + 0xce, 0x50, 0x52, 0xa3, 0x49, 0x4e, 0xa3, 0x4d, + 0x41, 0x58, 0x46, 0x49, 0x4c, 0x45, 0xd3, 0x46, + 0xd0, 0x49, 0x4e, 0xd4, 0x42, 0x53, 0x41, 0x56, + 0xc5, 0x42, 0x4c, 0x4f, 0x41, 0xc4, 0x42, 0x52, + + 0x55, 0xce, 0x56, 0x45, 0x52, 0x49, 0x46, 0xd9, + 0x00, 0x21, 0x70, 0xa0, 0x70, 0xa1, 0x70, 0xa0, + 0x70, 0x20, 0x70, 0x20, 0x70, 0x20, 0x70, 0x20, + 0x70, 0x60, 0x00, 0x22, 0x06, 0x20, 0x74, 0x22, + 0x06, 0x22, 0x04, 0x23, 0x78, 0x22, 0x70, 0x30, + 0x70, 0x40, 0x70, 0x40, 0x80, 0x40, 0x80, 0x08, + 0x00, 0x08, 0x00, 0x04, 0x00, 0x40, 0x70, 0x40, + 0x00, 0x21, 0x79, 0x20, 0x71, 0x20, 0x71, 0x20, + 0x70, 0xd6, 0xc4, 0xd3, 0xcc, 0xd2, 0xc2, 0xc1, + 0xc3, 0xc9, 0xcf, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0xc0, 0xa0, 0x90, 0x00, 0x00, 0xfe, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, + 0x00, 0x01, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xff, + 0x7f, 0x00, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xff, + 0xff, 0x0d, 0x07, 0x8d, 0x4c, 0x41, 0x4e, 0x47, + 0x55, 0x41, 0x47, 0x45, 0x20, 0x4e, 0x4f, 0x54, + 0x20, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, + 0x4c, 0xc5, 0x52, 0x41, 0x4e, 0x47, 0x45, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x57, 0x52, 0x49, + 0x54, 0x45, 0x20, 0x50, 0x52, 0x4f, 0x54, 0x45, + 0x43, 0x54, 0x45, 0xc4, 0x45, 0x4e, 0x44, 0x20, + 0x4f, 0x46, 0x20, 0x44, 0x41, 0x54, 0xc1, 0x46, + 0x49, 0x4c, 0x45, 0x20, 0x4e, 0x4f, 0x54, 0x20, + 0x46, 0x4f, 0x55, 0x4e, 0xc4, 0x56, 0x4f, 0x4c, + 0x55, 0x4d, 0x45, 0x20, 0x4d, 0x49, 0x53, 0x4d, + 0x41, 0x54, 0x43, 0xc8, 0x49, 0x2f, 0x4f, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x44, 0x49, 0x53, + 0x4b, 0x20, 0x46, 0x55, 0x4c, 0xcc, 0x46, 0x49, + 0x4c, 0x45, 0x20, 0x4c, 0x4f, 0x43, 0x4b, 0x45, + 0xc4, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0xd2, 0x4e, 0x4f, 0x20, + 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x53, 0x20, + + 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, + 0xc5, 0x46, 0x49, 0x4c, 0x45, 0x20, 0x54, 0x59, + 0x50, 0x45, 0x20, 0x4d, 0x49, 0x53, 0x4d, 0x41, + 0x54, 0x43, 0xc8, 0x50, 0x52, 0x4f, 0x47, 0x52, + 0x41, 0x4d, 0x20, 0x54, 0x4f, 0x4f, 0x20, 0x4c, + 0x41, 0x52, 0x47, 0xc5, 0x4e, 0x4f, 0x54, 0x20, + 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20, 0x43, + 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0xc4, 0x8d, 0x00, + 0x03, 0x19, 0x19, 0x24, 0x33, 0x3e, 0x4c, 0x5b, + 0x64, 0x6d, 0x78, 0x84, 0x98, 0xaa, 0xbb, 0x2d, + 0x18, 0x00, 0x00, 0xf0, 0xfd, 0x1b, 0xfd, 0x03, + 0x03, 0xfb, 0x0a, 0x28, 0x8d, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc5, 0xcc, + 0xcc, 0xcf, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0x03, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc1, 0xd0, 0xd0, 0xcc, 0xc5, 0xd3, 0xcf, 0xc6, + 0xd4, 0xe8, 0x37, 0xbb, 0x33, 0xbb, 0x34, 0x00, + 0x40, 0x7e, 0x33, 0x21, 0x2b, 0x05, 0x2c, 0x57, + 0x2c, 0x6f, 0x2c, 0x2a, 0x2d, 0x97, 0x2d, 0xee, + 0x2c, 0xf5, 0x2c, 0x39, 0x2c, 0x11, 0x2d, 0x8d, + 0x2e, 0x17, 0x2d, 0x7e, 0x33, 0x7e, 0x33, 0x89, + 0x2c, 0x95, 0x2c, 0x86, 0x2c, 0x92, 0x2c, 0x7e, + 0x33, 0x7e, 0x33, 0xbd, 0x2c, 0xc9, 0x2c, 0xba, + 0x2c, 0xc6, 0x2c, 0x7e, 0x33, 0xe0, 0x00, 0xf0, + + 0x02, 0xa2, 0x02, 0x8e, 0x5f, 0x2a, 0xba, 0x8e, + 0x9b, 0x33, 0x20, 0x6a, 0x2e, 0xad, 0xbb, 0x35, + 0xc9, 0x0d, 0xb0, 0x0b, 0x0a, 0xaa, 0xbd, 0xca, + 0x2a, 0x48, 0xbd, 0xc9, 0x2a, 0x48, 0x60, 0x4c, + 0x63, 0x33, 0x20, 0x28, 0x2b, 0x4c, 0x7f, 0x33, + 0x20, 0xdc, 0x2b, 0xa9, 0x01, 0x8d, 0xe3, 0x35, + 0xae, 0xbe, 0x35, 0xad, 0xbd, 0x35, 0xd0, 0x05, + 0xe0, 0x00, 0xd0, 0x01, 0xe8, 0x8d, 0xe8, 0x35, + 0x8e, 0xe9, 0x35, 0x20, 0xc9, 0x31, 0x90, 0x5e, + 0x8e, 0x9c, 0x33, 0xae, 0x5f, 0x2a, 0xbd, 0x09, + 0x29, 0xae, 0x9c, 0x33, 0x4a, 0xb0, 0x0d, 0xad, + 0x51, 0x2a, 0xc9, 0xc0, 0xd0, 0x03, 0x4c, 0x5f, + 0x33, 0x4c, 0x73, 0x33, 0xa9, 0x00, 0x9d, 0xe8, + 0x34, 0xa9, 0x01, 0x9d, 0xe7, 0x34, 0x8e, 0x9c, + 0x33, 0x20, 0x44, 0x32, 0xae, 0x9c, 0x33, 0x9d, + 0xc7, 0x34, 0x8d, 0xd2, 0x35, 0x8d, 0xd4, 0x35, + 0xad, 0xf1, 0x35, 0x9d, 0xc6, 0x34, 0x8d, 0xd1, + 0x35, 0x8d, 0xd3, 0x35, 0xad, 0xc2, 0x35, 0x9d, + 0xc8, 0x34, 0x20, 0x37, 0x30, 0x20, 0x0c, 0x2f, + 0x20, 0xd6, 0x37, 0x20, 0x3a, 0x2f, 0xae, 0x9c, + 0x33, 0xa9, 0x06, 0x8d, 0xc5, 0x35, 0xbd, 0xc6, + 0x34, 0x8d, 0xd1, 0x35, 0xbd, 0xc7, 0x34, 0x8d, + 0xd2, 0x35, 0xbd, 0xc8, 0x34, 0x8d, 0xc2, 0x35, + 0x8d, 0xf6, 0x35, 0xbd, 0xe7, 0x34, 0x8d, 0xee, + 0x35, 0xbd, 0xe8, 0x34, 0x8d, 0xef, 0x35, 0x8e, + 0xd9, 0x35, 0xa9, 0xff, 0x8d, 0xe0, 0x35, 0x8d, + 0xe1, 0x35, 0xad, 0xe2, 0x33, 0x8d, 0xda, 0x35, + 0x18, 0x4c, 0x5e, 0x2f, 0xa9, 0x00, 0xaa, 0x9d, + 0xd1, 0x35, 0xe8, 0xe0, 0x2d, 0xd0, 0xf8, 0xad, + 0xbf, 0x35, 0x49, 0xff, 0x8d, 0xf9, 0x35, 0xad, + 0xc0, 0x35, 0x8d, 0xf8, 0x35, 0xad, 0xc1, 0x35, + 0x0a, 0x0a, 0x0a, 0x0a, 0xaa, 0x8e, 0xf7, 0x35, + + 0xa9, 0x11, 0x8d, 0xfa, 0x35, 0x60, 0x20, 0x1d, + 0x2f, 0x20, 0x34, 0x2f, 0x20, 0xc3, 0x32, 0xa9, + 0x02, 0x2d, 0xd5, 0x35, 0xf0, 0x21, 0x20, 0xf7, + 0x2f, 0xa9, 0x00, 0x18, 0x20, 0x11, 0x30, 0x38, + 0xce, 0xd8, 0x35, 0xd0, 0xf7, 0xae, 0xd9, 0x35, + 0xad, 0xee, 0x35, 0x9d, 0xe7, 0x34, 0xad, 0xef, + 0x35, 0x9d, 0xe8, 0x34, 0x20, 0x37, 0x30, 0x4c, + 0x7f, 0x33, 0x20, 0x28, 0x2b, 0xad, 0xf6, 0x35, + 0x30, 0x2b, 0xad, 0xbd, 0x35, 0x85, 0x42, 0xad, + 0xbe, 0x35, 0x85, 0x43, 0xae, 0x9c, 0x33, 0x20, + 0x1c, 0x32, 0x20, 0x37, 0x30, 0x4c, 0x7f, 0x33, + 0xad, 0xbc, 0x35, 0xc9, 0x05, 0xb0, 0x0b, 0x0a, + 0xaa, 0xbd, 0xe6, 0x2a, 0x48, 0xbd, 0xe5, 0x2a, + 0x48, 0x60, 0x4c, 0x67, 0x33, 0x4c, 0x7b, 0x33, + 0xad, 0xf6, 0x35, 0x30, 0xf8, 0xad, 0xbc, 0x35, + 0xc9, 0x05, 0xb0, 0xee, 0x0a, 0xaa, 0xbd, 0xf2, + 0x2a, 0x48, 0xbd, 0xf1, 0x2a, 0x48, 0x60, 0x20, + 0x00, 0x33, 0x20, 0xa8, 0x2c, 0x8d, 0xc3, 0x35, + 0x4c, 0x7f, 0x33, 0x20, 0x00, 0x33, 0x20, 0xb5, + 0x31, 0x20, 0xa8, 0x2c, 0x48, 0x20, 0xa2, 0x31, + 0xa0, 0x00, 0x68, 0x91, 0x42, 0x4c, 0x96, 0x2c, + 0x20, 0xb6, 0x30, 0xb0, 0x0b, 0xb1, 0x42, 0x48, + 0x20, 0x5b, 0x31, 0x20, 0x94, 0x31, 0x68, 0x60, + 0x4c, 0x6f, 0x33, 0x20, 0x00, 0x33, 0xad, 0xc3, + 0x35, 0x20, 0xda, 0x2c, 0x4c, 0x7f, 0x33, 0x20, + 0x00, 0x33, 0x20, 0xa2, 0x31, 0xa0, 0x00, 0xb1, + 0x42, 0x20, 0xda, 0x2c, 0x20, 0xb5, 0x31, 0x4c, + 0xca, 0x2c, 0x48, 0x20, 0xb6, 0x30, 0x68, 0x91, + 0x42, 0xa9, 0x40, 0x0d, 0xd5, 0x35, 0x8d, 0xd5, + 0x35, 0x20, 0x5b, 0x31, 0x4c, 0x94, 0x31, 0xa9, + 0x80, 0x8d, 0x9e, 0x33, 0xd0, 0x05, 0xa9, 0x00, + 0x8d, 0x9e, 0x33, 0x20, 0x28, 0x2b, 0xae, 0x9c, + + 0x33, 0xbd, 0xc8, 0x34, 0x29, 0x7f, 0x0d, 0x9e, + 0x33, 0x9d, 0xc8, 0x34, 0x20, 0x37, 0x30, 0x4c, + 0x7f, 0x33, 0x20, 0x00, 0x33, 0x4c, 0x7f, 0x33, + 0x20, 0x28, 0x2b, 0x20, 0xb6, 0x30, 0xb0, 0xef, + 0xee, 0xe4, 0x35, 0xd0, 0xf6, 0xee, 0xe5, 0x35, + 0x4c, 0x1b, 0x2d, 0x20, 0x28, 0x2b, 0xae, 0x9c, + 0x33, 0xbd, 0xc8, 0x34, 0x10, 0x03, 0x4c, 0x7b, + 0x33, 0xae, 0x9c, 0x33, 0xbd, 0xc6, 0x34, 0x8d, + 0xd1, 0x35, 0x9d, 0xe6, 0x34, 0xa9, 0xff, 0x9d, + 0xc6, 0x34, 0xbc, 0xc7, 0x34, 0x8c, 0xd2, 0x35, + 0x20, 0x37, 0x30, 0x18, 0x20, 0x5e, 0x2f, 0xb0, + 0x2a, 0x20, 0x0c, 0x2f, 0xa0, 0x0c, 0x8c, 0x9c, + 0x33, 0xb1, 0x42, 0x30, 0x0b, 0xf0, 0x09, 0x48, + 0xc8, 0xb1, 0x42, 0xa8, 0x68, 0x20, 0x89, 0x2d, + 0xac, 0x9c, 0x33, 0xc8, 0xc8, 0xd0, 0xe7, 0xad, + 0xd3, 0x35, 0xac, 0xd4, 0x35, 0x20, 0x89, 0x2d, + 0x38, 0xb0, 0xd1, 0x20, 0xfb, 0x2f, 0x4c, 0x7f, + 0x33, 0x38, 0x20, 0xdd, 0x32, 0xa9, 0x00, 0xa2, + 0x03, 0x9d, 0xf0, 0x35, 0xca, 0x10, 0xfa, 0x60, + 0x20, 0xdc, 0x2b, 0xa9, 0xff, 0x8d, 0xf9, 0x35, + 0x20, 0xf7, 0x2f, 0xa9, 0x16, 0x8d, 0x9d, 0x33, + 0x20, 0x2f, 0x2e, 0x20, 0x2f, 0x2e, 0xa2, 0x0b, + 0xbd, 0xaf, 0x33, 0x20, 0xed, 0xfd, 0xca, 0x10, + 0xf7, 0x86, 0x45, 0xad, 0xf6, 0x37, 0x85, 0x44, + 0x20, 0x42, 0x2e, 0x20, 0x2f, 0x2e, 0x20, 0x2f, + 0x2e, 0x18, 0x20, 0x11, 0x30, 0xb0, 0x5d, 0xa2, + 0x00, 0x8e, 0x9c, 0x33, 0xbd, 0xc6, 0x34, 0xf0, + 0x53, 0x30, 0x4a, 0xa0, 0xa0, 0xbd, 0xc8, 0x34, + 0x10, 0x02, 0xa0, 0xaa, 0x98, 0x20, 0xed, 0xfd, + 0xbd, 0xc8, 0x34, 0x29, 0x7f, 0xa0, 0x07, 0x0a, + 0x0a, 0xb0, 0x03, 0x88, 0xd0, 0xfa, 0xb9, 0xa7, + 0x33, 0x20, 0xed, 0xfd, 0xa9, 0xa0, 0x20, 0xed, + + 0xfd, 0xbd, 0xe7, 0x34, 0x85, 0x44, 0xbd, 0xe8, + 0x34, 0x85, 0x45, 0x20, 0x42, 0x2e, 0xa9, 0xa0, + 0x20, 0xed, 0xfd, 0xe8, 0xe8, 0xe8, 0xa0, 0x1d, + 0xbd, 0xc6, 0x34, 0x20, 0xed, 0xfd, 0xe8, 0x88, + 0x10, 0xf6, 0x20, 0x2f, 0x2e, 0x20, 0x30, 0x32, + 0x90, 0xa7, 0xb0, 0x9e, 0x4c, 0x7f, 0x33, 0xa9, + 0x8d, 0x20, 0xed, 0xfd, 0xce, 0x9d, 0x33, 0xd0, + 0x08, 0x20, 0x0c, 0xfd, 0xa9, 0x15, 0x8d, 0x9d, + 0x33, 0x60, 0xa0, 0x02, 0xa9, 0x00, 0x48, 0xa5, + 0x44, 0xd9, 0xa4, 0x33, 0x90, 0x12, 0xf9, 0xa4, + 0x33, 0x85, 0x44, 0xa5, 0x45, 0xe9, 0x00, 0x85, + 0x45, 0x68, 0x69, 0x00, 0x48, 0x4c, 0x47, 0x2e, + 0x68, 0x09, 0xb0, 0x20, 0xed, 0xfd, 0x88, 0x10, + 0xdb, 0x60, 0x20, 0x08, 0x2f, 0xa0, 0x00, 0x8c, + 0xc5, 0x35, 0xb1, 0x42, 0x99, 0xd1, 0x35, 0xc8, + 0xc0, 0x2d, 0xd0, 0xf6, 0x18, 0x60, 0x20, 0x08, + 0x2f, 0xa0, 0x00, 0xb9, 0xd1, 0x35, 0x91, 0x42, + 0xc8, 0xc0, 0x2d, 0xd0, 0xf6, 0x60, 0x20, 0xdc, + 0x2b, 0xa9, 0x04, 0x20, 0x58, 0x30, 0xad, 0xf9, + 0x35, 0x49, 0xff, 0x8d, 0xc1, 0x33, 0xa9, 0x11, + 0x8d, 0xeb, 0x33, 0xa9, 0x01, 0x8d, 0xec, 0x33, + 0xa2, 0x38, 0xa9, 0x00, 0x9d, 0xbb, 0x33, 0xe8, + 0xd0, 0xfa, 0xa2, 0x0c, 0xe0, 0x8c, 0xf0, 0x14, + 0xa0, 0x03, 0xb9, 0xa0, 0x33, 0x9d, 0xf3, 0x33, + 0xe8, 0x88, 0x10, 0xf6, 0xe0, 0x44, 0xd0, 0xec, + 0xa2, 0x48, 0xd0, 0xe8, 0x20, 0xfb, 0x2f, 0xa2, + 0x00, 0x8a, 0x9d, 0xbb, 0x34, 0xe8, 0xd0, 0xfa, + 0x20, 0x45, 0x30, 0xa9, 0x11, 0xac, 0xf0, 0x33, + 0x88, 0x88, 0x8d, 0xec, 0x37, 0x8d, 0xbc, 0x34, + 0x8c, 0xbd, 0x34, 0xc8, 0x8c, 0xed, 0x37, 0xa9, + 0x02, 0x20, 0x58, 0x30, 0xac, 0xbd, 0x34, 0x88, + 0x30, 0x05, 0xd0, 0xec, 0x98, 0xf0, 0xe6, 0x20, + + 0xc2, 0x37, 0x20, 0x4a, 0x37, 0x4c, 0x7f, 0x33, + 0xa2, 0x00, 0xf0, 0x06, 0xa2, 0x02, 0xd0, 0x02, + 0xa2, 0x04, 0xbd, 0xc7, 0x35, 0x85, 0x42, 0xbd, + 0xc8, 0x35, 0x85, 0x43, 0x60, 0x2c, 0xd5, 0x35, + 0x70, 0x01, 0x60, 0x20, 0xe4, 0x2f, 0xa9, 0x02, + 0x20, 0x52, 0x30, 0xa9, 0xbf, 0x2d, 0xd5, 0x35, + 0x8d, 0xd5, 0x35, 0x60, 0xad, 0xd5, 0x35, 0x30, + 0x01, 0x60, 0x20, 0x4b, 0x2f, 0xa9, 0x02, 0x20, + 0x52, 0x30, 0xa9, 0x7f, 0x2d, 0xd5, 0x35, 0x8d, + 0xd5, 0x35, 0x60, 0xad, 0xc9, 0x35, 0x8d, 0xf0, + 0x37, 0xad, 0xca, 0x35, 0x8d, 0xf1, 0x37, 0xae, + 0xd3, 0x35, 0xac, 0xd4, 0x35, 0x60, 0x08, 0x20, + 0x34, 0x2f, 0x20, 0x4b, 0x2f, 0x20, 0x0c, 0x2f, + 0x28, 0xb0, 0x09, 0xae, 0xd1, 0x35, 0xac, 0xd2, + 0x35, 0x4c, 0xb5, 0x2f, 0xa0, 0x01, 0xb1, 0x42, + 0xf0, 0x08, 0xaa, 0xc8, 0xb1, 0x42, 0xa8, 0x4c, + 0xb5, 0x2f, 0xad, 0xbb, 0x35, 0xc9, 0x04, 0xf0, + 0x02, 0x38, 0x60, 0x20, 0x44, 0x32, 0xa0, 0x02, + 0x91, 0x42, 0x48, 0x88, 0xad, 0xf1, 0x35, 0x91, + 0x42, 0x48, 0x20, 0x3a, 0x2f, 0x20, 0xd6, 0x37, + 0xa0, 0x05, 0xad, 0xde, 0x35, 0x91, 0x42, 0xc8, + 0xad, 0xdf, 0x35, 0x91, 0x42, 0x68, 0xaa, 0x68, + 0xa8, 0xa9, 0x02, 0xd0, 0x02, 0xa9, 0x01, 0x8e, + 0xd3, 0x35, 0x8c, 0xd4, 0x35, 0x20, 0x52, 0x30, + 0xa0, 0x05, 0xb1, 0x42, 0x8d, 0xdc, 0x35, 0x18, + 0x6d, 0xda, 0x35, 0x8d, 0xde, 0x35, 0xc8, 0xb1, + 0x42, 0x8d, 0xdd, 0x35, 0x6d, 0xdb, 0x35, 0x8d, + 0xdf, 0x35, 0x18, 0x60, 0x20, 0xe4, 0x2f, 0xa9, + 0x01, 0x4c, 0x52, 0x30, 0xac, 0xcb, 0x35, 0xad, + 0xcc, 0x35, 0x8c, 0xf0, 0x37, 0x8d, 0xf1, 0x37, + 0xae, 0xd6, 0x35, 0xac, 0xd7, 0x35, 0x60, 0xa9, + 0x01, 0xd0, 0x02, 0xa9, 0x02, 0xac, 0xc3, 0x2a, + + 0x8c, 0xf0, 0x37, 0xac, 0xc4, 0x2a, 0x8c, 0xf1, + 0x37, 0xae, 0xfa, 0x35, 0xa0, 0x00, 0x4c, 0x52, + 0x30, 0x08, 0x20, 0x45, 0x30, 0x28, 0xb0, 0x08, + 0xac, 0xbd, 0x33, 0xae, 0xbc, 0x33, 0xd0, 0x0a, + 0xae, 0xbc, 0x34, 0xd0, 0x02, 0x38, 0x60, 0xac, + 0xbd, 0x34, 0x8e, 0x97, 0x33, 0x8c, 0x98, 0x33, + 0xa9, 0x01, 0x20, 0x52, 0x30, 0x18, 0x60, 0x20, + 0x45, 0x30, 0xae, 0x97, 0x33, 0xac, 0x98, 0x33, + 0xa9, 0x02, 0x4c, 0x52, 0x30, 0xad, 0xc5, 0x2a, + 0x8d, 0xf0, 0x37, 0xad, 0xc6, 0x2a, 0x8d, 0xf1, + 0x37, 0x60, 0x8e, 0xec, 0x37, 0x8c, 0xed, 0x37, + 0x8d, 0xf4, 0x37, 0xc9, 0x02, 0xd0, 0x06, 0x0d, + 0xd5, 0x35, 0x8d, 0xd5, 0x35, 0xad, 0xf9, 0x35, + 0x49, 0xff, 0x8d, 0xeb, 0x37, 0xad, 0xf7, 0x35, + 0x8d, 0xe9, 0x37, 0xad, 0xf8, 0x35, 0x8d, 0xea, + 0x37, 0xad, 0xe2, 0x35, 0x8d, 0xf2, 0x37, 0xad, + 0xe3, 0x35, 0x8d, 0xf3, 0x37, 0xa9, 0x01, 0x8d, + 0xe8, 0x37, 0xac, 0xc1, 0x2a, 0xad, 0xc2, 0x2a, + 0x20, 0xb5, 0x37, 0xad, 0xf6, 0x37, 0x8d, 0xbf, + 0x35, 0xa9, 0xff, 0x8d, 0xeb, 0x37, 0xb0, 0x01, + 0x60, 0xad, 0xf5, 0x37, 0xa0, 0x07, 0xc9, 0x20, + 0xf0, 0x08, 0xa0, 0x04, 0xc9, 0x10, 0xf0, 0x02, + 0xa0, 0x08, 0x98, 0x4c, 0x85, 0x33, 0xad, 0xe4, + 0x35, 0xcd, 0xe0, 0x35, 0xd0, 0x08, 0xad, 0xe5, + 0x35, 0xcd, 0xe1, 0x35, 0xf0, 0x66, 0x20, 0x1d, + 0x2f, 0xad, 0xe5, 0x35, 0xcd, 0xdd, 0x35, 0x90, + 0x1c, 0xd0, 0x08, 0xad, 0xe4, 0x35, 0xcd, 0xdc, + 0x35, 0x90, 0x12, 0xad, 0xe5, 0x35, 0xcd, 0xdf, + 0x35, 0x90, 0x10, 0xd0, 0x08, 0xad, 0xe4, 0x35, + 0xcd, 0xde, 0x35, 0x90, 0x06, 0x20, 0x5e, 0x2f, + 0x90, 0xd7, 0x60, 0x38, 0xad, 0xe4, 0x35, 0xed, + 0xdc, 0x35, 0x0a, 0x69, 0x0c, 0xa8, 0x20, 0x0c, + + 0x2f, 0xb1, 0x42, 0xd0, 0x0f, 0xad, 0xbb, 0x35, + 0xc9, 0x04, 0xf0, 0x02, 0x38, 0x60, 0x20, 0x34, + 0x31, 0x4c, 0x20, 0x31, 0x8d, 0xd6, 0x35, 0xc8, + 0xb1, 0x42, 0x8d, 0xd7, 0x35, 0x20, 0xdc, 0x2f, + 0xad, 0xe4, 0x35, 0x8d, 0xe0, 0x35, 0xad, 0xe5, + 0x35, 0x8d, 0xe1, 0x35, 0x20, 0x10, 0x2f, 0xac, + 0xe6, 0x35, 0x18, 0x60, 0x8c, 0x9d, 0x33, 0x20, + 0x44, 0x32, 0xac, 0x9d, 0x33, 0xc8, 0x91, 0x42, + 0x8d, 0xd7, 0x35, 0x88, 0xad, 0xf1, 0x35, 0x91, + 0x42, 0x8d, 0xd6, 0x35, 0x20, 0x10, 0x2f, 0x20, + 0xd6, 0x37, 0xa9, 0xc0, 0x0d, 0xd5, 0x35, 0x8d, + 0xd5, 0x35, 0x60, 0xae, 0xea, 0x35, 0x8e, 0xbd, + 0x35, 0xae, 0xeb, 0x35, 0x8e, 0xbe, 0x35, 0xae, + 0xec, 0x35, 0xac, 0xed, 0x35, 0x8e, 0xbf, 0x35, + 0x8c, 0xc0, 0x35, 0xe8, 0xd0, 0x01, 0xc8, 0xcc, + 0xe9, 0x35, 0xd0, 0x11, 0xec, 0xe8, 0x35, 0xd0, + 0x0c, 0xa2, 0x00, 0xa0, 0x00, 0xee, 0xea, 0x35, + 0xd0, 0x03, 0xee, 0xeb, 0x35, 0x8e, 0xec, 0x35, + 0x8c, 0xed, 0x35, 0x60, 0xee, 0xe6, 0x35, 0xd0, + 0x08, 0xee, 0xe4, 0x35, 0xd0, 0x03, 0xee, 0xe5, + 0x35, 0x60, 0xac, 0xc3, 0x35, 0xae, 0xc4, 0x35, + 0x84, 0x42, 0x86, 0x43, 0xee, 0xc3, 0x35, 0xd0, + 0x03, 0xee, 0xc4, 0x35, 0x60, 0xac, 0xc1, 0x35, + 0xd0, 0x08, 0xae, 0xc2, 0x35, 0xf0, 0x07, 0xce, + 0xc2, 0x35, 0xce, 0xc1, 0x35, 0x60, 0x4c, 0x7f, + 0x33, 0x20, 0xf7, 0x2f, 0xad, 0xc3, 0x35, 0x85, + 0x42, 0xad, 0xc4, 0x35, 0x85, 0x43, 0xa9, 0x01, + 0x8d, 0x9d, 0x33, 0xa9, 0x00, 0x8d, 0xd8, 0x35, + 0x18, 0xee, 0xd8, 0x35, 0x20, 0x11, 0x30, 0xb0, + 0x51, 0xa2, 0x00, 0x8e, 0x9c, 0x33, 0xbd, 0xc6, + 0x34, 0xf0, 0x1f, 0x30, 0x22, 0xa0, 0x00, 0xe8, + 0xe8, 0xe8, 0xb1, 0x42, 0xdd, 0xc6, 0x34, 0xd0, + + 0x0a, 0xc8, 0xc0, 0x1e, 0xd0, 0xf3, 0xae, 0x9c, + 0x33, 0x18, 0x60, 0x20, 0x30, 0x32, 0x90, 0xdb, + 0xb0, 0xcf, 0xac, 0x9d, 0x33, 0xd0, 0xc1, 0xac, + 0x9d, 0x33, 0xd0, 0xef, 0xa0, 0x00, 0xe8, 0xe8, + 0xe8, 0xb1, 0x42, 0x9d, 0xc6, 0x34, 0xc8, 0xc0, + 0x1e, 0xd0, 0xf5, 0xae, 0x9c, 0x33, 0x38, 0x60, + 0x18, 0xad, 0x9c, 0x33, 0x69, 0x23, 0xaa, 0xe0, + 0xf5, 0x60, 0xa9, 0x00, 0xac, 0x9d, 0x33, 0xd0, + 0x97, 0x4c, 0x77, 0x33, 0xad, 0xf1, 0x35, 0xf0, + 0x21, 0xce, 0xf0, 0x35, 0x30, 0x17, 0x18, 0xa2, + 0x04, 0x3e, 0xf1, 0x35, 0xca, 0xd0, 0xfa, 0x90, + 0xf0, 0xee, 0xee, 0x35, 0xd0, 0x03, 0xee, 0xef, + 0x35, 0xad, 0xf0, 0x35, 0x60, 0xa9, 0x00, 0x8d, + 0xf1, 0x35, 0xa9, 0x00, 0x8d, 0x9e, 0x33, 0x20, + 0xf7, 0x2f, 0x18, 0xad, 0xeb, 0x33, 0x6d, 0xec, + 0x33, 0xf0, 0x09, 0xcd, 0xef, 0x33, 0x90, 0x14, + 0xa9, 0xff, 0xd0, 0x0a, 0xad, 0x9e, 0x33, 0xd0, + 0x37, 0xa9, 0x01, 0x8d, 0x9e, 0x33, 0x8d, 0xec, + 0x33, 0x18, 0x69, 0x11, 0x8d, 0xeb, 0x33, 0x8d, + 0xf1, 0x35, 0xa8, 0x0a, 0x0a, 0xa8, 0xa2, 0x04, + 0x18, 0xb9, 0xf6, 0x33, 0x9d, 0xf1, 0x35, 0xf0, + 0x06, 0x38, 0xa9, 0x00, 0x99, 0xf6, 0x33, 0x88, + 0xca, 0xd0, 0xee, 0x90, 0xbd, 0x20, 0xfb, 0x2f, + 0xad, 0xf0, 0x33, 0x8d, 0xf0, 0x35, 0xd0, 0x89, + 0x4c, 0x77, 0x33, 0xad, 0xf1, 0x35, 0xd0, 0x01, + 0x60, 0x48, 0x20, 0xf7, 0x2f, 0xac, 0xf0, 0x35, + 0x68, 0x18, 0x20, 0xdd, 0x32, 0xa9, 0x00, 0x8d, + 0xf1, 0x35, 0x4c, 0xfb, 0x2f, 0xa2, 0xfc, 0x7e, + 0xf6, 0x34, 0xe8, 0xd0, 0xfa, 0xc8, 0xcc, 0xf0, + 0x33, 0xd0, 0xf2, 0x0a, 0x0a, 0xa8, 0xf0, 0x0f, + 0xa2, 0x04, 0xbd, 0xf1, 0x35, 0x19, 0xf6, 0x33, + 0x99, 0xf6, 0x33, 0x88, 0xca, 0xd0, 0xf3, 0x60, + + 0xad, 0xbd, 0x35, 0x8d, 0xe6, 0x35, 0x8d, 0xea, + 0x35, 0xad, 0xbe, 0x35, 0x8d, 0xe4, 0x35, 0x8d, + 0xeb, 0x35, 0xa9, 0x00, 0x8d, 0xe5, 0x35, 0xa0, + 0x10, 0xaa, 0xad, 0xe6, 0x35, 0x4a, 0xb0, 0x03, + 0x8a, 0x90, 0x0e, 0x18, 0xad, 0xe5, 0x35, 0x6d, + 0xe8, 0x35, 0x8d, 0xe5, 0x35, 0x8a, 0x6d, 0xe9, + 0x35, 0x6a, 0x6e, 0xe5, 0x35, 0x6e, 0xe4, 0x35, + 0x6e, 0xe6, 0x35, 0x88, 0xd0, 0xdb, 0xad, 0xbf, + 0x35, 0x8d, 0xec, 0x35, 0x6d, 0xe6, 0x35, 0x8d, + 0xe6, 0x35, 0xad, 0xc0, 0x35, 0x8d, 0xed, 0x35, + 0x6d, 0xe4, 0x35, 0x8d, 0xe4, 0x35, 0xa9, 0x00, + 0x6d, 0xe5, 0x35, 0x8d, 0xe5, 0x35, 0x60, 0xa9, + 0x01, 0xd0, 0x22, 0xa9, 0x02, 0xd0, 0x1e, 0xa9, + 0x03, 0xd0, 0x1a, 0xa9, 0x04, 0xd0, 0x16, 0xa9, + 0x05, 0xd0, 0x12, 0xa9, 0x06, 0xd0, 0x0e, 0x4c, + 0xed, 0x3f, 0xea, 0xa9, 0x0a, 0xd0, 0x06, 0xad, + 0xc5, 0x35, 0x18, 0x90, 0x01, 0x38, 0x08, 0x8d, + 0xc5, 0x35, 0xa9, 0x00, 0x85, 0x48, 0x20, 0x7e, + 0x2e, 0x28, 0xae, 0x9b, 0x33, 0x9a, 0x60, 0x00, + 0x00, 0x00, 0x00, 0xf5, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf8, 0xff, 0x01, 0x0a, 0x64, 0xd4, + 0xc9, 0xc1, 0xc2, 0xd3, 0xd2, 0xc1, 0xc2, 0xa0, + 0xc5, 0xcd, 0xd5, 0xcc, 0xcf, 0xd6, 0xa0, 0xcb, + 0xd3, 0xc9, 0xc4, 0x02, 0x11, 0x0c, 0x03, 0x00, + 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x23, + 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, + 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x01, 0x00, 0xfe, + 0x01, 0x06, 0x00, 0x75, 0x2a, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x17, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, + 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +} // namespace DiskImgLib diff --git a/diskimg/DiskFS.cpp b/diskimg/DiskFS.cpp new file mode 100644 index 0000000..239a631 --- /dev/null +++ b/diskimg/DiskFS.cpp @@ -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); + } +} diff --git a/diskimg/DiskImg.cpp b/diskimg/DiskImg.cpp new file mode 100644 index 0000000..1654ea7 --- /dev/null +++ b/diskimg/DiskImg.cpp @@ -0,0 +1,3502 @@ +/* + * CiderPress + * Copyright (C) 2009 by CiderPress authors. All Rights Reserved. + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Implementation of the DiskImg class. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" +#include "TwoImg.h" + + +/* + * =========================================================================== + * DiskImg + * =========================================================================== + */ + +/* + * Standard NibbleDescr profiles. + * + * These will be tried in the order in which they appear here. + * + * IMPORTANT: if you add or remove an entry, update the StdNibbleDescr enum + * in DiskImg.h. + * + * Formats that allow the data checksum to be ignored should NOT be written. + * It's possible that the DOS on the disk is ignoring the checksums, but + * it's more likely that they're using a non-standard seed, and the newly- + * written sectors will have the wrong checksum value. + * + * Non-standard headers are usually okay, because we don't rewrite the + * headers, just the sector contents. + */ +/*static*/ const DiskImg::NibbleDescr DiskImg::kStdNibbleDescrs[] = { + { + "DOS 3.3 Standard", + 16, + { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + true, // verify checksum + true, // verify track + 2, // epilog verify count + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + true, // verify checksum + 2, // epilog verify count + kNibbleEnc62, + kNibbleSpecialNone, + }, + { + "DOS 3.3 Patched", + 16, + { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + false, // verify checksum + false, // verify track + 0, // epilog verify count + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + true, // verify checksum + 0, // epilog verify count + kNibbleEnc62, + kNibbleSpecialNone, + }, + { + "DOS 3.3 Ignore Checksum", + 16, + { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + false, // verify checksum + false, // verify track + 0, // epilog verify count + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, // checksum seed + false, // verify checksum + 0, // epilog verify count + kNibbleEnc62, + kNibbleSpecialNone, + }, + { + "DOS 3.2 Standard", + 13, + { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + true, + 2, + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + 2, + kNibbleEnc53, + kNibbleSpecialNone, + }, + { + "DOS 3.2 Patched", + 13, + { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, + 0x00, + false, + false, + 0, + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + 0, + kNibbleEnc53, + kNibbleSpecialNone, + }, + { + "Muse DOS 3.2", // standard DOS 3.2 with doubled sectors + 13, + { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + true, + 2, + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + 2, + kNibbleEnc53, + kNibbleSpecialMuse, + }, + { + "RDOS 3.3", // SSI 16-sector RDOS, with altered headers + 16, + { 0xd4, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + true, + 0, // epilog verify count + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + 2, + kNibbleEnc62, + kNibbleSpecialSkipFirstAddrByte, + /* odd tracks use d4aa96, even tracks use d5aa96 */ + }, + { + "RDOS 3.2", // SSI 13-sector RDOS, with altered headers + 13, + { 0xd4, 0xaa, 0xb7 }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + true, + 2, + { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, + 0x00, + true, + 2, + kNibbleEnc53, + kNibbleSpecialNone, + }, + { + "Custom", // reserve space for empty slot + 0, + }, +}; +/*static*/ const DiskImg::NibbleDescr* +DiskImg::GetStdNibbleDescr(StdNibbleDescr idx) +{ + if ((int)idx < 0 || (int)idx >= (int) NELEM(kStdNibbleDescrs)) + return NULL; + return &kStdNibbleDescrs[(int)idx]; +} + + +/* + * Initialize the members during construction. + */ +DiskImg::DiskImg(void) +{ + assert(Global::GetAppInitCalled()); + + fOuterFormat = kOuterFormatUnknown; + fFileFormat = kFileFormatUnknown; + fPhysical = kPhysicalFormatUnknown; + fpNibbleDescr = NULL; + fOrder = kSectorOrderUnknown; + fFormat = kFormatUnknown; + + fFileSysOrder = kSectorOrderUnknown; + fSectorPairing = false; + fSectorPairOffset = -1; + + fpOuterGFD = NULL; + fpWrapperGFD = NULL; + fpDataGFD = NULL; + fpOuterWrapper = NULL; + fpImageWrapper = NULL; + fpParentImg = NULL; + fDOSVolumeNum = kVolumeNumNotSet; + fOuterLength = -1; + fWrappedLength = -1; + fLength = -1; + fExpandable = false; + fReadOnly = true; + fDirty = false; + + fHasSectors = false; + fHasBlocks = false; + fHasNibbles = false; + + fNumTracks = -1; + fNumSectPerTrack = -1; + fNumBlocks = -1; + + fpScanProgressCallback = NULL; + fScanProgressCookie = NULL; + fScanCount = 0; + fScanMsg[0] = '\0'; + fScanLastMsgWhen = 0; + + /* + * Create a working copy of the nibble descr table. We want to leave + * open the possibility of applications editing or discarding entries, + * so we work off of a copy. + * + * Ideally we'd allow these to be set per-track, so that certain odd + * formats could be handled transparently (e.g. Muse tweaked DOS 3.2) + * for formatting as well as reading. + */ + assert(kStdNibbleDescrs[kNibbleDescrCustom].numSectors == 0); + assert(kNibbleDescrCustom == NELEM(kStdNibbleDescrs)-1); + fpNibbleDescrTable = new NibbleDescr[NELEM(kStdNibbleDescrs)]; + fNumNibbleDescrEntries = NELEM(kStdNibbleDescrs); + memcpy(fpNibbleDescrTable, kStdNibbleDescrs, sizeof(kStdNibbleDescrs)); + + fNibbleTrackBuf = NULL; + fNibbleTrackLoaded = -1; + + fNuFXCompressType = kNuThreadFormatLZW2; + + fNotes = NULL; + fpBadBlockMap = NULL; + fDiskFSRefCnt = 0; +} + +/* + * Throw away local storage. + */ +DiskImg::~DiskImg(void) +{ + if (fpDataGFD != NULL) { + LOGI("~DiskImg closing GenericFD(s)"); + } + (void) CloseImage(); + delete[] fpNibbleDescrTable; + delete[] fNibbleTrackBuf; + delete[] fNotes; + delete fpBadBlockMap; + + /* normally these will be closed, but perhaps not if something failed */ + if (fpOuterGFD != NULL) + delete fpOuterGFD; + if (fpWrapperGFD != NULL) + delete fpWrapperGFD; + if (fpDataGFD != NULL) + delete fpDataGFD; + if (fpOuterWrapper != NULL) + delete fpOuterWrapper; + if (fpImageWrapper != NULL) + delete fpImageWrapper; + + fDiskFSRefCnt = 100; // flag as freed +} + + +/* + * Set the nibble descr pointer. + */ +void DiskImg::SetNibbleDescr(int idx) +{ + assert(idx >= 0 && idx < kNibbleDescrMAX); + fpNibbleDescr = &fpNibbleDescrTable[idx]; +} + +/* + * Set up a custom nibble descriptor. + */ +void DiskImg::SetCustomNibbleDescr(const NibbleDescr* pDescr) +{ + if (pDescr == NULL) { + fpNibbleDescr = NULL; + } else { + assert(fpNibbleDescrTable != NULL); + //LOGI("Overwriting entry %d with new value (special=%d)", + // kNibbleDescrCustom, pDescr->special); + fpNibbleDescrTable[kNibbleDescrCustom] = *pDescr; + fpNibbleDescr = &fpNibbleDescrTable[kNibbleDescrCustom]; + } +} + +const char* A2File::GetRawFileName(size_t* size) const { // get unmodified file name + if (size) { + *size = strlen(GetFileName()); + } + return GetFileName(); +} + +/* + * Open a volume or a file on disk. + * + * For Windows, we need to handle logical/physical volumes specially. If + * the filename matches the appropriate pattern, use a different GFD. + */ +DIError DiskImg::OpenImage(const char* pathName, char fssep, bool readOnly) +{ + DIError dierr = kDIErrNone; + bool isWinDevice = false; + + if (fpDataGFD != NULL) { + LOGI(" DI already open!"); + return kDIErrAlreadyOpen; + } + LOGI(" DI OpenImage '%s' '%.1s' ro=%d", pathName, &fssep, readOnly); + + fReadOnly = readOnly; + +#ifdef _WIN32 + if ((fssep == '\0' || fssep == '\\') && + pathName[0] >= 'A' && pathName[0] <= 'Z' && + pathName[1] == ':' && pathName[2] == '\\' && + pathName[3] == '\0') + { + isWinDevice = true; // logical volume ("A:\") + } + if ((fssep == '\0' || fssep == '\\') && + isdigit(pathName[0]) && isdigit(pathName[1]) && + pathName[2] == ':' && pathName[3] == '\\' && + pathName[4] == '\0') + { + isWinDevice = true; // physical volume ("80:\") + } + if ((fssep == '\0' || fssep == '\\') && + strncmp(pathName, kASPIDev, strlen(kASPIDev)) == 0 && + pathName[strlen(pathName)-1] == '\\') + { + isWinDevice = true; // ASPI volume ("ASPI:x:y:z\") + } +#endif + + if (isWinDevice) { +#ifdef _WIN32 + GFDWinVolume* pGFDWinVolume = new GFDWinVolume; + + dierr = pGFDWinVolume->Open(pathName, fReadOnly); + if (dierr != kDIErrNone) { + delete pGFDWinVolume; + goto bail; + } + + fpWrapperGFD = pGFDWinVolume; + // Use a unique extension to skip some of the probing. + dierr = AnalyzeImageFile("CPDevice.cp-win-vol", '\0'); + if (dierr != kDIErrNone) + goto bail; +#endif + } else { + GFDFile* pGFDFile = new GFDFile; + + dierr = pGFDFile->Open(pathName, fReadOnly); + if (dierr != kDIErrNone) { + delete pGFDFile; + goto bail; + } + + //fImageFileName = new char[strlen(pathName) + 1]; + //strcpy(fImageFileName, pathName); + + fpWrapperGFD = pGFDFile; + pGFDFile = NULL; + + dierr = AnalyzeImageFile(pathName, fssep); + if (dierr != kDIErrNone) + goto bail; + } + + + assert(fpDataGFD != NULL); + +bail: + return dierr; +} + +DIError DiskImg::OpenImageFromBufferRO(const uint8_t* buffer, long length) { + return OpenImageFromBuffer(const_cast(buffer), length, true); +} + +DIError DiskImg::OpenImageFromBufferRW(uint8_t* buffer, long length) { + return OpenImageFromBuffer(buffer, length, false); +} + +/* + * Open from a buffer, which could point to unadorned ready-to-go content + * or to a preloaded image file. + */ +DIError DiskImg::OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly) +{ + if (fpDataGFD != NULL) { + LOGW(" DI already open!"); + return kDIErrAlreadyOpen; + } + LOGI(" DI OpenImage %08lx %ld ro=%d", (long) buffer, length, readOnly); + + DIError dierr; + GFDBuffer* pGFDBuffer; + + fReadOnly = readOnly; + pGFDBuffer = new GFDBuffer; + + dierr = pGFDBuffer->Open(buffer, length, false, false, readOnly); + if (dierr != kDIErrNone) { + delete pGFDBuffer; + return dierr; + } + + fpWrapperGFD = pGFDBuffer; + pGFDBuffer = NULL; + + dierr = AnalyzeImageFile("", '\0'); + if (dierr != kDIErrNone) + return dierr; + + assert(fpDataGFD != NULL); + return kDIErrNone; +} + +/* + * Open a range of blocks from an already-open disk image. This is only + * useful for things like UNIDOS volumes, which don't have an associated + * file in the image and are linear. + * + * The "read only" flag is inherited from the parent. + * + * For embedded images with visible file structure, we should be using + * an EmbeddedFD instead. [Note these were never implemented.] + * + * NOTE: there is an implicit ProDOS block ordering imposed on the parent + * image. It turns out that all of our current embedded parents use + * ProDOS-ordered blocks, so it works out okay, but the "linear" requirement + * above goes beyond just having contiguous blocks. + */ +DIError DiskImg::OpenImage(DiskImg* pParent, long firstBlock, long numBlocks) +{ + LOGI(" DI OpenImage parent=0x%08lx %ld %ld", (long) pParent, firstBlock, + numBlocks); + if (fpDataGFD != NULL) { + LOGI(" DW already open!"); + return kDIErrAlreadyOpen; + } + + if (pParent == NULL || firstBlock < 0 || numBlocks <= 0 || + firstBlock + numBlocks > pParent->GetNumBlocks()) + { + assert(false); + return kDIErrInvalidArg; + } + + fReadOnly = pParent->GetReadOnly(); // very important + + DIError dierr; + GFDGFD* pGFDGFD; + + pGFDGFD = new GFDGFD; + dierr = pGFDGFD->Open(pParent->fpDataGFD, firstBlock * kBlockSize, fReadOnly); + if (dierr != kDIErrNone) { + delete pGFDGFD; + return dierr; + } + + fpDataGFD = pGFDGFD; + assert(fpWrapperGFD == NULL); + + /* + * This replaces the call to "analyze image file" because we know we + * already have an open file with specific characteristics. + */ + //fOffset = pParent->fOffset + kBlockSize * firstBlock; + fLength = (di_off_t)numBlocks * kBlockSize; + fOuterLength = fWrappedLength = fLength; + fFileFormat = kFileFormatUnadorned; + fPhysical = pParent->fPhysical; + fOrder = pParent->fOrder; + + fpParentImg = pParent; + + return dierr; +} + +DIError DiskImg::OpenImage(DiskImg* pParent, long firstTrack, long firstSector, + long numSectors) +{ + LOGI(" DI OpenImage parent=0x%08lx %ld %ld %ld", (long) pParent, + firstTrack, firstSector, numSectors); + if (fpDataGFD != NULL) { + LOGW(" DI already open!"); + return kDIErrAlreadyOpen; + } + + if (pParent == NULL) + return kDIErrInvalidArg; + + int prntSectPerTrack = pParent->GetNumSectPerTrack(); + int lastTrack = firstTrack + + (numSectors + prntSectPerTrack-1) / prntSectPerTrack; + if (firstTrack < 0 || numSectors <= 0 || + lastTrack > pParent->GetNumTracks()) + { + return kDIErrInvalidArg; + } + + fReadOnly = pParent->GetReadOnly(); // very important + + DIError dierr; + GFDGFD* pGFDGFD; + + pGFDGFD = new GFDGFD; + dierr = pGFDGFD->Open(pParent->fpDataGFD, + kSectorSize * firstTrack * prntSectPerTrack, fReadOnly); + if (dierr != kDIErrNone) { + delete pGFDGFD; + return dierr; + } + + fpDataGFD = pGFDGFD; + assert(fpWrapperGFD == NULL); + + /* + * This replaces the call to "analyze image file" because we know we + * already have an open file with specific characteristics. + */ + assert(firstSector == 0); // else fOffset calculation breaks + //fOffset = pParent->fOffset + kSectorSize * firstTrack * prntSectPerTrack; + fLength = numSectors * kSectorSize; + fOuterLength = fWrappedLength = fLength; + fFileFormat = kFileFormatUnadorned; + fPhysical = pParent->fPhysical; + fOrder = pParent->fOrder; + + fpParentImg = pParent; + + return dierr; +} + + +/* + * Enable sector pairing. Useful for OzDOS. + */ +void DiskImg::SetPairedSectors(bool enable, int idx) +{ + fSectorPairing = enable; + fSectorPairOffset = idx; + + if (enable) { + assert(idx == 0 || idx == 1); + } +} + +/* + * Close the image, freeing resources. + * + * If we write to a child DiskImg, it's responsible for setting the "dirty" + * flag in its parent (and so on up the chain). That's necessary so that, + * when we close the file, changes made to a child DiskImg cause the parent + * to do any necessary recompression. + * + * [ This is getting called even when image creation failed with an error. + * This is probably the correct behavior, but we may want to be aborting the + * image creation instead of completing it. That's a higher-level decision + * though. ++ATM 20040506 ] + */ +DIError DiskImg::CloseImage(void) +{ + DIError dierr; + + LOGI("CloseImage 0x%p", this); + + /* check for DiskFS objects that still point to us */ + if (fDiskFSRefCnt != 0) { + LOGE("ERROR: CloseImage: fDiskFSRefCnt=%d", fDiskFSRefCnt); + assert(false); //DebugBreak(); + } + + /* + * Flush any changes. + */ + dierr = FlushImage(kFlushAll); + if (dierr != kDIErrNone) + return dierr; + + /* + * Clean up. Close GFD, OrigGFD, and OuterGFD. Delete ImageWrapper + * and OuterWrapper. + * + * In some cases we will have the file open more than once (e.g. a + * NuFX archive, which must be opened on disk). + */ + if (fpDataGFD != NULL) { + fpDataGFD->Close(); + delete fpDataGFD; + fpDataGFD = NULL; + } + if (fpWrapperGFD != NULL) { + fpWrapperGFD->Close(); + delete fpWrapperGFD; + fpWrapperGFD = NULL; + } + if (fpOuterGFD != NULL) { + fpOuterGFD->Close(); + delete fpOuterGFD; + fpOuterGFD = NULL; + } + delete fpImageWrapper; + fpImageWrapper = NULL; + delete fpOuterWrapper; + fpOuterWrapper = NULL; + + return dierr; +} + + +/* + * Flush data to disk. + * + * The only time this really needs to do anything on a disk image file is + * when we have compressed data (NuFX, DDD, .gz, .zip). The uncompressed + * wrappers either don't do anything ("unadorned") or just update some + * header fields (DiskCopy42). + * + * If "mode" is kFlushFastOnly, we only flush the formats that don't really + * need flushing. This is part of a scheme to keep the disk contents in a + * reasonable state on the off chance we crash with a modified file open. + * It also helps the user understand when changes are being made immediately + * vs. when they're written to memory and compressed later. We could just + * refuse to raise the "dirty" flag when modifying "simple" file formats, + * but that would change the meaning of the flag from "something has been + * changed" to "what's in the file and what's in memory differ". I want it + * to be a "dirty" flag. + */ +DIError DiskImg::FlushImage(FlushMode mode) +{ + DIError dierr = kDIErrNone; + + LOGI(" DI FlushImage (dirty=%d mode=%d)", fDirty, mode); + if (!fDirty) + return kDIErrNone; + if (fpDataGFD == NULL) { + /* + * This can happen if we tried to create a disk image but failed, e.g. + * couldn't create the output file because of access denied on the + * directory. There's no data, therefore nothing to flush, but the + * "dirty" flag is set because CreateImageCommon sets it almost + * immediately. + */ + LOGI(" (disk must've failed during creation)"); + fDirty = false; + return kDIErrNone; + } + + if (mode == kFlushFastOnly && + ((fpImageWrapper != NULL && !fpImageWrapper->HasFastFlush()) || + (fpOuterWrapper != NULL && !fpOuterWrapper->HasFastFlush()) )) + { + LOGI("DI fast flush requested, but one or both wrappers are slow"); + return kDIErrNone; + } + + /* + * Step 1: make sure any local caches have been flushed. + */ + /* (none) */ + + /* + * Step 2: push changes from fpDataGFD to fpWrapperGFD. This will + * cause ImageWrapper to rebuild itself (SHK, DDD, whatever). In + * some cases this amounts to copying the data on top of itself, + * which we can avoid easily. + * + * Embedded volumes don't have wrappers; when you write to an + * embedded volume, it passes straight through to the parent. + * + * (Note to self: formats like NuFX that write to a temp file and then + * rename over the old will close fpWrapperGFD and just access it + * directly. This is bad, because it doesn't allow them to have an + * "outer" format, but it's the way life is. The point is that it's + * okay for fpWrapperGFD to be non-NULL but represent a closed file, + * so long as the "Flush" function has it figured out.) + */ + if (fpWrapperGFD != NULL) { + LOGI(" DI flushing data changes to wrapper (fLen=%ld fWrapLen=%ld)", + (long) fLength, (long) fWrappedLength); + dierr = fpImageWrapper->Flush(fpWrapperGFD, fpDataGFD, fLength, + &fWrappedLength); + if (dierr != kDIErrNone) { + LOGI(" ERROR: wrapper flush failed (err=%d)", dierr); + return dierr; + } + /* flush the GFD in case it's a Win32 volume with block caching */ + dierr = fpWrapperGFD->Flush(); + } else { + assert(fpParentImg != NULL); + } + + /* + * Step 3: if we have an fpOuterGFD, rebuild the file with the data + * in fpWrapperGFD. + */ + if (fpOuterWrapper != NULL) { + LOGI(" DI saving wrapper to outer, fWrapLen=%ld", + (long) fWrappedLength); + assert(fpOuterGFD != NULL); + dierr = fpOuterWrapper->Save(fpOuterGFD, fpWrapperGFD, + fWrappedLength); + if (dierr != kDIErrNone) { + LOGI(" ERROR: outer save failed (err=%d)", dierr); + return dierr; + } + } + + fDirty = false; + return kDIErrNone; +} + + +/* + * Given the filename extension and a GFD, figure out what's inside. + * + * The filename extension should give us some idea what to expect: + * SHK, SDK, BXY - ShrinkIt compressed disk image + * GZ - gzip-compressed file (with something else inside) + * ZIP - ZIP archive with a single disk image inside + * DDD - DDD, DDD Pro, or DDD5.0 compressed image + * DSK - DiskCopy 4.2 or DO/PO + * DC - DiskCopy 4.2 (or 6?) + * DC6 - DiskCopy 6 (usually just raw sectors) + * DO, PO, D13, RAW? - DOS-order or ProDOS-order uncompressed + * IMG - Copy ][+ image (unadorned, physical sector order) + * HDV - virtual hard drive image + * NIB, RAW? - nibblized image + * (no extension) uncompressed + * cp-win-vol - our "magic" extension to indicate a Windows logical volume + * + * We can also examine the file length to see if it's a standard size + * (140K, 800K) and look for magic values in the header. + * + * If we can access the contents directly from disk, we do so. It's + * possibly more efficient to load the whole thing into memory, but if + * we have that much memory then the OS should cache it for us. (I have + * some 20MB disk images from my hard drive that shouldn't be loaded + * in their entirety. Certainly don't want to load a 512MB CFFA image.) + * + * On input, the following fields must be set: + * fpWrapperGFD - GenericFD for the file pointed to by "pathname" (or for a + * memory buffer if this is a sub-volume) + * + * On success, the following fields will be set: + * fWrappedLength, fOuterLength - set appropriately + * fpDataGFD - GFD for the raw data, possibly just a GFDGFD with an offset + * fLength - length of unadorned data in the file, or the length of + * data stored in fBuffer (test for fBuffer!=NULL) + * fFileFormat - set to the overall file format, mostly interesting + * for identification of the file "wrapper" + * fPhysicalFormat - set to the type of data this holds + * (maybe) fOrder - set when the file format or extension dictates, e.g. + * 2MG or *.po; not always reliable + * (maybe) fDOSVolumeNum - set to DOS volume number from wrapper + * + * This may set fReadOnly if one of the wrappers looks okay but is reporting + * a bad checksum. + */ +DIError DiskImg::AnalyzeImageFile(const char* pathName, char fssep) +{ + DIError dierr = kDIErrNone; + FileFormat probableFormat; + bool reliableExt; + const char* ext = FindExtension(pathName, fssep); + char* extBuf = NULL; // uses malloc/free + bool needExtFromOuter = false; + + if (ext != NULL) { + assert(*ext == '.'); + ext++; + } else + ext = ""; + + LOGI(" DI AnalyzeImageFile '%s' '%c' ext='%s'", + pathName, fssep, ext); + + /* sanity check: nobody should have configured these yet */ + assert(fOuterFormat == kOuterFormatUnknown); + assert(fFileFormat == kFileFormatUnknown); + assert(fOrder == kSectorOrderUnknown); + assert(fFormat == kFormatUnknown); + fLength = -1; + dierr = fpWrapperGFD->Seek(0, kSeekEnd); + if (dierr != kDIErrNone) { + LOGW(" DI Couldn't seek to end of wrapperGFD"); + goto bail; + } + fWrappedLength = fOuterLength = fpWrapperGFD->Tell(); + + /* quick test for zero-length files */ + if (fWrappedLength == 0) + return kDIErrUnrecognizedFileFmt; + + /* + * Start by checking for a zip/gzip "wrapper wrapper". We want to strip + * that away before we do anything else. Because web sites tend to + * gzip everything in sight whether it needs it or not, we treat this + * as a special case and assume that anything could be inside. + * + * Some cases are difficult to handle, e.g. ".SDK", since NufxLib + * doesn't let us open an archive that is sitting in memory. + * + * We could also handle disk images stored as ordinary files stored + * inside SHK. Not much point in handling multiple files down at + * this level though. + */ + if (strcasecmp(ext, "gz") == 0 && + OuterGzip::Test(fpWrapperGFD, fOuterLength) == kDIErrNone) + { + LOGI(" DI found gz outer wrapper"); + + fpOuterWrapper = new OuterGzip(); + if (fpOuterWrapper == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + fOuterFormat = kOuterFormatGzip; + + /* drop the ".gz" and get down to the next extension */ + ext = ""; + extBuf = strdup(pathName); + if (extBuf != NULL) { + char* localExt; + + localExt = (char*) FindExtension(extBuf, fssep); + if (localExt != NULL) + *localExt = '\0'; + localExt = (char*) FindExtension(extBuf, fssep); + if (localExt != NULL) { + ext = localExt; + assert(*ext == '.'); + ext++; + } + } + LOGI(" DI after gz, ext='%s'", ext == NULL ? "(NULL)" : ext); + + } else if (strcasecmp(ext, "zip") == 0) { + dierr = OuterZip::Test(fpWrapperGFD, fOuterLength); + if (dierr != kDIErrNone) + goto bail; + + LOGI(" DI found ZIP outer wrapper"); + + fpOuterWrapper = new OuterZip(); + if (fpOuterWrapper == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + fOuterFormat = kOuterFormatZip; + + needExtFromOuter = true; + + } else { + fOuterFormat = kOuterFormatNone; + } + + /* finish up outer wrapper stuff */ + if (fOuterFormat != kOuterFormatNone) { + GenericFD* pNewGFD = NULL; + dierr = fpOuterWrapper->Load(fpWrapperGFD, fOuterLength, fReadOnly, + &fWrappedLength, &pNewGFD); + if (dierr != kDIErrNone) { + LOGI(" DW outer prep failed"); + /* extensions are "reliable", so failure is unavoidable */ + goto bail; + } + + /* Load() sets this */ + if (fpOuterWrapper->IsDamaged()) { + AddNote(kNoteWarning, "The zip/gzip wrapper appears to be damaged."); + fReadOnly = true; + } + + /* shift GFDs */ + fpOuterGFD = fpWrapperGFD; + fpWrapperGFD = pNewGFD; + + if (needExtFromOuter) { + ext = fpOuterWrapper->GetExtension(); + if (ext == NULL) + ext = ""; + } + } + + /* + * Try to figure out what format the file is in. + * + * First pass, try only what the filename says it is. This way, if + * two file formats look alike, we have a good chance of getting it + * right. + * + * The "Test" functions have the complete file at their disposal. The + * file's length is stored in "fWrappedLength" for convenience. + */ + reliableExt = false; + probableFormat = kFileFormatUnknown; + if (strcasecmp(ext, "2mg") == 0 || strcasecmp(ext, "2img") == 0) { + reliableExt = true; + if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormat2MG; + } else if (strcasecmp(ext, "shk") == 0 || strcasecmp(ext, "sdk") == 0 || + strcasecmp(ext, "bxy") == 0) + { + DIError dierr2; + reliableExt = true; + dierr2 = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength); + if (dierr2 == kDIErrNone) + probableFormat = kFileFormatNuFX; + else if (dierr2 == kDIErrFileArchive) { + LOGI(" AnalyzeImageFile thinks it found a NuFX file archive"); + dierr = dierr2; + goto bail; + } + } else if (strcasecmp(ext, "hdv") == 0) { + /* usually just a "raw" disk, but check for Sim //e */ + if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormatSim2eHDV; + + /* ProDOS .hdv volumes can expand */ + fExpandable = true; + } else if (strcasecmp(ext, "dsk") == 0 || strcasecmp(ext, "dc") == 0) { + /* might be DiskCopy */ + if (WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormatDiskCopy42; + } else if (strcasecmp(ext, "ddd") == 0) { + /* do this after compressed formats but before unadorned */ + reliableExt = true; + if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormatDDD; + } else if (strcasecmp(ext, "app") == 0) { + reliableExt = true; + if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormatTrackStar; + } else if (strcasecmp(ext, "fdi") == 0) { + reliableExt = true; + if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + probableFormat = kFileFormatFDI; + } else if (strcasecmp(ext, "img") == 0) { + if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatSectors; + fOrder = kSectorOrderPhysical; + } + } else if (strcasecmp(ext, "nib") == 0 || strcasecmp(ext, "raw") == 0) { + if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatNib525_6656; + /* figure out NibbleFormat later */ + } + } else if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "po") == 0 || + strcasecmp(ext, "d13") == 0 || strcasecmp(ext, "dc6") == 0) + { + if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatSectors; + if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "d13") == 0) + fOrder = kSectorOrderDOS; + else + fOrder = kSectorOrderProDOS; // po, dc6 + LOGI(" DI guessing order is %d by extension", fOrder); + } + } else if (strcasecmp(ext, "cp-win-vol") == 0) { + /* this is a Windows logical volume */ + reliableExt = true; + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatSectors; + fOrder = kSectorOrderProDOS; + } else { + /* no match on the filename extension; start guessing */ + } + + if (probableFormat != kFileFormatUnknown) { + /* + * Found a match. Use "probableFormat" to open the file. + */ + LOGI(" DI scored hit on extension '%s'", ext); + } else { + /* + * Didn't work. If the file extension was marked "reliable", then + * either we have the wrong extension on the file, or the contents + * are damaged. + * + * If the extension isn't reliable, or simply absent, then we have + * to probe through the formats we know and just hope for the best. + * + * If the "test" function returns with a checksum failure, we take + * it to mean that the format was positively identified, but the + * data inside is corrupted. This results in an immediate return + * with the checksum failure noted. Only a few wrapper formats + * have checksums embedded. (The "test" functions should only + * be looking at header checksums.) + */ + if (reliableExt) { + LOGI(" DI file extension '%s' did not match contents", ext); + dierr = kDIErrBadFileFormat; + goto bail; + } else { + LOGI(" DI extension '%s' not useful, probing formats", ext); + dierr = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength); + if (dierr == kDIErrNone) { + probableFormat = kFileFormatNuFX; + goto gotit; + } else if (dierr == kDIErrFileArchive) + goto bail; // we know it's NuFX, we know we can't use it + else if (dierr == kDIErrBadChecksum) + goto bail; // right file type, bad data + + dierr = WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength); + if (dierr == kDIErrNone) { + probableFormat = kFileFormatDiskCopy42; + goto gotit; + } else if (dierr == kDIErrBadChecksum) + goto bail; // right file type, bad data + + if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { + probableFormat = kFileFormat2MG; + } else if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { + probableFormat = kFileFormatDDD; + } else if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatSim2eHDV; + } else if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatTrackStar; + } else if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) + { + probableFormat = kFileFormatFDI; + } else if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatNib525_6656; // placeholder + } else if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { + probableFormat = kFileFormatUnadorned; + fPhysical = kPhysicalFormatSectors; + } +gotit: ; + } + } + + /* + * Either we recognize it or we don't. Finish opening the file by + * setting up "fLength" and "fPhysical" values, extracting data + * into a memory buffer if necessary. fpDataGFD is set up by the + * "prep" function. + * + * If we're lucky, this will also configure "fOrder" for us, which is + * important when we can't recognize the filesystem format (for correct + * operation of disk tools). + */ + switch (probableFormat) { + case kFileFormat2MG: + fpImageWrapper = new Wrapper2MG(); + break; + case kFileFormatDiskCopy42: + fpImageWrapper = new WrapperDiskCopy42(); + break; + case kFileFormatSim2eHDV: + fpImageWrapper = new WrapperSim2eHDV(); + break; + case kFileFormatTrackStar: + fpImageWrapper = new WrapperTrackStar(); + break; + case kFileFormatFDI: + fpImageWrapper = new WrapperFDI(); + fReadOnly = true; // writing to FDI not yet supported + break; + case kFileFormatNuFX: + fpImageWrapper = new WrapperNuFX(); + ((WrapperNuFX*)fpImageWrapper)->SetCompressType( + (NuThreadFormat) fNuFXCompressType); + break; + case kFileFormatDDD: + fpImageWrapper = new WrapperDDD(); + break; + case kFileFormatUnadorned: + if (IsSectorFormat(fPhysical)) + fpImageWrapper = new WrapperUnadornedSector(); + else if (IsNibbleFormat(fPhysical)) + fpImageWrapper = new WrapperUnadornedNibble(); + else { + assert(false); + } + break; + default: + LOGI(" DI couldn't figure out the file format"); + dierr = kDIErrUnrecognizedFileFmt; + break; + } + if (fpImageWrapper != NULL) { + assert(fpDataGFD == NULL); + dierr = fpImageWrapper->Prep(fpWrapperGFD, fWrappedLength, fReadOnly, + &fLength, &fPhysical, &fOrder, &fDOSVolumeNum, + &fpBadBlockMap, &fpDataGFD); + } else { + /* could be a mem alloc failure that didn't set dierr */ + if (dierr == kDIErrNone) + dierr = kDIErrGeneric; + } + + if (dierr != kDIErrNone) { + LOGI(" DI wrapper prep failed (err=%d)", dierr); + goto bail; + } + + /* check for non-fatal checksum failures, e.g. DiskCopy42 */ + if (fpImageWrapper->IsDamaged()) { + AddNote(kNoteWarning, "File checksum didn't match."); + fReadOnly = true; + } + + fFileFormat = probableFormat; + + assert(fLength >= 0); + assert(fpDataGFD != NULL); + assert(fOuterFormat != kOuterFormatUnknown); + assert(fFileFormat != kFileFormatUnknown); + assert(fPhysical != kPhysicalFormatUnknown); + +bail: + free(extBuf); + return dierr; +} + + +/* + * Try to figure out what we're looking at. + * + * Returns an error if we don't think this is even a disk image. If we + * just can't figure it out, we return success but with the format value + * set to "unknown". This gives the caller a chance to use "override" + * to help us find our way. + * + * On entry: + * fpDataGFD, fLength, and fFileFormat are defined + * fSectorPairing is specified + * fOrder has a semi-reliable guess at sector ordering + * On exit: + * fOrder and fFormat are set to the best of our ability + * fNumTracks, fNumSectPerTrack, and fNumBlocks are set + * fHasSectors, fHasTracks, and fHasNibbles are set + * fFileSysOrder is set + * fpNibbleDescr will be set for nibble images + */ +DIError DiskImg::AnalyzeImage(void) +{ + assert(fLength >= 0); + assert(fpDataGFD != NULL); + assert(fFileFormat != kFileFormatUnknown); + assert(fPhysical != kPhysicalFormatUnknown); + assert(fFormat == kFormatUnknown); + assert(fFileSysOrder == kSectorOrderUnknown); + assert(fNumTracks == -1); + assert(fNumSectPerTrack == -1); + assert(fNumBlocks == -1); + if (fpDataGFD == NULL) + return kDIErrInternal; + + /* + * Figure out how many tracks and sectors the image has. + * + * For an odd-sized ProDOS image, there will be no tracks and sectors. + */ + if (IsSectorFormat(fPhysical)) { + if (!fLength) { + LOGI(" DI zero-length disk images not allowed"); + return kDIErrOddLength; + } + + if (fLength == kD13Length) { + /* 13-sector .d13 image */ + fHasSectors = true; + fNumSectPerTrack = 13; + fNumTracks = kTrackCount525; + assert(!fHasBlocks); + } else if (fLength % (16 * kSectorSize) == 0) { + /* looks like a collection of 16-sector tracks */ + fHasSectors = true; + + fNumSectPerTrack = 16; + fNumTracks = (int) (fLength / (fNumSectPerTrack * kSectorSize)); + + /* sector pairing effectively cuts #of tracks in half */ + if (fSectorPairing) { + if ((fNumTracks & 0x01) != 0) { + LOGI(" DI error: bad attempt at sector pairing"); + assert(false); + fSectorPairing = false; + } + } + + if (fSectorPairing) + fNumTracks /= 2; + } else { + if (fSectorPairing) { + LOGI("GLITCH: sector pairing enabled, but fLength=%ld", + (long) fLength); + return kDIErrOddLength; + } + + assert(fNumTracks == -1); + assert(fNumSectPerTrack == -1); + assert((fLength % kBlockSize) == 0); + + fHasBlocks = true; + fNumBlocks = (long) (fLength / kBlockSize); + } + } else if (IsNibbleFormat(fPhysical)) { + fHasNibbles = fHasSectors = true; + + /* + * Figure out if it's 13-sector or 16-sector (or garbage). We + * have to make an assessment of the entire disk so we can declare + * it to be 13-sector or 16-sector, which is useful for DiskFS + * which will want to scan for DOS VTOCs and other goodies. We + * also want to provide a default NibbleDescr. + * + * Failing that, we still allow it to be opened for raw track access. + * + * This also sets fNumTracks, which could be more than 35 if we're + * working with a TrackStar or FDI image. + */ + DIError dierr; + dierr = AnalyzeNibbleData(); // sets nibbleDescr and DOS vol num + if (dierr == kDIErrNone) { + assert(fpNibbleDescr != NULL); + fNumSectPerTrack = fpNibbleDescr->numSectors; + fOrder = kSectorOrderPhysical; + + if (!fReadOnly && !fpNibbleDescr->dataVerifyChecksum) { + LOGI("DI nibbleDescr does not verify data checksum, disabling writes"); + AddNote(kNoteInfo, + "Sectors use non-standard data checksums; writing disabled."); + fReadOnly = true; + } + } else { + //assert(fpNibbleDescr == NULL); + fNumSectPerTrack = -1; + fOrder = kSectorOrderPhysical; + fHasSectors = false; + } + } else { + LOGI("Unsupported physical %d", fPhysical); + assert(false); + return kDIErrGeneric; + } + + /* + * Compute the number of blocks. For a 13-sector disk, block access + * is not possible. + * + * For nibble formats, we have to base the block count on the number + * of sectors rather than the file length. + */ + if (fHasSectors) { + assert(fNumSectPerTrack > 0); + if ((fNumSectPerTrack & 0x01) == 0) { + /* not a 13-sector disk, so define blocks in terms of sectors */ + /* (effects of sector pairing are already taken into account) */ + fHasBlocks = true; + fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2; + } + } else if (fHasBlocks) { + if ((fLength % kBlockSize) == 0) { + /* not sector-oriented, so define blocks based on length */ + fHasBlocks = true; + fNumBlocks = (long) (fLength / kBlockSize); + + if (fSectorPairing) { + if ((fNumBlocks & 0x01) != 0) { + LOGI(" DI error: bad attempt at sector pairing (blk)"); + assert(false); + fSectorPairing = false; + } else + fNumBlocks /= 2; + } + + } else { + assert(false); + return kDIErrGeneric; + } + } else if (fHasNibbles) { + assert(fNumBlocks == -1); + } else { + LOGI(" DI none of fHasSectors/fHasBlocks/fHasNibbles are set"); + assert(false); + return kDIErrInternal; + } + + /* + * We've got the track/sector/block layout sorted out; now figure out + * what kind of filesystem we're dealing with. + */ + AnalyzeImageFS(); + + LOGI(" DI AnalyzeImage tracks=%ld sectors=%d blocks=%ld fileSysOrder=%d", + fNumTracks, fNumSectPerTrack, fNumBlocks, fFileSysOrder); + LOGI(" hasBlocks=%d hasSectors=%d hasNibbles=%d", + fHasBlocks, fHasSectors, fHasNibbles); + + return kDIErrNone; +} + +/* + * Try to figure out what filesystem exists on this disk image. + * + * We want to test for DOS before ProDOS, because sometimes they overlap (e.g. + * 800K ProDOS disk with five 160K DOS volumes on it). + * + * Sets fFormat, fOrder, and fFileSysOrder. + */ +void DiskImg::AnalyzeImageFS(void) +{ + /* + * In some circumstances it would be useful to have a set describing + * what filesystems we might expect to find, e.g. we're not likely to + * encounter RDOS embedded in a CF card. + */ + if (DiskFSMacPart::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatMacPart); + LOGI(" DI found MacPart, order=%d", fOrder); + } else if (DiskFSMicroDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatMicroDrive); + LOGI(" DI found MicroDrive, order=%d", fOrder); + } else if (DiskFSFocusDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatFocusDrive); + LOGI(" DI found FocusDrive, order=%d", fOrder); + } else if (DiskFSCFFA::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + // The CFFA format doesn't have a partition map, but we do insist + // on finding multiple volumes. It needs to come after MicroDrive, + // because a disk formatted for CFFA then subsequently partitioned + // for MicroDrive will still look like valid CFFA unless you zero + // out the blocks. + assert(fFormat == kFormatCFFA4 || fFormat == kFormatCFFA8); + LOGI(" DI found CFFA, order=%d", fOrder); + } else if (DiskFSFAT::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + // This is really just a trap to catch CFFA cards that were formatted + // for ProDOS and then re-formatted for MSDOS. As such it needs to + // come before the ProDOS test. It only works on larger volumes, + // and can be overridden, so it's pretty safe. + assert(fFormat == kFormatMSDOS); + LOGI(" DI found MSDOS, order=%d", fOrder); + } else if (DiskFSDOS33::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatDOS32 || fFormat == kFormatDOS33); + LOGI(" DI found DOS3.x, order=%d", fOrder); + if (fNumSectPerTrack == 13) + fFormat = kFormatDOS32; + } else if (DiskFSUNIDOS::TestWideFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + // Should only succeed on 400K embedded chunks. + assert(fFormat == kFormatDOS33); + fNumSectPerTrack = 32; + fNumTracks /= 2; + LOGI(" DI found 'wide' DOS3.3, order=%d", fOrder); + } else if (DiskFSUNIDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatUNIDOS); + fNumSectPerTrack = 32; + fNumTracks /= 2; + LOGI(" DI found UNIDOS, order=%d", fOrder); + } else if (DiskFSOzDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatOzDOS); + fNumSectPerTrack = 32; + fNumTracks /= 2; + LOGI(" DI found OzDOS, order=%d", fOrder); + } else if (DiskFSProDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatProDOS); + LOGI(" DI found ProDOS, order=%d", fOrder); + } else if (DiskFSPascal::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatPascal); + LOGI(" DI found Pascal, order=%d", fOrder); + } else if (DiskFSCPM::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatCPM); + LOGI(" DI found CP/M, order=%d", fOrder); + } else if (DiskFSRDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatRDOS33 || + fFormat == kFormatRDOS32 || + fFormat == kFormatRDOS3); + LOGI(" DI found RDOS 3.3, order=%d", fOrder); + } else if (DiskFSHFS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatMacHFS); + LOGI(" DI found HFS, order=%d", fOrder); + } else if (DiskFSGutenberg::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) + { + assert(fFormat == kFormatGutenberg); + LOGI(" DI found Gutenberg, order=%d", fOrder); + } else { + fFormat = kFormatUnknown; + LOGI(" DI no recognizeable filesystem found (fOrder=%d)", + fOrder); + } + + fFileSysOrder = CalcFSSectorOrder(); +} + + +/* + * Override the format determined by the analyzer. + * + * If they insist on the presence of a valid filesystem, check to make sure + * that filesystem actually exists. + * + * Note that this does not allow overriding the file structure, which must + * be clearly identifiable to be at all useful. If the file has no "wrapper" + * structure, the "unadorned" format should be specified, and the contents + * identified by the PhysicalFormat. + */ +DIError DiskImg::OverrideFormat(PhysicalFormat physical, FSFormat format, + SectorOrder order) +{ + DIError dierr = kDIErrNone; + SectorOrder newOrder; + FSFormat newFormat; + + LOGI(" DI override: physical=%d format=%d order=%d", + physical, format, order); + + if (!IsSectorFormat(physical) && !IsNibbleFormat(physical)) + return kDIErrUnsupportedPhysicalFmt; + + /* don't allow forcing physical format change */ + if (physical != fPhysical) + return kDIErrInvalidArg; + + /* optimization */ + if (physical == fPhysical && format == fFormat && order == fOrder) { + LOGI(" DI override matches existing, ignoring"); + return kDIErrNone; + } + + newOrder = order; + newFormat = format; + + switch (format) { + case kFormatDOS33: + case kFormatDOS32: + dierr = DiskFSDOS33::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + // Go ahead and allow the override even if the DOS version is wrong. + // So long as the sector count is correct, it's okay. + break; + case kFormatProDOS: + dierr = DiskFSProDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatPascal: + dierr = DiskFSPascal::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatMacHFS: + dierr = DiskFSHFS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatUNIDOS: + dierr = DiskFSUNIDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatOzDOS: + dierr = DiskFSOzDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatCFFA4: + case kFormatCFFA8: + dierr = DiskFSCFFA::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + // So long as it's CFFA, we allow the user to force it to be 4-mode + // or 8-mode. Don't require newFormat==format. + break; + case kFormatMacPart: + dierr = DiskFSMacPart::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatMicroDrive: + dierr = DiskFSMicroDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatFocusDrive: + dierr = DiskFSFocusDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatCPM: + dierr = DiskFSCPM::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatMSDOS: + dierr = DiskFSFAT::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + break; + case kFormatRDOS33: + case kFormatRDOS32: + case kFormatRDOS3: + dierr = DiskFSRDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); + if (newFormat != format) + dierr = kDIErrFilesystemNotFound; // found RDOS, but wrong flavor + break; + case kFormatGenericPhysicalOrd: + case kFormatGenericProDOSOrd: + case kFormatGenericDOSOrd: + case kFormatGenericCPMOrd: + /* no discussion possible, since there's no FS to validate */ + newFormat = format; + newOrder = order; + break; + case kFormatUnknown: + /* only valid in rare situations, e.g. CFFA CreatePlaceholder */ + newFormat = format; + newOrder = order; + break; + default: + dierr = kDIErrUnsupportedFSFmt; + break; + } + + if (dierr != kDIErrNone) { + LOGI(" DI override failed"); + goto bail; + } + + /* + * We passed in "order" to TestFS. If it came back with something + * different, it means that it didn't like the new order value even + * when "leniency" was granted. + */ + if (newOrder != order) { + dierr = kDIErrBadOrdering; + goto bail; + } + + fFormat = format; + fOrder = newOrder; + fFileSysOrder = CalcFSSectorOrder(); + + LOGI(" DI override accepted"); + +bail: + return dierr; +} + +/* + * Figure out the sector ordering for this filesystem, so we can decide + * how the sectors need to be re-arranged when we're reading them. + * + * If the value returned by this function matches fOrder, then no swapping + * will be done. + * + * NOTE: this table is redundant with some knowledge embedded in the + * individual "TestFS" functions. + */ +DiskImg::SectorOrder DiskImg::CalcFSSectorOrder(void) const +{ + /* in the absence of information, just leave it alone */ + if (fFormat == kFormatUnknown || fOrder == kSectorOrderUnknown) { + LOGI(" DI WARNING: FindSectorOrder but format not known"); + return fOrder; + } + + assert(fOrder == kSectorOrderPhysical || fOrder == kSectorOrderCPM || + fOrder == kSectorOrderProDOS || fOrder == kSectorOrderDOS); + + switch (fFormat) { + case kFormatGenericPhysicalOrd: + case kFormatRDOS32: + case kFormatRDOS3: + return kSectorOrderPhysical; + + case kFormatGenericDOSOrd: + case kFormatDOS33: + case kFormatDOS32: + case kFormatUNIDOS: + case kFormatOzDOS: + case kFormatGutenberg: + return kSectorOrderDOS; + + case kFormatGenericCPMOrd: + case kFormatCPM: + return kSectorOrderCPM; + + case kFormatGenericProDOSOrd: + case kFormatProDOS: + case kFormatRDOS33: + case kFormatPascal: + case kFormatMacHFS: + case kFormatMacMFS: + case kFormatLisa: + case kFormatMSDOS: + case kFormatISO9660: + case kFormatCFFA4: + case kFormatCFFA8: + case kFormatMacPart: + case kFormatMicroDrive: + case kFormatFocusDrive: + return kSectorOrderProDOS; + + default: + assert(false); + return fOrder; + } +} + +/* + * Based on the disk format, figure out if we should prefer blocks or + * sectors when examining disk contents. + */ +bool DiskImg::ShowAsBlocks(void) const +{ + if (!fHasBlocks) + return false; + + /* in the absence of information, assume sectors */ + if (fFormat == kFormatUnknown) { + if (fOrder == kSectorOrderProDOS) + return true; + else + return false; + } + + switch (fFormat) { + case kFormatGenericPhysicalOrd: + case kFormatGenericDOSOrd: + case kFormatDOS33: + case kFormatDOS32: + case kFormatRDOS3: + case kFormatRDOS33: + case kFormatUNIDOS: + case kFormatOzDOS: + case kFormatGutenberg: + return false; + + case kFormatGenericProDOSOrd: + case kFormatGenericCPMOrd: + case kFormatProDOS: + case kFormatPascal: + case kFormatMacHFS: + case kFormatMacMFS: + case kFormatLisa: + case kFormatCPM: + case kFormatMSDOS: + case kFormatISO9660: + case kFormatCFFA4: + case kFormatCFFA8: + case kFormatMacPart: + case kFormatMicroDrive: + case kFormatFocusDrive: + return true; + + default: + assert(false); + return false; + } +} + + +/* + * Format an image with the requested fileystem format. This only works if + * the matching DiskFS supports formatting of disks. + */ +DIError DiskImg::FormatImage(FSFormat format, const char* volName) +{ + DIError dierr = kDIErrNone; + DiskFS* pDiskFS = NULL; + FSFormat savedFormat; + + LOGI(" DI FormatImage '%s'", volName); + + /* + * Open a temporary DiskFS for the requested format. We do this via the + * standard OpenAppropriate call, so we temporarily switch our format + * out. (We will eventually replace it, but we want to make sure that + * local error handling works correctly, so we restore it for now.) + */ + savedFormat = fFormat; + fFormat = format; + pDiskFS = OpenAppropriateDiskFS(false); + fFormat = savedFormat; + + if (pDiskFS == NULL) { + dierr = kDIErrUnsupportedFSFmt; + goto bail; + } + + dierr = pDiskFS->Format(this, volName); + if (dierr != kDIErrNone) + goto bail; + + LOGI("DI format successful"); + fFormat = format; + +bail: + delete pDiskFS; + return dierr; +} + +/* + * Clear an image to zeros, usually done as a prelude to a higher-level format. + * + * BUG: this should also handle the track/sector case. + * + * HEY: this is awfully slow on large disks... should have some sort of + * optimized path that just writes to the GFD or something. Maybe even just + * a "ZeroBlock" instead of "WriteBlock" so we can memset instead of memcpy? + */ +DIError DiskImg::ZeroImage(void) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlockSize]; + long block; + + LOGI(" DI ZeroImage (%ld blocks)", GetNumBlocks()); + memset(blkBuf, 0, sizeof(blkBuf)); + + for (block = 0; block < GetNumBlocks(); block++) { + dierr = WriteBlock(block, blkBuf); + if (dierr != kDIErrNone) + break; + } + + return dierr; +} + + +/* + * Set the "scan progress" function. + * + * We want to use the same function for our sub-volumes too. + */ +void DiskImg::SetScanProgressCallback(ScanProgressCallback func, void* cookie) +{ + if (fpParentImg != NULL) { + /* unexpected, but perfectly okay */ + DebugBreak(); + } + + fpScanProgressCallback = func; + fScanProgressCookie = cookie; + fScanCount = 0; + fScanMsg[0] = '\0'; + fScanLastMsgWhen = time(NULL); +} + +/* + * Update the progress. Call with a string at the start of a volume, then + * call with a NULL pointer every time we add a file. + */ +bool DiskImg::UpdateScanProgress(const char* newStr) +{ + ScanProgressCallback func = fpScanProgressCallback; + DiskImg* pImg = this; + bool result = true; + + /* search up the tree to find a progress updater */ + while (func == NULL) { + pImg = pImg->fpParentImg; + if (pImg == NULL) + return result; // none defined, bail out + func = pImg->fpScanProgressCallback; + } + + time_t now = time(NULL); + + if (newStr == NULL) { + fScanCount++; + //if ((fScanCount % 100) == 0) + if (fScanLastMsgWhen != now) { + result = (*func)(fScanProgressCookie, + fScanMsg, fScanCount); + fScanLastMsgWhen = now; + } + } else { + fScanCount = 0; + strncpy(fScanMsg, newStr, sizeof(fScanMsg)); + fScanMsg[sizeof(fScanMsg)-1] = '\0'; + result = (*func)(fScanProgressCookie, fScanMsg, + fScanCount); + fScanLastMsgWhen = now; + } + + return result; +} + + +/* + * ========================================================================== + * Block/track/sector I/O + * ========================================================================== + */ + +/* + * Handle sector order conversions. + */ +DIError DiskImg::CalcSectorAndOffset(long track, int sector, SectorOrder imageOrder, + SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector) +{ + if (!fHasSectors) + return kDIErrUnsupportedAccess; + + /* + * Sector order conversions. No table is needed for Copy ][+ format, + * which is equivalent to "physical". + */ + static const int raw2dos[16] = { + 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 + }; + static const int dos2raw[16] = { + 0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15 + }; + static const int raw2prodos[16] = { + 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 + }; + static const int prodos2raw[16] = { + 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15 + }; + static const int raw2cpm[16] = { + 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5 + }; + static const int cpm2raw[16] = { + 0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, 1, 4, 7, 10, 13 + }; + + if (track < 0 || track >= fNumTracks) { + LOGI(" DI read invalid track %ld", track); + return kDIErrInvalidTrack; + } + if (sector < 0 || sector >= fNumSectPerTrack) { + LOGI(" DI read invalid sector %d", sector); + return kDIErrInvalidSector; + } + + di_off_t offset; + int newSector = -1; + + /* + * 16-sector disks write sectors in ascending order and then remap + * them with a translation table. + */ + if (fNumSectPerTrack == 16 || fNumSectPerTrack == 32) { + if (fSectorPairing) { + assert(fSectorPairOffset == 0 || fSectorPairOffset == 1); + // this pushes "track" beyond fNumTracks + track *= 2; + if (sector >= 16) { + track++; + sector -= 16; + } + offset = track * fNumSectPerTrack * kSectorSize; + + sector = sector * 2 + fSectorPairOffset; + if (sector >= 16) { + offset += 16*kSectorSize; + sector -= 16; + } + } else { + offset = track * fNumSectPerTrack * kSectorSize; + if (sector >= 16) { + offset += 16*kSectorSize; + sector -= 16; + } + } + assert(sector >= 0 && sector < 16); + + /* convert request to "raw" sector number */ + switch (fsOrder) { + case kSectorOrderProDOS: + newSector = prodos2raw[sector]; + break; + case kSectorOrderDOS: + newSector = dos2raw[sector]; + break; + case kSectorOrderCPM: + newSector = cpm2raw[sector]; + break; + case kSectorOrderPhysical: // used for Copy ][+ + newSector = sector; + break; + case kSectorOrderUnknown: + // should never happen; fall through to "default" + default: + assert(false); + newSector = sector; + break; + } + + /* convert "raw" request to the image's ordering */ + switch (imageOrder) { + case kSectorOrderProDOS: + newSector = raw2prodos[newSector]; + break; + case kSectorOrderDOS: + newSector = raw2dos[newSector]; + break; + case kSectorOrderCPM: + newSector = raw2cpm[newSector]; + break; + case kSectorOrderPhysical: + //newSector = newSector; + break; + case kSectorOrderUnknown: + // should never happen; fall through to "default" + default: + assert(false); + //newSector = newSector; + break; + } + + if (imageOrder == fsOrder) { + assert(sector == newSector); + } + + offset += newSector * kSectorSize; + } else if (fNumSectPerTrack == 13) { + /* sector skew has no meaning, so assume no translation */ + offset = track * fNumSectPerTrack * kSectorSize; + newSector = sector; + offset += newSector * kSectorSize; + if (imageOrder != fsOrder) { + /* translation expected */ + LOGI("NOTE: CalcSectorAndOffset for nspt=13 with img=%d fs=%d", + imageOrder, fsOrder); + } + } else { + assert(false); // should not be here + + /* try to do something reasonable */ + assert(imageOrder == fsOrder); + offset = (di_off_t)track * fNumSectPerTrack * kSectorSize; + offset += sector * kSectorSize; + } + + *pOffset = offset; + *pNewSector = newSector; + return kDIErrNone; +} + +/* + * Determine whether an image uses a linear mapping. This allows us to + * optimize block reads & writes, very useful when dealing with logical + * volumes under Windows (which also use 512-byte blocks). + * + * The "imageOrder" argument usually comes from fOrder, and "fsOrder" + * comes from "fFileSysOrder". + */ +inline bool DiskImg::IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder) +{ + /* + * Any time fOrder==fFileSysOrder, we know that we have a linear + * mapping. This holds true for reading ProDOS blocks from a ".po" + * file or reading DOS sectors from a ".do" file. + */ + return (IsSectorFormat(fPhysical) && fHasBlocks && + imageOrder == fsOrder); +} + +/* + * Read the specified track and sector, adjusting for sector ordering as + * appropriate. + * + * Copies 256 bytes into "*buf". + * + * Returns 0 on success, nonzero on failure. + */ +DIError DiskImg::ReadTrackSectorSwapped(long track, int sector, void* buf, + SectorOrder imageOrder, SectorOrder fsOrder) +{ + DIError dierr; + di_off_t offset; + int newSector = -1; + + if (buf == NULL) + return kDIErrInvalidArg; + +#if 0 // Pre-d13 + if (fNumSectPerTrack == 13) { + /* no sector skewing possible for 13-sector disks */ + assert(fHasNibbles); + + return ReadNibbleSector(track, sector, buf, fpNibbleDescr); + } +#endif + + dierr = CalcSectorAndOffset(track, sector, imageOrder, fsOrder, + &offset, &newSector); + if (dierr != kDIErrNone) + return dierr; + + if (IsSectorFormat(fPhysical)) { + assert(offset+kSectorSize <= fLength); + + //LOGI(" DI t=%d s=%d", track, + // (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize); + + dierr = CopyBytesOut(buf, offset, kSectorSize); + } else if (IsNibbleFormat(fPhysical)) { + if (imageOrder != kSectorOrderPhysical) { + LOGI(" NOTE: nibble imageOrder is %d (expected %d)", + imageOrder, kSectorOrderPhysical); + } + dierr = ReadNibbleSector(track, newSector, buf, fpNibbleDescr); + } else { + assert(false); + dierr = kDIErrInternal; + } + + return dierr; +} + +/* + * Write the specified track and sector, adjusting for sector ordering as + * appropriate. + * + * Copies 256 bytes out of "buf". + * + * Returns 0 on success, nonzero on failure. + */ +DIError DiskImg::WriteTrackSector(long track, int sector, const void* buf) +{ + DIError dierr; + di_off_t offset; + int newSector = -1; + + if (buf == NULL) + return kDIErrInvalidArg; + if (fReadOnly) + return kDIErrAccessDenied; + +#if 0 // Pre-d13 + if (fNumSectPerTrack == 13) { + /* no sector skewing possible for 13-sector disks */ + assert(fHasNibbles); + + return WriteNibbleSector(track, sector, buf, fpNibbleDescr); + } +#endif + + dierr = CalcSectorAndOffset(track, sector, fOrder, fFileSysOrder, + &offset, &newSector); + if (dierr != kDIErrNone) + return dierr; + + if (IsSectorFormat(fPhysical)) { + assert(offset+kSectorSize <= fLength); + + //LOGI(" DI t=%d s=%d", track, + // (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize); + + dierr = CopyBytesIn(buf, offset, kSectorSize); + } else if (IsNibbleFormat(fPhysical)) { + if (fOrder != kSectorOrderPhysical) { + LOGI(" NOTE: nibble fOrder is %d (expected %d)", + fOrder, kSectorOrderPhysical); + } + dierr = WriteNibbleSector(track, newSector, buf, fpNibbleDescr); + } else { + assert(false); + dierr = kDIErrInternal; + } + + return dierr; +} + +/* + * Read a 512-byte block. + * + * Copies 512 bytes into "*buf". + */ +DIError DiskImg::ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder, + SectorOrder fsOrder) +{ + if (!fHasBlocks) + return kDIErrUnsupportedAccess; + if (block < 0 || block >= fNumBlocks) + return kDIErrInvalidBlock; + if (buf == NULL) + return kDIErrInvalidArg; + + DIError dierr; + long track, blkInTrk; + + /* if we have a bad block map, check it */ + if (CheckForBadBlocks(block, 1)) { + dierr = kDIErrReadFailed; + goto bail; + } + + if (fHasSectors && !IsLinearBlocks(imageOrder, fsOrder)) { + /* run it through the t/s call so we handle DOS ordering */ + track = block / (fNumSectPerTrack/2); + blkInTrk = block - (track * (fNumSectPerTrack/2)); + dierr = ReadTrackSectorSwapped(track, blkInTrk*2, buf, + imageOrder, fsOrder); + if (dierr != kDIErrNone) + return dierr; + dierr = ReadTrackSectorSwapped(track, blkInTrk*2+1, + (char*)buf+kSectorSize, imageOrder, fsOrder); + } else if (fHasBlocks) { + /* no sectors, so no swapping; must be linear blocks */ + if (imageOrder != fsOrder) { + LOGI(" DI NOTE: ReadBlockSwapped on non-sector (%d/%d)", + imageOrder, fsOrder); + } + dierr = CopyBytesOut(buf, (di_off_t) block * kBlockSize, kBlockSize); + } else { + assert(false); + dierr = kDIErrInternal; + } + +bail: + return dierr; +} + +/* + * Read multiple blocks. + * + * IMPORTANT: this returns immediately when a read fails. The buffer will + * probably not contain data from all readable sectors. The application is + * expected to retry the blocks individually. + */ +DIError DiskImg::ReadBlocks(long startBlock, int numBlocks, void* buf) +{ + DIError dierr = kDIErrNone; + + assert(fHasBlocks); + assert(startBlock >= 0); + assert(numBlocks > 0); + assert(buf != NULL); + + if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) { + assert(false); + return kDIErrInvalidArg; + } + + /* if we have a bad block map, check it */ + if (CheckForBadBlocks(startBlock, numBlocks)) { + dierr = kDIErrReadFailed; + goto bail; + } + + if (!IsLinearBlocks(fOrder, fFileSysOrder)) { + /* + * This isn't a collection of linear blocks, so we need to read it one + * block at a time with sector swapping. This almost certainly means + * that we're not reading from physical media, so performance shouldn't + * be an issue. + */ + if (startBlock == 0) { + LOGI(" ReadBlocks: nonlinear, not trying"); + } + while (numBlocks--) { + dierr = ReadBlock(startBlock, buf); + if (dierr != kDIErrNone) + goto bail; + startBlock++; + buf = (uint8_t*)buf + kBlockSize; + } + } else { + if (startBlock == 0) { + LOGI(" ReadBlocks: doing big linear reads"); + } + dierr = CopyBytesOut(buf, + (di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize); + } + +bail: + return dierr; +} + +/* + * Check to see if any blocks in a range of blocks show up in the bad + * block map. This is primarily useful for 3.5" disk images converted + * from nibble images, because we convert them directly to "cooked" + * 512-byte blocks. + * + * Returns "true" if we found bad blocks, "false" if not. + */ +bool DiskImg::CheckForBadBlocks(long startBlock, int numBlocks) +{ + int i; + + if (fpBadBlockMap == NULL) + return false; + + for (i = startBlock; i < startBlock+numBlocks; i++) { + if (fpBadBlockMap->IsSet(i)) + return true; + } + return false; +} + +/* + * Write a block of data to a DiskImg. + * + * Returns immediately when a block write fails. Does not try to write all + * blocks before returning failure. + */ +DIError DiskImg::WriteBlock(long block, const void* buf) +{ + if (!fHasBlocks) + return kDIErrUnsupportedAccess; + if (block < 0 || block >= fNumBlocks) + return kDIErrInvalidBlock; + if (buf == NULL) + return kDIErrInvalidArg; + if (fReadOnly) + return kDIErrAccessDenied; + + DIError dierr; + long track, blkInTrk; + + if (fHasSectors && !IsLinearBlocks(fOrder, fFileSysOrder)) { + /* run it through the t/s call so we handle DOS ordering */ + track = block / (fNumSectPerTrack/2); + blkInTrk = block - (track * (fNumSectPerTrack/2)); + dierr = WriteTrackSector(track, blkInTrk*2, buf); + if (dierr != kDIErrNone) + return dierr; + dierr = WriteTrackSector(track, blkInTrk*2+1, (char*)buf+kSectorSize); + } else if (fHasBlocks) { + /* no sectors, so no swapping; must be linear blocks */ + if (fOrder != fFileSysOrder) { + LOGI(" DI NOTE: WriteBlock on non-sector (%d/%d)", + fOrder, fFileSysOrder); + } + dierr = CopyBytesIn(buf, (di_off_t)block * kBlockSize, kBlockSize); + } else { + assert(false); + dierr = kDIErrInternal; + } + return dierr; +} + +/* + * Write multiple blocks. + */ +DIError DiskImg::WriteBlocks(long startBlock, int numBlocks, const void* buf) +{ + DIError dierr = kDIErrNone; + + assert(fHasBlocks); + assert(startBlock >= 0); + assert(numBlocks > 0); + assert(buf != NULL); + + if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) { + assert(false); + return kDIErrInvalidArg; + } + + if (!IsLinearBlocks(fOrder, fFileSysOrder)) { + /* + * This isn't a collection of linear blocks, so we need to write it + * one block at a time with sector swapping. This almost certainly + * means that we're not reading from physical media, so performance + * shouldn't be an issue. + */ + if (startBlock == 0) { + LOGI(" WriteBlocks: nonlinear, not trying"); + } + while (numBlocks--) { + dierr = WriteBlock(startBlock, buf); + if (dierr != kDIErrNone) + goto bail; + startBlock++; + buf = (uint8_t*)buf + kBlockSize; + } + } else { + if (startBlock == 0) { + LOGI(" WriteBlocks: doing big linear writes"); + } + dierr = CopyBytesIn(buf, + (di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize); + } + +bail: + return dierr; +} + + +/* + * Copy a chunk of bytes out of the disk image. + * + * (This is the lowest-level read routine in this class.) + */ +DIError DiskImg::CopyBytesOut(void* buf, di_off_t offset, int size) const +{ + DIError dierr; + + dierr = fpDataGFD->Seek(offset, kSeekSet); + if (dierr != kDIErrNone) { + LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr); + return dierr; + } + + dierr = fpDataGFD->Read(buf, size); + if (dierr != kDIErrNone) { + LOGI(" DI read off=%ld size=%d failed (err=%d)", + (long) offset, size, dierr); + return dierr; + } + + return kDIErrNone; +} + +/* + * Copy a chunk of bytes into the disk image. + * + * Sets the "dirty" flag. + * + * (This is the lowest-level write routine in DiskImg.) + */ +DIError DiskImg::CopyBytesIn(const void* buf, di_off_t offset, int size) +{ + DIError dierr; + + if (fReadOnly) { + DebugBreak(); + return kDIErrAccessDenied; + } + assert(fpDataGFD != NULL); // somebody closed the image? + + dierr = fpDataGFD->Seek(offset, kSeekSet); + if (dierr != kDIErrNone) { + LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr); + return dierr; + } + + dierr = fpDataGFD->Write(buf, size); + if (dierr != kDIErrNone) { + LOGI(" DI write off=%ld size=%d failed (err=%d)", + (long) offset, size, dierr); + return dierr; + } + + /* set the dirty flag here and everywhere above */ + DiskImg* pImg = this; + while (pImg != NULL) { + pImg->fDirty = true; + pImg = pImg->fpParentImg; + } + + return kDIErrNone; +} + + +/* + * =========================================================================== + * Image creation + * =========================================================================== + */ + +/* + * Create a disk image with the specified parameters. + * + * "storageName" and "pNibbleDescr" may be NULL. + */ +DIError DiskImg::CreateImage(const char* pathName, const char* storageName, + OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical, + const NibbleDescr* pNibbleDescr, SectorOrder order, + FSFormat format, long numBlocks, bool skipFormat) +{ + assert(fpDataGFD == NULL); // should not be open already! + + if (numBlocks <= 0) { + LOGI("ERROR: bad numBlocks %ld", numBlocks); + assert(false); + return kDIErrInvalidCreateReq; + } + + fOuterFormat = outerFormat; + fFileFormat = fileFormat; + fPhysical = physical; + SetCustomNibbleDescr(pNibbleDescr); + fOrder = order; + fFormat = format; + + fNumBlocks = numBlocks; + fHasBlocks = true; + + return CreateImageCommon(pathName, storageName, skipFormat); +} + +DIError DiskImg::CreateImage(const char* pathName, const char* storageName, + OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical, + const NibbleDescr* pNibbleDescr, SectorOrder order, + FSFormat format, long numTracks, long numSectPerTrack, bool skipFormat) +{ + assert(fpDataGFD == NULL); // should not be open already! + + if (numTracks <= 0 || numSectPerTrack == 0) { + LOGI("ERROR: bad tracks/sectors %ld/%ld", numTracks, numSectPerTrack); + assert(false); + return kDIErrInvalidCreateReq; + } + + fOuterFormat = outerFormat; + fFileFormat = fileFormat; + fPhysical = physical; + SetCustomNibbleDescr(pNibbleDescr); + fOrder = order; + fFormat = format; + + fNumTracks = numTracks; + fNumSectPerTrack = numSectPerTrack; + fHasSectors = true; + if (numSectPerTrack < 0) { + /* nibble image with non-standard formatting */ + if (!IsNibbleFormat(fPhysical)) { + LOGI("Whoa: expected nibble format here"); + assert(false); + return kDIErrInvalidCreateReq; + } + LOGI("Sector image w/o sectors, switching to nibble mode"); + fHasNibbles = true; + fHasSectors = false; + fpNibbleDescr = NULL; + } + + return CreateImageCommon(pathName, storageName, skipFormat); +} + +/* + * Do the actual disk image creation. + */ +DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName, + bool skipFormat) +{ + DIError dierr; + + /* + * Step 1: figure out fHasBlocks/fHasSectors/fHasNibbles and any + * other misc fields. + * + * If the disk is a nibble image expected to have a particular + * volume number, it should have already been set by the application. + */ + if (fHasBlocks) { + if ((fNumBlocks % 8) == 0) { + fHasSectors = true; + fNumSectPerTrack = 16; + fNumTracks = fNumBlocks / 8; + } else { + LOGI("NOTE: sector access to new image not possible"); + } + } else if (fHasSectors) { + if ((fNumSectPerTrack & 0x01) == 0) { + fHasBlocks = true; + fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2; + } else { + LOGI("NOTE: block access to new image not possible"); + } + } + if (fHasSectors && fPhysical != kPhysicalFormatSectors) + fHasNibbles = true; + assert(fHasBlocks || fHasSectors || fHasNibbles); + + fFileSysOrder = CalcFSSectorOrder(); + fReadOnly = false; + fDirty = true; + + /* + * Step 2: check for invalid arguments and bad combinations. + */ + dierr = ValidateCreateFormat(); + if (dierr != kDIErrNone) { + LOGE("ERROR: CIC arg validation failed, bailing"); + goto bail; + } + + /* + * Step 3: create the destination file. Put this into fpWrapperGFD + * or fpOuterGFD. + * + * The file must not already exist. + * + * THOUGHT: should allow creation of an in-memory disk image. This won't + * work for NuFX, but will work for pretty much everything else. + */ + LOGI(" CIC: creating '%s'", pathName); + int fd; + fd = open(pathName, O_CREAT | O_EXCL, 0644); + if (fd < 0) { + dierr = (DIError) errno; + LOGE("ERROR: unable to create file '%s' (errno=%d)", + pathName, dierr); + goto bail; + } + close(fd); + + GFDFile* pGFDFile; + pGFDFile = new GFDFile; + + dierr = pGFDFile->Open(pathName, false); + if (dierr != kDIErrNone) { + delete pGFDFile; + goto bail; + } + + if (fOuterFormat == kOuterFormatNone) + fpWrapperGFD = pGFDFile; + else + fpOuterGFD = pGFDFile; + pGFDFile = NULL; + + /* + * Step 4: if we have an outer GFD and therefore don't currently have + * an fpWrapperGFD, create an expandable memory buffer to use. + * + * We want to take a guess at how big the image will be, so compute + * fLength now. + * + * Create an OuterWrapper as needed. + */ + if (IsSectorFormat(fPhysical)) { + if (fHasBlocks) + fLength = (di_off_t) GetNumBlocks() * kBlockSize; + else + fLength = (di_off_t) GetNumTracks() * GetNumSectPerTrack() * kSectorSize; + } else { + assert(IsNibbleFormat(fPhysical)); + fLength = GetNumTracks() * GetNibbleTrackAllocLength(); + } + assert(fLength > 0); + + if (fpWrapperGFD == NULL) { + /* shift GFDs and create a new memory GFD, pre-sized */ + GFDBuffer* pGFDBuffer = new GFDBuffer; + + /* use fLength as a starting point for buffer size; this may expand */ + dierr = pGFDBuffer->Open(NULL, fLength, true, true, false); + if (dierr != kDIErrNone) { + delete pGFDBuffer; + goto bail; + } + + fpWrapperGFD = pGFDBuffer; + pGFDBuffer = NULL; + } + + /* create an fpOuterWrapper struct */ + switch (fOuterFormat) { + case kOuterFormatNone: + break; + case kOuterFormatGzip: + fpOuterWrapper = new OuterGzip; + if (fpOuterWrapper == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + break; + case kOuterFormatZip: + fpOuterWrapper = new OuterZip; + if (fpOuterWrapper == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + break; + default: + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + /* + * Step 5: tell the ImageWrapper to write itself into the GFD, passing + * in the blank memory buffer. + * + * - Unadorned formats copy from memory buffer to fpWrapperGFD on disk. + * (With gz, fpWrapperGFD is actually a memory buffer.) fpDataGFD + * becomes an offset into the file. + * - 2MG writes header into GFD and follows it with all data; DC42 + * and Sim2e do similar things. + * - NuFX reopens pathName as SHK file (fpWrapperGFD must point to a + * file) and accesses the archive through an fpArchive. fpDataGFD + * is created as a memory buffer and the blank image is copied in. + * - DDD leaves fpWrapperGFD alone and copies the blank image into a + * new buffer for fpDataGFD. + * + * Sets fWrappedLength when possible, determined from fPhysical and + * either fNumBlocks or fNumTracks. Creates fpDataGFD, often as a + * GFDGFD offset into fpWrapperGFD. + */ + switch (fFileFormat) { + case kFileFormat2MG: + fpImageWrapper = new Wrapper2MG(); + break; + case kFileFormatDiskCopy42: + fpImageWrapper = new WrapperDiskCopy42(); + fpImageWrapper->SetStorageName(storageName); + break; + case kFileFormatSim2eHDV: + fpImageWrapper = new WrapperSim2eHDV(); + break; + case kFileFormatTrackStar: + fpImageWrapper = new WrapperTrackStar(); + fpImageWrapper->SetStorageName(storageName); + break; + case kFileFormatFDI: + fpImageWrapper = new WrapperFDI(); + break; + case kFileFormatNuFX: + fpImageWrapper = new WrapperNuFX(); + fpImageWrapper->SetStorageName(storageName); + ((WrapperNuFX*)fpImageWrapper)->SetCompressType( + (NuThreadFormat) fNuFXCompressType); + break; + case kFileFormatDDD: + fpImageWrapper = new WrapperDDD(); + break; + case kFileFormatUnadorned: + if (IsSectorFormat(fPhysical)) + fpImageWrapper = new WrapperUnadornedSector(); + else if (IsNibbleFormat(fPhysical)) + fpImageWrapper = new WrapperUnadornedNibble(); + else { + assert(false); + } + break; + default: + assert(fpImageWrapper == NULL); + break; + } + + if (fpImageWrapper == NULL) { + LOGW(" DI couldn't figure out the file format"); + dierr = kDIErrUnrecognizedFileFmt; + goto bail; + } + + /* create the wrapper, write the header, and create fpDataGFD */ + assert(fpDataGFD == NULL); + dierr = fpImageWrapper->Create(fLength, fPhysical, fOrder, + fDOSVolumeNum, fpWrapperGFD, &fWrappedLength, &fpDataGFD); + if (dierr != kDIErrNone) { + LOGE("ImageWrapper Create failed, err=%d", dierr); + goto bail; + } + assert(fpDataGFD != NULL); + + /* + * Step 6: "format" fpDataGFD. + * + * Note we don't specify an ordering to the "create blank" functions. + * Either it's sectors, in which case it's all zeroes, or it's nibbles, + * in which case it's always in physical order. + * + * If we're formatting for nibbles, and the application hasn't specified + * a disk volume number, use the default (254). + */ + if (fPhysical == kPhysicalFormatSectors) + dierr = FormatSectors(fpDataGFD, skipFormat); // zero out the image + else { + assert(!skipFormat); // don't skip low-level nibble formatting! + if (fDOSVolumeNum == kVolumeNumNotSet) { + fDOSVolumeNum = kDefaultNibbleVolumeNum; + LOGD(" Using default nibble volume num"); + } + + dierr = FormatNibbles(fpDataGFD); // write basic nibble stuff + } + + + /* + * We're done! + * + * Quick sanity check... + */ + if (fOuterFormat != kOuterFormatNone) { + assert(fpOuterGFD != NULL); + assert(fpWrapperGFD != NULL); + assert(fpDataGFD != NULL); + } + +bail: + return dierr; +} + +/* + * Check that the requested format is one we can create. + * + * We don't allow .SDK.GZ or 6384-byte nibble 2MG. 2MG sector images + * must be in DOS or ProDOS order. + * + * Only "generic" FS formats may be used. The application may choose + * to call AnalyzeImage later on to set the actual FS once data has + * been written. + */ +DIError DiskImg::ValidateCreateFormat(void) const +{ + /* + * Check for invalid arguments. + */ + if (fHasBlocks && fNumBlocks >= 4194304) { // 2GB or larger? + if (fFileFormat != kFileFormatUnadorned) { + LOGW("CreateImage: images >= 2GB can only be unadorned"); + return kDIErrInvalidCreateReq; + } + } + if (fOuterFormat == kOuterFormatUnknown || + fFileFormat == kFileFormatUnknown || + fPhysical == kPhysicalFormatUnknown || + fOrder == kSectorOrderUnknown || + fFormat == kFormatUnknown) + { + LOGW("CreateImage: ambiguous format"); + return kDIErrInvalidCreateReq; + } + if (fOuterFormat != kOuterFormatNone && + fOuterFormat != kOuterFormatGzip && + fOuterFormat != kOuterFormatZip) + { + LOGW("CreateImage: unsupported outer format %d", fOuterFormat); + return kDIErrInvalidCreateReq; + } + if (fFileFormat != kFileFormatUnadorned && + fFileFormat != kFileFormat2MG && + fFileFormat != kFileFormatDiskCopy42 && + fFileFormat != kFileFormatSim2eHDV && + fFileFormat != kFileFormatTrackStar && + fFileFormat != kFileFormatFDI && + fFileFormat != kFileFormatNuFX && + fFileFormat != kFileFormatDDD) + { + LOGW("CreateImage: unsupported file format %d", fFileFormat); + return kDIErrInvalidCreateReq; + } + if (fFormat != kFormatGenericPhysicalOrd && + fFormat != kFormatGenericProDOSOrd && + fFormat != kFormatGenericDOSOrd && + fFormat != kFormatGenericCPMOrd) + { + LOGW("CreateImage: may only use 'generic' formats"); + return kDIErrInvalidCreateReq; + } + + /* + * Check for invalid combinations. + */ + if (fPhysical != kPhysicalFormatSectors) { + if (fOrder != kSectorOrderPhysical) { + LOGW("CreateImage: nibble images are always 'physical' order"); + return kDIErrInvalidCreateReq; + } + + if (GetHasSectors() == false && GetHasNibbles() == false) { + LOGW("CreateImage: must set hasSectors(%d) or hasNibbles(%d)", + GetHasSectors(), GetHasNibbles()); + return kDIErrInvalidCreateReq; + } + + if (fpNibbleDescr == NULL && GetNumSectPerTrack() > 0) { + LOGW("CreateImage: must provide NibbleDescr for non-sector"); + return kDIErrInvalidCreateReq; + } + + if (fpNibbleDescr != NULL && + fpNibbleDescr->numSectors != GetNumSectPerTrack()) + { + LOGW("CreateImage: ?? nd->numSectors=%d, GetNumSectPerTrack=%d", + fpNibbleDescr->numSectors, GetNumSectPerTrack()); + return kDIErrInvalidCreateReq; + } + + if (fpNibbleDescr != NULL && ( + (fpNibbleDescr->numSectors == 13 && + fpNibbleDescr->encoding != kNibbleEnc53) || + (fpNibbleDescr->numSectors == 16 && + fpNibbleDescr->encoding != kNibbleEnc62)) + ) + { + LOGW("CreateImage: sector count/encoding mismatch"); + return kDIErrInvalidCreateReq; + } + + if (GetNumTracks() != kTrackCount525 && + !(GetNumTracks() == 40 && fFileFormat == kFileFormatTrackStar)) + { + LOGW("CreateImage: unexpected track count %ld", GetNumTracks()); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormat2MG) { + if (fPhysical != kPhysicalFormatSectors && + fPhysical != kPhysicalFormatNib525_6656) + { + LOGW("CreateImage: 2MG can't handle physical %d", fPhysical); + return kDIErrInvalidCreateReq; + } + + if (fPhysical == kPhysicalFormatSectors && + (fOrder != kSectorOrderProDOS && + fOrder != kSectorOrderDOS)) + { + LOGW("CreateImage: 2MG requires DOS or ProDOS ordering"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatNuFX) { + if (fOuterFormat != kOuterFormatNone) { + LOGW("CreateImage: can't mix NuFX and outer wrapper"); + return kDIErrInvalidCreateReq; + } + if (fPhysical != kPhysicalFormatSectors) { + LOGW("CreateImage: NuFX physical must be sectors"); + return kDIErrInvalidCreateReq; + } + if (fOrder != kSectorOrderProDOS) { + LOGW("CreateImage: NuFX is always ProDOS-order"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatDiskCopy42) { + if (fPhysical != kPhysicalFormatSectors) { + LOGW("CreateImage: DC42 physical must be sectors"); + return kDIErrInvalidCreateReq; + } + if ((GetHasBlocks() && GetNumBlocks() != 1600) || + (GetHasSectors() && + (GetNumTracks() != 200 || GetNumSectPerTrack() != 16))) + { + LOGW("CreateImage: DC42 only for 800K disks"); + return kDIErrInvalidCreateReq; + } + if (fOrder != kSectorOrderProDOS && + fOrder != kSectorOrderDOS) // used for UNIDOS disks?? + { + LOGW("CreateImage: DC42 is always ProDOS or DOS"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatSim2eHDV) { + if (fPhysical != kPhysicalFormatSectors) { + LOGW("CreateImage: Sim2eHDV physical must be sectors"); + return kDIErrInvalidCreateReq; + } + if (fOrder != kSectorOrderProDOS) { + LOGW("CreateImage: Sim2eHDV is always ProDOS-order"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatTrackStar) { + if (fPhysical != kPhysicalFormatNib525_Var) { + LOGW("CreateImage: TrackStar physical must be var-nibbles"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatFDI) { + if (fPhysical != kPhysicalFormatNib525_Var) { + LOGW("CreateImage: FDI physical must be var-nibbles"); + return kDIErrInvalidCreateReq; + } + } + if (fFileFormat == kFileFormatDDD) { + if (fPhysical != kPhysicalFormatSectors) { + LOGW("CreateImage: DDD physical must be sectors"); + return kDIErrInvalidCreateReq; + } + if (fOrder != kSectorOrderDOS) { + LOGW("CreateImage: DDD is always DOS-order"); + return kDIErrInvalidCreateReq; + } + if (!GetHasSectors() || GetNumTracks() != 35 || + GetNumSectPerTrack() != 16) + { + LOGW("CreateImage: DDD is only for 16-sector 35-track disks"); + return kDIErrInvalidCreateReq; + } + } + + return kDIErrNone; +} + +/* + * Create a blank image for physical=="sectors". + * + * fLength must be a multiple of 256. + * + * If "quickFormat" is set, only the very last sector is written (to set + * the EOF on the file). + */ +DIError DiskImg::FormatSectors(GenericFD* pGFD, bool quickFormat) const +{ + DIError dierr = kDIErrNone; + char sctBuf[kSectorSize]; + di_off_t length; + + assert(fLength > 0 && (fLength & 0xff) == 0); + + //if (!(fLength & 0x01)) + // return FormatBlocks(pGFD); + + memset(sctBuf, 0, sizeof(sctBuf)); + pGFD->Rewind(); + + if (quickFormat) { + dierr = pGFD->Seek(fLength - sizeof(sctBuf), kSeekSet); + if (dierr != kDIErrNone) { + LOGI(" FormatSectors: GFD seek %ld failed (err=%d)", + (long) fLength - sizeof(sctBuf), dierr); + goto bail; + } + dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL); + if (dierr != kDIErrNone) { + LOGI(" FormatSectors: GFD quick write failed (err=%d)", dierr); + goto bail; + } + } else { + for (length = fLength ; length > 0; length -= sizeof(sctBuf)) { + dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL); + if (dierr != kDIErrNone) { + LOGI(" FormatSectors: GFD write failed (err=%d)", dierr); + goto bail; + } + } + assert(length == 0); + } + + +bail: + return dierr; +} + +#if 0 // didn't help +/* + * Create a blank image for physical=="sectors". This is called from + * FormatSectors when it looks like we're formatting entire blocks. + */ +DIError +DiskImg::FormatBlocks(GenericFD* pGFD) const +{ + DIError dierr; + char blkBuf[kBlockSize]; + long length; + time_t start, end; + + assert(fLength > 0 && (fLength & 0x1ff) == 0); + + start = time(NULL); + + memset(blkBuf, 0, sizeof(blkBuf)); + pGFD->Rewind(); + + for (length = fLength ; length > 0; length -= sizeof(blkBuf)) { + dierr = pGFD->Write(blkBuf, sizeof(blkBuf), NULL); + if (dierr != kDIErrNone) { + LOGI(" FormatBlocks: GFD write failed (err=%d)", dierr); + return dierr; + } + } + assert(length == 0); + + end = time(NULL); + LOGI("FormatBlocks complete, time=%ld", end - start); + + return kDIErrNone; +} +#endif + + +/* + * =========================================================================== + * Utility functions + * =========================================================================== + */ + +/* + * Add a note to this disk image. + * + * This is how we communicate cautions and warnings to the user. Use + * linefeeds ('\n') to indicate line breaks. + * + * The maximum length of a single note is set by the size of "buf". + */ +void DiskImg::AddNote(NoteType type, const char* fmt, ...) +{ + char buf[512]; + char* cp = buf; + int maxLen = sizeof(buf); + va_list args; + int len; + + /* + * Prepend a string that highlights the note. + */ + switch (type) { + case kNoteWarning: + strcpy(cp, "- WARNING: "); + break; + default: + strcpy(cp, "- "); + break; + } + len = strlen(cp); + cp += len; + maxLen -= len; + + /* + * Add the note. + */ + va_start(args, fmt); +#if defined(HAVE_VSNPRINTF) + (void) vsnprintf(cp, maxLen, fmt, args); +#elif defined(HAVE__VSNPRINTF) + (void) _vsnprintf(cp, maxLen, fmt, args); +#else +# error "hosed" +#endif + va_end(args); + + buf[sizeof(buf)-2] = '\0'; // leave room for additional '\n' + len = strlen(buf); + if (len > 0 && buf[len-1] != '\n') { + buf[len] = '\n'; + buf[len+1] = '\0'; + len++; + } + + LOGD("+++ adding note '%s'", buf); + + if (fNotes == NULL) { + fNotes = new char[len +1]; + if (fNotes == NULL) { + LOGW("Unable to create notes[%d]", len+1); + assert(false); + return; + } + strcpy(fNotes, buf); + } else { + int existingLen = strlen(fNotes); + char* newNotes = new char[existingLen + len +1]; + if (newNotes == NULL) { + LOGW("Unable to create newNotes[%d]", existingLen+len+1); + assert(false); + return; + } + strcpy(newNotes, fNotes); + strcpy(newNotes + existingLen, buf); + delete[] fNotes; + fNotes = newNotes; + } +} + +/* + * Return a string with the notes in it. + */ +const char* DiskImg::GetNotes(void) const +{ + if (fNotes == NULL) + return ""; + else + return fNotes; +} + + +/* + * Get length and offset of tracks in a nibble image. This is necessary + * because of formats with variable-length tracks (e.g. TrackStar). + */ +int DiskImg::GetNibbleTrackLength(long track) const +{ + assert(fpImageWrapper != NULL); + return fpImageWrapper->GetNibbleTrackLength(fPhysical, track); +} + +int DiskImg::GetNibbleTrackOffset(long track) const +{ + assert(fpImageWrapper != NULL); + return fpImageWrapper->GetNibbleTrackOffset(fPhysical, track); +} + + +/* + * Return a new object with the appropriate DiskFS sub-class. + * + * If the image hasn't been analyzed, or was analyzed to no avail, "NULL" + * is returned unless "allowUnknown" is set to "true". In that case, a + * DiskFSUnknown is returned. + * + * This doesn't inspire the DiskFS to do any processing, just creates the + * new object. + */ +DiskFS* DiskImg::OpenAppropriateDiskFS(bool allowUnknown) +{ + DiskFS* pDiskFS = NULL; + + /* + * Create an appropriate DiskFS object. + */ + switch (GetFSFormat()) { + case DiskImg::kFormatDOS33: + case DiskImg::kFormatDOS32: + pDiskFS = new DiskFSDOS33(); + break; + case DiskImg::kFormatProDOS: + pDiskFS = new DiskFSProDOS(); + break; + case DiskImg::kFormatPascal: + pDiskFS = new DiskFSPascal(); + break; + case DiskImg::kFormatMacHFS: + pDiskFS = new DiskFSHFS(); + break; + case DiskImg::kFormatUNIDOS: + pDiskFS = new DiskFSUNIDOS(); + break; + case DiskImg::kFormatOzDOS: + pDiskFS = new DiskFSOzDOS(); + break; + case DiskImg::kFormatCFFA4: + case DiskImg::kFormatCFFA8: + pDiskFS = new DiskFSCFFA(); + break; + case DiskImg::kFormatMacPart: + pDiskFS = new DiskFSMacPart(); + break; + case DiskImg::kFormatMicroDrive: + pDiskFS = new DiskFSMicroDrive(); + break; + case DiskImg::kFormatFocusDrive: + pDiskFS = new DiskFSFocusDrive(); + break; + case DiskImg::kFormatCPM: + pDiskFS = new DiskFSCPM(); + break; + case DiskImg::kFormatMSDOS: + pDiskFS = new DiskFSFAT(); + break; + case DiskImg::kFormatRDOS33: + case DiskImg::kFormatRDOS32: + case DiskImg::kFormatRDOS3: + pDiskFS = new DiskFSRDOS(); + break; + case DiskImg::kFormatGutenberg: + pDiskFS = new DiskFSGutenberg(); + break; + + default: + LOGI("WARNING: unhandled DiskFS case %d", GetFSFormat()); + assert(false); + /* fall through */ + case DiskImg::kFormatGenericPhysicalOrd: + case DiskImg::kFormatGenericProDOSOrd: + case DiskImg::kFormatGenericDOSOrd: + case DiskImg::kFormatGenericCPMOrd: + case DiskImg::kFormatUnknown: + if (allowUnknown) { + pDiskFS = new DiskFSUnknown(); + break; + } + } + + return pDiskFS; +} + + +/* + * Fill an array with SectorOrder values. The ordering specified by "first" + * will come first. Unused entries will be set to "unknown" and should be + * ignored. + * + * "orderArray" must have kSectorOrderMax elements. + */ +/*static*/ void DiskImg::GetSectorOrderArray(SectorOrder* orderArray, + SectorOrder first) +{ + // init array + for (int i = 0; i < kSectorOrderMax; i++) + orderArray[i] = (SectorOrder) i; + + // pull the best-guess ordering to the front + assert(orderArray[0] == kSectorOrderUnknown); + + orderArray[0] = first; + orderArray[(int) first] = kSectorOrderUnknown; + + // don't bother checking CP/M sector order + orderArray[kSectorOrderCPM] = kSectorOrderUnknown; +} + + +/* + * Return a short string describing "format". + * + * These are semi-duplicated in ImageFormatDialog.cpp in CiderPress. + */ +/*static*/ const char* DiskImg::ToStringCommon(int format, + const ToStringLookup* pTable, int tableSize) +{ + for (int i = 0; i < tableSize; i++) { + if (pTable[i].format == format) + return pTable[i].str; + } + + assert(false); + return "(unknown)"; +} + +/*static*/ const char* DiskImg::ToString(OuterFormat format) +{ + static const ToStringLookup kOuterFormats[] = { + { DiskImg::kOuterFormatUnknown, "Unknown format" }, + { DiskImg::kOuterFormatNone, "(none)" }, + { DiskImg::kOuterFormatCompress, "UNIX compress" }, + { DiskImg::kOuterFormatGzip, "gzip" }, + { DiskImg::kOuterFormatBzip2, "bzip2" }, + { DiskImg::kOuterFormatZip, "Zip archive" }, + }; + + return ToStringCommon(format, kOuterFormats, NELEM(kOuterFormats)); +} + +/*static*/ const char* DiskImg::ToString(FileFormat format) +{ + static const ToStringLookup kFileFormats[] = { + { DiskImg::kFileFormatUnknown, "Unknown format" }, + { DiskImg::kFileFormatUnadorned, "Unadorned raw data" }, + { DiskImg::kFileFormat2MG, "2MG" }, + { DiskImg::kFileFormatNuFX, "NuFX (ShrinkIt)" }, + { DiskImg::kFileFormatDiskCopy42, "DiskCopy 4.2" }, + { DiskImg::kFileFormatDiskCopy60, "DiskCopy 6.0" }, + { DiskImg::kFileFormatDavex, "Davex volume image" }, + { DiskImg::kFileFormatSim2eHDV, "Sim //e HDV" }, + { DiskImg::kFileFormatTrackStar, "TrackStar image" }, + { DiskImg::kFileFormatFDI, "FDI image" }, + { DiskImg::kFileFormatDDD, "DDD" }, + { DiskImg::kFileFormatDDDDeluxe, "DDDDeluxe" }, + }; + + return ToStringCommon(format, kFileFormats, NELEM(kFileFormats)); +}; + +/*static*/ const char* DiskImg::ToString(PhysicalFormat format) +{ + static const ToStringLookup kPhysicalFormats[] = { + { DiskImg::kPhysicalFormatUnknown, "Unknown format" }, + { DiskImg::kPhysicalFormatSectors, "Sectors" }, + { DiskImg::kPhysicalFormatNib525_6656, "Raw nibbles (6656-byte)" }, + { DiskImg::kPhysicalFormatNib525_6384, "Raw nibbles (6384-byte)" }, + { DiskImg::kPhysicalFormatNib525_Var, "Raw nibbles (variable len)" }, + }; + + return ToStringCommon(format, kPhysicalFormats, NELEM(kPhysicalFormats)); +}; + +/*static*/ const char* DiskImg::ToString(SectorOrder format) +{ + static const ToStringLookup kSectorOrders[] = { + { DiskImg::kSectorOrderUnknown, "Unknown ordering" }, + { DiskImg::kSectorOrderProDOS, "ProDOS block ordering" }, + { DiskImg::kSectorOrderDOS, "DOS sector ordering" }, + { DiskImg::kSectorOrderCPM, "CP/M block ordering" }, + { DiskImg::kSectorOrderPhysical, "Physical sector ordering" }, + }; + + return ToStringCommon(format, kSectorOrders, NELEM(kSectorOrders)); +}; + +/*static*/ const char* DiskImg::ToString(FSFormat format) +{ + static const ToStringLookup kFSFormats[] = { + { DiskImg::kFormatUnknown, "Unknown" }, + { DiskImg::kFormatProDOS, "ProDOS" }, + { DiskImg::kFormatDOS33, "DOS 3.3" }, + { DiskImg::kFormatDOS32, "DOS 3.2" }, + { DiskImg::kFormatPascal, "Pascal" }, + { DiskImg::kFormatMacHFS, "HFS" }, + { DiskImg::kFormatMacMFS, "MFS" }, + { DiskImg::kFormatLisa, "Lisa" }, + { DiskImg::kFormatCPM, "CP/M" }, + { DiskImg::kFormatMSDOS, "MS-DOS FAT" }, + { DiskImg::kFormatISO9660, "ISO-9660" }, + { DiskImg::kFormatRDOS33, "RDOS 3.3 (16-sector)" }, + { DiskImg::kFormatRDOS32, "RDOS 3.2 (13-sector)" }, + { DiskImg::kFormatRDOS3, "RDOS 3 (cracked 13-sector)" }, + { DiskImg::kFormatGenericDOSOrd, "Generic DOS sectors" }, + { DiskImg::kFormatGenericProDOSOrd, "Generic ProDOS blocks" }, + { DiskImg::kFormatGenericPhysicalOrd, "Generic raw sectors" }, + { DiskImg::kFormatGenericCPMOrd, "Generic CP/M blocks" }, + { DiskImg::kFormatUNIDOS, "UNIDOS (400K DOS x2)" }, + { DiskImg::kFormatOzDOS, "OzDOS (400K DOS x2)" }, + { DiskImg::kFormatCFFA4, "CFFA (4 or 6 partitions)" }, + { DiskImg::kFormatCFFA8, "CFFA (8 partitions)" }, + { DiskImg::kFormatMacPart, "Macintosh partitioned disk" }, + { DiskImg::kFormatMicroDrive, "MicroDrive partitioned disk" }, + { DiskImg::kFormatFocusDrive, "FocusDrive partitioned disk" }, + }; + + return ToStringCommon(format, kFSFormats, NELEM(kFSFormats)); +}; + + +/* + * strerror() equivalent for DiskImg errors. + */ +const char* DiskImgLib::DIStrError(DIError dierr) +{ + if (dierr > 0) { + const char* msg; + msg = strerror(dierr); + if (msg != NULL) + return msg; + } + + /* + * BUG: this should be set up as per-thread storage in an MT environment. + * I would be more inclined to worry about this if I was expecting + * to hit "default:". So long as valid values are passed in, and the + * switch statement is kept up to date, we should never have cause + * to return this. + * + * An easier solution, should this present a problem for someone, would + * be to have the function return NULL or "unknown error" when the + * error value isn't recognized. I'd recommend leaving it as-is for + * debug builds, though, as it's helpful to know *which* error is not + * recognized. + */ + static char defaultMsg[32]; + + switch (dierr) { + case kDIErrNone: + return "(no error)"; + + case kDIErrAccessDenied: + return "access denied"; + case kDIErrVWAccessForbidden: + return "for safety, write access to this volume is forbidden"; + case kDIErrSharingViolation: + return "file is already open and cannot be shared"; + case kDIErrNoExclusiveAccess: + return "couldn't get exclusive access"; + case kDIErrWriteProtected: + return "write protected"; + case kDIErrCDROMNotSupported: + return "access to CD-ROM drives is not supported"; + case kDIErrASPIFailure: + return "an ASPI request failed"; + case kDIErrSPTIFailure: + return "an SPTI request failed"; + case kDIErrSCSIFailure: + return "a SCSI request failed"; + case kDIErrDeviceNotReady: + return "device not ready"; + + case kDIErrFileNotFound: + return "file not found"; + case kDIErrForkNotFound: + return "fork not found"; + case kDIErrAlreadyOpen: + return "an image is already open"; + case kDIErrFileOpen: + return "file is open"; + case kDIErrNotReady: + return "object not ready"; + case kDIErrFileExists: + return "file already exists"; + case kDIErrDirectoryExists: + return "directory already exists"; + + case kDIErrEOF: + return "end of file reached"; + case kDIErrReadFailed: + return "read failed"; + case kDIErrWriteFailed: + return "write failed"; + case kDIErrDataUnderrun: + return "tried to read past end of file"; + case kDIErrDataOverrun: + return "tried to write past end of file"; + case kDIErrGenericIO: + return "I/O error"; + + case kDIErrOddLength: + return "image size is wrong"; + case kDIErrUnrecognizedFileFmt: + return "not a recognized disk image format"; + case kDIErrBadFileFormat: + return "image file contents aren't in expected format"; + case kDIErrUnsupportedFileFmt: + return "file format not supported"; + case kDIErrUnsupportedPhysicalFmt: + return "physical format not supported"; + case kDIErrUnsupportedFSFmt: + return "filesystem type not supported"; + case kDIErrBadOrdering: + return "bad sector ordering"; + case kDIErrFilesystemNotFound: + return "specified filesystem not found"; + case kDIErrUnsupportedAccess: + return "the method of access used isn't supported for this image"; + case kDIErrUnsupportedImageFeature: + return "image file uses features that CiderPress doesn't support"; + + case kDIErrInvalidTrack: + return "invalid track number"; + case kDIErrInvalidSector: + return "invalid sector number"; + case kDIErrInvalidBlock: + return "invalid block number"; + case kDIErrInvalidIndex: + return "invalid index number"; + + case kDIErrDirectoryLoop: + return "disk directory structure has an infinite loop"; + case kDIErrFileLoop: + return "file structure has an infinite loop"; + case kDIErrBadDiskImage: + return "the filesystem on this image appears damaged"; + case kDIErrBadFile: + return "file structure appears damaged"; + case kDIErrBadDirectory: + return "a directory appears damaged"; + case kDIErrBadPartition: + return "bad partition"; + + case kDIErrFileArchive: + return "this looks like a file archive, not a disk archive"; + case kDIErrUnsupportedCompression: + return "compression method not supported"; + case kDIErrBadChecksum: + return "checksum doesn't match, data may be corrupted"; + case kDIErrBadCompressedData: + return "the compressed data is corrupted"; + case kDIErrBadArchiveStruct: + return "archive may be damaged"; + + case kDIErrBadNibbleSectors: + return "couldn't read sectors from this image"; + case kDIErrSectorUnreadable: + return "sector not readable"; + case kDIErrInvalidDiskByte: + return "found invalid nibble image disk byte"; + case kDIErrBadRawData: + return "couldn't convert raw data to nibble data"; + + case kDIErrInvalidFileName: + return "invalid file name"; + case kDIErrDiskFull: + return "disk full"; + case kDIErrVolumeDirFull: + return "volume directory is full"; + case kDIErrInvalidCreateReq: + return "invalid disk image create request"; + case kDIErrTooBig: + return "size is larger than we can handle"; + + case kDIErrGeneric: + return "DiskImg generic error"; + case kDIErrInternal: + return "DiskImg internal error"; + case kDIErrMalloc: + return "memory allocation failure"; + case kDIErrInvalidArg: + return "invalid argument"; + case kDIErrNotSupported: + return "feature not supported"; + case kDIErrCancelled: + return "cancelled by user"; + + case kDIErrNufxLibInitFailed: + return "NufxLib initialization failed"; + + default: + sprintf(defaultMsg, "(error=%d)", dierr); + return defaultMsg; + } +} + +/* + * High ASCII conversion table, from Technical Note PT515, + * "Apple File Exchange Q&As". The table is available in a hopelessly + * blurry PDF or a pair of GIFs created with small fonts, but I think I + * have mostly captured it. + */ +/*static*/ const uint8_t DiskImg::kMacHighASCII[128+1] = + "AACENOUaaaaaaceeeeiiiinooooouuuu" // 0x80 - 0x9f + "tocL$oPBrct'.=AO%+<>YudsPpSaoOao" // 0xa0 - 0xbf + "?!-vf=d<>. AAOOo--\"\"''/oyY/o<> f" // 0xc0 - 0xdf + "|*,,%AEAEEIIIIOOaOUUUi^~-,**,\"? "; // 0xe0 - 0xff + + +/* + * Hack for Win32 systems. See Win32BlockIO.cpp for commentary. + */ +bool DiskImgLib::gAllowWritePhys0 = false; +/*static*/ void DiskImg::SetAllowWritePhys0(bool val) { + DiskImgLib::gAllowWritePhys0 = val; +} diff --git a/diskimg/DiskImg.h b/diskimg/DiskImg.h new file mode 100644 index 0000000..5bbe7b4 --- /dev/null +++ b/diskimg/DiskImg.h @@ -0,0 +1,1686 @@ +/* + * CiderPress + * Copyright (C) 2009 by CiderPress authors. All Rights Reserved. + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Public declarations for the DiskImg library. + * + * Everything is wrapped in the "DiskImgLib" namespace. Either prefix + * all references with "DiskImgLib::", or add "using namespace DiskImgLib" + * to all C++ source files that make use of it. + * + * Under Linux, this should be compiled with -D_FILE_OFFSET_BITS=64. + * + * These classes are not thread-safe with respect to access to a single + * disk image. Writing to the same disk image from multiple threads + * simultaneously is bound to end in disaster. Simultaneous access to + * different objects will work, though modifying the same disk image + * file from multiple objects will lead to unpredictable results. + */ +#ifndef DISKIMG_DISKIMG_H +#define DISKIMG_DISKIMG_H + +#include +#include +#include +#include +#include + +//#define EXCISE_GPL_CODE + +/* Windows DLL stuff */ +#ifdef _WIN32 +# ifdef DISKIMG_EXPORTS +# define DISKIMG_API __declspec(dllexport) +# else +# define DISKIMG_API __declspec(dllimport) +# endif +#else +# define DISKIMG_API +#endif + +namespace DiskImgLib { + +/* compiled-against versions; call DiskImg::GetVersion for linked-against */ +#define kDiskImgVersionMajor 5 +#define kDiskImgVersionMinor 0 +#define kDiskImgVersionBug 1 + + +/* + * Errors from the various disk image classes. + */ +typedef enum DIError { + kDIErrNone = 0, + + /* I/O request errors (should renumber/rename to match GS/OS errors?) */ + kDIErrAccessDenied = -10, + kDIErrVWAccessForbidden = -11, // write access to volume forbidden + kDIErrSharingViolation = -12, // file is in use and not shareable + kDIErrNoExclusiveAccess = -13, // couldn't get exclusive access + kDIErrWriteProtected = -14, // disk is write protected + kDIErrCDROMNotSupported = -15, // access to CD-ROM drives not supptd + kDIErrASPIFailure = -16, // generic ASPI failure result + kDIErrSPTIFailure = -17, // generic SPTI failure result + kDIErrSCSIFailure = -18, // generic SCSI failure result + kDIErrDeviceNotReady = -19, // floppy or CD-ROM drive has no media + + kDIErrFileNotFound = -20, + kDIErrForkNotFound = -21, // requested fork does not exist + kDIErrAlreadyOpen = -22, // already open, can't open a 2nd time + kDIErrFileOpen = -23, // file is open, can't delete it + kDIErrNotReady = -24, + kDIErrFileExists = -25, // file already exists + kDIErrDirectoryExists = -26, // directory already exists + + kDIErrEOF = -30, // end-of-file reached + kDIErrReadFailed = -31, + kDIErrWriteFailed = -32, + kDIErrDataUnderrun = -33, // tried to read off end of the image + kDIErrDataOverrun = -34, // tried to write off end of the image + kDIErrGenericIO = -35, // generic I/O error + + kDIErrOddLength = -40, // image size not multiple of sectors + kDIErrUnrecognizedFileFmt = -41, // file format just not recognized + kDIErrBadFileFormat = -42, // filename ext doesn't match contents + kDIErrUnsupportedFileFmt = -43, // recognized but not supported + kDIErrUnsupportedPhysicalFmt = -44, // (same) + kDIErrUnsupportedFSFmt = -45, // (and again) + kDIErrBadOrdering = -46, // requested sector ordering no good + kDIErrFilesystemNotFound = -47, // requested filesystem isn't there + kDIErrUnsupportedAccess = -48, // e.g. read sectors from blocks-only + kDIErrUnsupportedImageFeature = -49, // e.g. FDI image w/Amiga sectors + + kDIErrInvalidTrack = -50, // request for invalid track number + kDIErrInvalidSector = -51, // request for invalid sector number + kDIErrInvalidBlock = -52, // request for invalid block number + kDIErrInvalidIndex = -53, // request with an invalid index + + kDIErrDirectoryLoop = -60, // directory chain points into itself + kDIErrFileLoop = -61, // file sector or block alloc loops + kDIErrBadDiskImage = -62, // the FS on the disk image is damaged + kDIErrBadFile = -63, // bad file on disk image + kDIErrBadDirectory = -64, // bad dir on disk image + kDIErrBadPartition = -65, // bad partition on multi-part format + + kDIErrFileArchive = -70, // file archive, not disk archive + kDIErrUnsupportedCompression = -71, // compression method is not supported + kDIErrBadChecksum = -72, // image file's checksum is bad + kDIErrBadCompressedData = -73, // data can't even be unpacked + kDIErrBadArchiveStruct = -74, // bad archive structure + + kDIErrBadNibbleSectors = -80, // can't read sectors from this image + kDIErrSectorUnreadable = -81, // requested sector not readable + kDIErrInvalidDiskByte = -82, // invalid byte for encoding type + kDIErrBadRawData = -83, // couldn't get correct nibbles + + kDIErrInvalidFileName = -90, // tried to create file with bad name + kDIErrDiskFull = -91, // no space left on disk + kDIErrVolumeDirFull = -92, // no more entries in volume dir + kDIErrInvalidCreateReq = -93, // CreateImage request was flawed + kDIErrTooBig = -94, // larger than we want to handle + + /* higher-level errors */ + kDIErrGeneric = -101, + kDIErrInternal = -102, + kDIErrMalloc = -103, + kDIErrInvalidArg = -104, + kDIErrNotSupported = -105, // feature not currently supported + kDIErrCancelled = -106, // an operation was cancelled by user + + kDIErrNufxLibInitFailed = -110, +} DIError; + +/* return a string describing the error */ +DISKIMG_API const char* DIStrError(DIError dierr); + + +/* exact definition of off_t varies, so just define our own */ +#ifdef _ULONGLONG_ + typedef LONGLONG di_off_t; +#else + typedef off_t di_off_t; +#endif + +/* common definition of "whence" for seeks */ +enum DIWhence { + kSeekSet = SEEK_SET, + kSeekCur = SEEK_CUR, + kSeekEnd = SEEK_END +}; + +/* try to load ASPI under Win2K; if successful, SPTI should be disabled */ +const bool kAlwaysTryASPI = false; +/* ASPI device "filenames" look like "ASPI:x:y:z\" */ +DISKIMG_API extern const char* kASPIDev; + +/* some nibble-encoding constants */ +const int kTrackLenNib525 = 6656; +const int kTrackLenNb2525 = 6384; +const int kTrackLenTrackStar525 = 6525; // max len of data in TS image +const int kTrackAllocSize = 6656; // max 5.25 nibble track len; for buffers +const int kTrackCount525 = 35; // expected #of tracks on 5.25 img +const int kMaxNibbleTracks525 = 40; // max #of tracks on 5.25 nibble img +const int kDefaultNibbleVolumeNum = 254; +const int kBlockSize = 512; // block size for DiskImg interfaces +const int kSectorSize = 256; // sector size (1/2 block) +const int kD13Length = 256 * 13 * 35; // length of a .d13 image + +/* largest expanse we allow access to on a volume (8GB in 512-byte blocks) */ +const long kVolumeMaxBlocks = 8*1024*(1024*1024 / kBlockSize); + +/* largest .gz file we'll open (uncompressed size) */ +const long kGzipMax = 32*1024*1024; + +/* forward and external class definitions */ +class DiskFS; +class A2File; +class A2FileDescr; +class GenericFD; +class OuterWrapper; +class ImageWrapper; +class CircularBufferAccess; +class ASPI; +class LinearBitmap; + + +/* + * Library-global data functions. + * + * This class is just a namespace clumper. Do not instantiate. + */ +class DISKIMG_API Global { +public: + // one-time DLL initialization; use SetDebugMsgHandler first + static DIError AppInit(void); + // one-time DLL cleanup + static DIError AppCleanup(void); + + // return the DiskImg version number + static void GetVersion(int32_t* pMajor, int32_t* pMinor, int32_t* pBug); + + static bool GetAppInitCalled(void) { return fAppInitCalled; } + static bool GetHasSPTI(void); + static bool GetHasASPI(void); + + // return a pointer to our global ASPI instance, or NULL + static ASPI* GetASPI(void) { return fpASPI; } + // shortcut for fpASPI->GetVersion() + static unsigned long GetASPIVersion(void); + + // pointer to the debug message handler + typedef void (*DebugMsgHandler)(const char* file, int line, const char* msg); + static DebugMsgHandler gDebugMsgHandler; + + static DebugMsgHandler SetDebugMsgHandler(DebugMsgHandler handler); + static void PrintDebugMsg(const char* file, int line, const char* fmt, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) + #endif + ; + +private: + // no instantiation allowed + Global(void) {} + ~Global(void) {} + + // make sure app calls AppInit + static bool fAppInitCalled; + + static ASPI* fpASPI; +}; + +extern bool gAllowWritePhys0; // ugh -- see Win32BlockIO.cpp + + +/* + * Disk I/O class, roughly equivalent to a GS/OS disk device driver. + * + * Abstracts away the file's source (file on disk, file in memory) and + * storage format (DOS order, ProDOS order, nibble). Will also cope + * with common disk compression and wrapper formats (Mac DiskCopy, 2MG, + * ShrinkIt, etc) if handed a file on disk. + * + * Images may be embedded within other images, e.g. UNIDOS and storage + * type $04 pascal volumes. + * + * THOUGHT: we need a list(?) of pointers from here back to the DiskFS + * so that any modifications here will "wake" the DiskFS and sub-volumes. + * We also need a "dirty" flag so things like CloseNufx can know not to + * re-do work when Closing after a Flush. Also DiskFS can alert us to + * any locally cached stuff, and we can tell them to flush everything. + * (Possibly useful when doing disk updates, so stuff can be trivially + * un-done. Currently CiderPress checks the filename manually after + * each write, but that's generally less reliable than having the knowledge + * contained in the DiskImg.) + * + * THOUGHT: need a ReadRawTrack that gets raw nibblized data. For a + * nibblized image it returns the data, for a sector image it generates + * the raw data. + * + * THOUGHT: we could reduce the risk of problems and increase performance + * for physical media with a "copy on write" scheme. We'd create a shadow + * array of modified blocks, and write them at Flush time. This would + * provide an instantaneous "revert" feature, and prevent formats like + * DiskCopy42 (which has a CRC in its header) from being inconsistent for + * long stretches. + */ +class DISKIMG_API DiskImg { +public: + // create DiskImg object + DiskImg(void); + virtual ~DiskImg(void); + + /* + * Types describing an image file. + * + * The file itself is described by an external parameter ("file source") + * that is either the name of the file, a memory buffer, or an EFD + * (EmbeddedFileDescriptor). + */ + typedef enum { // format of the "wrapper wrapper" + kOuterFormatUnknown = 0, + kOuterFormatNone = 1, // (plain) + kOuterFormatCompress = 2, // .xx.Z + kOuterFormatGzip = 3, // .xx.gz + kOuterFormatBzip2 = 4, // .xx.bz2 + kOuterFormatZip = 10, // .zip + } OuterFormat; + typedef enum { // format of the image "wrapper" + kFileFormatUnknown = 0, + kFileFormatUnadorned = 1, // .po, .do, ,nib, .raw, .d13 + kFileFormat2MG = 2, // .2mg, .2img, $e0/0130 + kFileFormatDiskCopy42 = 3, // .dsk/.disk, maybe .dc + kFileFormatDiskCopy60 = 4, // .dc6 (often just raw format) + kFileFormatDavex = 5, // $e0/8004 + kFileFormatSim2eHDV = 6, // .hdv + kFileFormatTrackStar = 7, // .app (40-track or 80-track) + kFileFormatFDI = 8, // .fdi (5.25" or 3.5") + kFileFormatNuFX = 20, // .shk, .sdk, .bxy + kFileFormatDDD = 21, // .ddd + kFileFormatDDDDeluxe = 22, // $DD, .ddd + } FileFormat; + typedef enum { // format of the image data stream + kPhysicalFormatUnknown = 0, + kPhysicalFormatSectors = 1, // sequential 256-byte sectors (13/16/32) + kPhysicalFormatNib525_6656 = 2, // 5.25" disk ".nib" (6656 bytes/track) + kPhysicalFormatNib525_6384 = 3, // 5.25" disk ".nb2" (6384 bytes/track) + kPhysicalFormatNib525_Var = 4, // 5.25" disk (variable len, e.g. ".app") + } PhysicalFormat; + typedef enum { // sector ordering for "sector" format images + kSectorOrderUnknown = 0, + kSectorOrderProDOS = 1, // written as series of ProDOS blocks + kSectorOrderDOS = 2, // written as series of DOS sectors + kSectorOrderCPM = 3, // written as series of 1K CP/M blocks + kSectorOrderPhysical = 4, // written as un-interleaved sectors + kSectorOrderMax, // (used for array sizing) + } SectorOrder; + typedef enum { // main filesystem format (based on NuFX enum) + kFormatUnknown = 0, + kFormatProDOS = 1, + kFormatDOS33 = 2, + kFormatDOS32 = 3, + kFormatPascal = 4, + kFormatMacHFS = 5, + kFormatMacMFS = 6, + kFormatLisa = 7, + kFormatCPM = 8, + //kFormatCharFST + kFormatMSDOS = 10, // any FAT filesystem + //kFormatHighSierra + kFormatISO9660 = 12, + //kFormatAppleShare + kFormatRDOS33 = 20, // 16-sector RDOS disk + kFormatRDOS32 = 21, // 13-sector RDOS disk + kFormatRDOS3 = 22, // 13-sector RDOS disk converted to 16 + // "generic" formats *must* be in their own "decade" + kFormatGenericPhysicalOrd = 30, // unknown, but physical-sector-ordered + kFormatGenericProDOSOrd = 31, // unknown, but ProDOS-block-ordered + kFormatGenericDOSOrd = 32, // unknown, but DOS-sector-ordered + kFormatGenericCPMOrd = 33, // unknown, but CP/M-block-ordered + kFormatUNIDOS = 40, // two 400K DOS 3.3 volumes + kFormatOzDOS = 41, // two 400K DOS 3.3 volumes, weird order + kFormatCFFA4 = 42, // CFFA image with 4 or 6 partitions + kFormatCFFA8 = 43, // CFFA image with 8 partitions + kFormatMacPart = 44, // Macintosh-style partitioned disk + kFormatMicroDrive = 45, // ///SHH Systeme's MicroDrive format + kFormatFocusDrive = 46, // Parsons Engineering FocusDrive format + kFormatGutenberg = 47, // Gutenberg word processor format + + // try to keep this in an unsigned char, e.g. for CP clipboard + } FSFormat; + + /* + * Nibble encode/decode description. Use no pointers here, so we + * store as an array and resize at will. + * + * Should we define an enum to describe whether address and data + * headers are standard or some wacky variant? + */ + enum { + kNibbleAddrPrologLen = 3, // d5 aa 96 + kNibbleAddrEpilogLen = 3, // de aa eb + kNibbleDataPrologLen = 3, // d5 aa ad + kNibbleDataEpilogLen = 3, // de aa eb + }; + typedef enum { + kNibbleEncUnknown = 0, + kNibbleEnc44, + kNibbleEnc53, + kNibbleEnc62, + } NibbleEnc; + typedef enum { + kNibbleSpecialNone = 0, + kNibbleSpecialMuse, // doubled sector numbers on tracks > 2 + kNibbleSpecialSkipFirstAddrByte, + } NibbleSpecial; + typedef struct { + char description[32]; + short numSectors; // 13 or 16 (or 18?) + + uint8_t addrProlog[kNibbleAddrPrologLen]; + uint8_t addrEpilog[kNibbleAddrEpilogLen]; + uint8_t addrChecksumSeed; + bool addrVerifyChecksum; + bool addrVerifyTrack; + int addrEpilogVerifyCount; + + uint8_t dataProlog[kNibbleDataPrologLen]; + uint8_t dataEpilog[kNibbleDataEpilogLen]; + uint8_t dataChecksumSeed; + bool dataVerifyChecksum; + int dataEpilogVerifyCount; + + NibbleEnc encoding; + NibbleSpecial special; + } NibbleDescr; + + + static inline bool IsSectorFormat(PhysicalFormat fmt) { + return (fmt == kPhysicalFormatSectors); + } + static inline bool IsNibbleFormat(PhysicalFormat fmt) { + return (fmt == kPhysicalFormatNib525_6656 || + fmt == kPhysicalFormatNib525_6384 || + fmt == kPhysicalFormatNib525_Var); + } + + // file is on disk; stuff like 2MG headers will be identified and stripped + DIError OpenImage(const char* filename, char fssep, bool readOnly); + // file is in memory; provide a pointer to the data start and buffer size + DIError OpenImageFromBufferRO(const uint8_t* buffer, long length); + // file is in memory; provide a pointer to the data start and buffer size + DIError OpenImageFromBufferRW(uint8_t* buffer, long length); + // file is a range of blocks on an open block-oriented disk image + DIError OpenImage(DiskImg* pParent, long firstBlock, long numBlocks); + // file is a range of tracks/sectors on an open sector-oriented disk image + DIError OpenImage(DiskImg* pParent, long firstTrack, long firstSector, + long numSectors); + + // create a new, blank image file + DIError CreateImage(const char* pathName, const char* storageName, + OuterFormat outerFormat, FileFormat fileFormat, + PhysicalFormat physical, const NibbleDescr* pNibbleDescr, + SectorOrder order, FSFormat format, + long numBlocks, bool skipFormat); + DIError CreateImage(const char* pathName, const char* storageName, + OuterFormat outerFormat, FileFormat fileFormat, + PhysicalFormat physical, const NibbleDescr* pNibbleDescr, + SectorOrder order, FSFormat format, + long numTracks, long numSectPerTrack, bool skipFormat); + + // flush any changes to disk; slow recompress only for "kFlushAll" + typedef enum { kFlushUnknown=0, kFlushFastOnly=1, kFlushAll=2 } FlushMode; + DIError FlushImage(FlushMode mode); + // close the image, freeing up any resources in use + DIError CloseImage(void); + // raise/lower refCnt (may want to track pointers someday) + void AddDiskFS(DiskFS* pDiskFS) { fDiskFSRefCnt++; } + void RemoveDiskFS(DiskFS* pDiskFS) { + assert(fDiskFSRefCnt > 0); + fDiskFSRefCnt--; + } + + // (re-)format this image in the specified FS format + DIError FormatImage(FSFormat format, const char* volName); + // reset all blocks/sectors to zeroes + DIError ZeroImage(void); + + // configure for paired sectors (OzDOS) + void SetPairedSectors(bool enable, int idx); + + // identify sector ordering and disk format + // (may want a version that takes "hints" for special disks?) + DIError AnalyzeImage(void); + // figure out what FS and sector ordering is on the disk image + void AnalyzeImageFS(void); + bool ShowAsBlocks(void) const; + // overrule the analyzer (generally not recommended) -- does not + // override FileFormat, which is very reliable + DIError OverrideFormat(PhysicalFormat physical, FSFormat format, + SectorOrder order); + + // Create a DiskFS that matches this DiskImg. Must be called after + // AnalayzeImage, or you will always get a DiskFSUnknown. The DiskFS + // must be freed with "delete" when no longer needed. + DiskFS* OpenAppropriateDiskFS(bool allowUnknown = false); + + // Add information or a warning to the list of notes. Use linefeeds to + // indicate line breaks. This is currently append-only. + typedef enum { kNoteInfo, kNoteWarning } NoteType; + void AddNote(NoteType type, const char* fmt, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) + #endif + ; + const char* GetNotes(void) const; + + // simple accessors + OuterFormat GetOuterFormat(void) const { return fOuterFormat; } + FileFormat GetFileFormat(void) const { return fFileFormat; } + PhysicalFormat GetPhysicalFormat(void) const { return fPhysical; } + SectorOrder GetSectorOrder(void) const { return fOrder; } + FSFormat GetFSFormat(void) const { return fFormat; } + long GetNumTracks(void) const { return fNumTracks; } + int GetNumSectPerTrack(void) const { return fNumSectPerTrack; } + long GetNumBlocks(void) const { return fNumBlocks; } + bool GetReadOnly(void) const { return fReadOnly; } + bool GetDirtyFlag(void) const { return fDirty; } + + // set read-only flag; don't use this (open with correct setting; + // this was added as safety hack for the volume copier) + void SetReadOnly(bool val) { fReadOnly = val; } + + // read a 256-byte sector + // NOTE to self: this function should not be available for odd-sized + // volumes, e.g. a ProDOS /RAM or /RAM5 stored with Davex. Need some way + // to communicate that to disk editor so it knows to grey-out the + // selection checkbox and/or not use "show as sectors" as default. + virtual DIError ReadTrackSector(long track, int sector, void* buf) { + return ReadTrackSectorSwapped(track, sector, buf, fOrder, + fFileSysOrder); + } + DIError ReadTrackSectorSwapped(long track, int sector, + void* buf, SectorOrder imageOrder, SectorOrder fsOrder); + // write a 256-byte sector + virtual DIError WriteTrackSector(long track, int sector, const void* buf); + + // read a 512-byte block + virtual DIError ReadBlock(long block, void* buf) { + return ReadBlockSwapped(block, buf, fOrder, fFileSysOrder); + } + DIError ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder, + SectorOrder fsOrder); + // read multiple blocks + virtual DIError ReadBlocks(long startBlock, int numBlocks, void* buf); + // check our virtual bad block map + bool CheckForBadBlocks(long startBlock, int numBlocks); + // write a 512-byte block + virtual DIError WriteBlock(long block, const void* buf); + // write multiple blocks + virtual DIError WriteBlocks(long startBlock, int numBlocks, const void* buf); + + // read an entire nibblized track + virtual DIError ReadNibbleTrack(long track, uint8_t* buf, + long* pTrackLen); + // write a track; trackLen must be <= those in image + virtual DIError WriteNibbleTrack(long track, const uint8_t* buf, + long trackLen); + + // save the current image as a 2MG file + //DIError Write2MG(const char* filename); + + // need to treat the DOS volume number as meta-data for some disks + short GetDOSVolumeNum(void) const { return fDOSVolumeNum; } + void SetDOSVolumeNum(short val) { fDOSVolumeNum = val; } + enum { kVolumeNumNotSet = -1 }; + + // some simple getters + bool GetHasSectors(void) const { return fHasSectors; } + bool GetHasBlocks(void) const { return fHasBlocks; } + bool GetHasNibbles(void) const { return fHasNibbles; } + bool GetIsEmbedded(void) const { return fpParentImg != NULL; } + + // return the current NibbleDescr + const NibbleDescr* GetNibbleDescr(void) const { return fpNibbleDescr; } + // set the NibbleDescr; we do this by copying the entry into our table + // (could improve by doing memcmp on available entries?) + void SetNibbleDescr(int idx); + void SetCustomNibbleDescr(const NibbleDescr* pDescr); + const NibbleDescr* GetNibbleDescrTable(int* pCount) const { + *pCount = fNumNibbleDescrEntries; + return fpNibbleDescrTable; + } + + // set the NuFX compression type, used when compressing or re-compressing; + // must be set before image is opened or created + void SetNuFXCompressionType(int val) { fNuFXCompressType = val; } + + /* + * Set up a progress callback to use when scanning a disk volume. Pass + * NULL for "func" to disable. + * + * The callback function is expected to return "true" if all is well. + * If it returns false, kDIErrCancelled will eventually come back. + */ + typedef bool (*ScanProgressCallback)(void* cookie, const char* str, + int count); + void SetScanProgressCallback(ScanProgressCallback func, void* cookie); + /* update status dialog during disk scan; called from DiskFS code */ + bool UpdateScanProgress(const char* newStr); + + /* + * Static utility functions. + */ + // returns "true" if the files on this image have DOS structure, i.e. + // simple file types and high-ASCII text files + static bool UsesDOSFileStructure(FSFormat format) { + return (format == kFormatDOS33 || + format == kFormatDOS32 || + format == kFormatGutenberg || + format == kFormatUNIDOS || + format == kFormatOzDOS || + format == kFormatRDOS33 || + format == kFormatRDOS32 || + format == kFormatRDOS3); + } + // returns "true" if we can open files on the specified filesystem + static bool CanOpenFiles(FSFormat format) { + return (format == kFormatProDOS || + format == kFormatDOS33 || + format == kFormatDOS32 || + format == kFormatPascal || + format == kFormatCPM || + format == kFormatRDOS33 || + format == kFormatRDOS32 || + format == kFormatRDOS3); + } + // returns "true" if we can create subdirectories on this filesystem + static bool IsHierarchical(FSFormat format) { + return (format == kFormatProDOS || + format == kFormatMacHFS || + format == kFormatMSDOS); + } + // returns "true" if we can create resource forks on this filesystem + static bool HasResourceForks(FSFormat format) { + return (format == kFormatProDOS || + format == kFormatMacHFS); + } + // returns "true" if the format is one of the "generics" + static bool IsGenericFormat(FSFormat format) { + return (format / 10 == DiskImg::kFormatGenericDOSOrd / 10); + } + + /* this must match DiskImg::kStdNibbleDescrs table */ + enum StdNibbleDescr { + kNibbleDescrDOS33Std = 0, + kNibbleDescrDOS33Patched, + kNibbleDescrDOS33IgnoreChecksum, + kNibbleDescrDOS32Std, + kNibbleDescrDOS32Patched, + kNibbleDescrMuse32, + kNibbleDescrRDOS33, + kNibbleDescrRDOS32, + kNibbleDescrCustom, // slot for custom entry + + kNibbleDescrMAX // must be last + }; + static const NibbleDescr* GetStdNibbleDescr(StdNibbleDescr idx); + // call this once, at DLL initialization time + static void CalcNibbleInvTables(void); + // calculate block number from cyl/head/sect on 3.5" disk + static int CylHeadSect35ToBlock(int cyl, int head, int sect); + // unpack nibble data from a 3.5" disk track + static DIError UnpackNibbleTrack35(const uint8_t* nibbleBuf, + long nibbleLen, uint8_t* outputBuf, int cyl, int head, + LinearBitmap* pBadBlockMap); + // compute the #of sectors per track for cylinder N (0-79) + static int SectorsPerTrack35(int cylinder); + + // get the order in which we test for sector ordering + static void GetSectorOrderArray(SectorOrder* orderArray, SectorOrder first); + + // utility function used by HFS filename normalizer; available to apps + static inline uint8_t MacToASCII(uint8_t uch) { + if (uch < 0x20) + return '?'; + else if (uch < 0x80) + return uch; + else + return kMacHighASCII[uch - 0x80]; + } + + // Allow write access to physical disk 0. This is usually the boot disk, + // but with some BIOS the first IDE drive is always physical 0 even if + // you're booting from SATA. This only has meaning under Win32. + static void SetAllowWritePhys0(bool val); + + /* + * Get string constants for enumerated values. + */ + typedef struct { int format; const char* str; } ToStringLookup; + static const char* ToStringCommon(int format, const ToStringLookup* pTable, + int tableSize); + static const char* ToString(OuterFormat format); + static const char* ToString(FileFormat format); + static const char* ToString(PhysicalFormat format); + static const char* ToString(SectorOrder format); + static const char* ToString(FSFormat format); + +private: + /* + * Fundamental disk image identification. + */ + OuterFormat fOuterFormat; // e.g. gzip + FileFormat fFileFormat; + PhysicalFormat fPhysical; + const NibbleDescr* fpNibbleDescr; // only used for "nibble" images + SectorOrder fOrder; // only used for "sector" images + FSFormat fFormat; + + /* + * This affects how the DiskImg responds to requests for reading + * a track or sector. + * + * "fFileSysOrder", together with with "fOrder", determines how + * sector numbers are translated. It describes the order that the + * DiskFS filesystem expects things to be in. If the image isn't + * sector-addressable, then it is assumed to be in linear block order. + * + * If "fSectorPairing" is set, the DiskImg treats the disk as if + * it were in OzDOS format, with one sector from two different + * volumes in a single 512-byte block. + */ + SectorOrder fFileSysOrder; + bool fSectorPairing; + int fSectorPairOffset; // which image (should be 0 or 1) + + /* + * Internal state. + */ + GenericFD* fpOuterGFD; // outer wrapper, if any (.gz only) + GenericFD* fpWrapperGFD; // Apple II image file + GenericFD* fpDataGFD; // raw Apple II data + OuterWrapper* fpOuterWrapper; // needed for outer .gz wrapper + ImageWrapper* fpImageWrapper; // disk image wrapper (2MG, SHK, etc) + DiskImg* fpParentImg; // set for embedded volumes + short fDOSVolumeNum; // specified by some wrapper formats + di_off_t fOuterLength; // total len of file + di_off_t fWrappedLength; // len of file after Outer wrapper removed + di_off_t fLength; // len of disk image (w/o wrappers) + bool fExpandable; // ProDOS .hdv can expand + bool fReadOnly; // allow writes to this image? + bool fDirty; // have we modified this image? + //bool fIsEmbedded; // is this image embedded in another? + + bool fHasSectors; // image is sector-addressable + bool fHasBlocks; // image is block-addressable + bool fHasNibbles; // image is nibble-addressable + + long fNumTracks; // for sector-addressable images + int fNumSectPerTrack; // (ditto) + long fNumBlocks; // for 512-byte block-addressable images + + uint8_t* fNibbleTrackBuf; // allocated on heap + int fNibbleTrackLoaded; // track currently in buffer + + int fNuFXCompressType; // used when compressing a NuFX image + + char* fNotes; // warnings and FYIs about DiskImg/DiskFS + + LinearBitmap* fpBadBlockMap; // used for 3.5" nibble images + + int fDiskFSRefCnt; // #of DiskFS objects pointing at us + + /* + * NibbleDescr entries. There are several standard ones, and we want + * to allow applications to define additional ones. + */ + NibbleDescr* fpNibbleDescrTable; + int fNumNibbleDescrEntries; + + /* static table of default values */ + static const NibbleDescr kStdNibbleDescrs[]; + + DIError OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly); + DIError CreateImageCommon(const char* pathName, const char* storageName, + bool skipFormat); + DIError ValidateCreateFormat(void) const; + DIError FormatSectors(GenericFD* pGFD, bool quickFormat) const; + //DIError FormatBlocks(GenericFD* pGFD) const; + + DIError CopyBytesOut(void* buf, di_off_t offset, int size) const; + DIError CopyBytesIn(const void* buf, di_off_t offset, int size); + DIError AnalyzeImageFile(const char* pathName, char fssep); + // Figure out the sector ordering for this filesystem, so we can decide + // how the sectors need to be re-arranged when we're reading them. + SectorOrder CalcFSSectorOrder(void) const; + // Handle sector order calculations. + DIError CalcSectorAndOffset(long track, int sector, SectorOrder ImageOrder, + SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector); + inline bool IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder); + + /* + * Progress update during the filesystem scan. This only exists in the + * topmost DiskImg. (This is arguably more appropriate in DiskFS, but + * DiskFS objects don't have a notion of "parent" and are somewhat more + * ephemeral.) + */ + ScanProgressCallback fpScanProgressCallback; + void* fScanProgressCookie; + int fScanCount; + char fScanMsg[128]; + time_t fScanLastMsgWhen; + + /* + * 5.25" nibble image access. + */ + enum { + kDataSize62 = 343, // 342 bytes + checksum byte + kChunkSize62 = 86, // (0x56) + + kDataSize53 = 411, // 410 bytes + checksum byte + kChunkSize53 = 51, // (0x33) + kThreeSize = (kChunkSize53 * 3) + 1, // same as 410 - 256 + }; + DIError ReadNibbleSector(long track, int sector, void* buf, + const NibbleDescr* pNibbleDescr); + DIError WriteNibbleSector(long track, int sector, const void* buf, + const NibbleDescr* pNibbleDescr); + void DumpNibbleDescr(const NibbleDescr* pNibDescr) const; + int GetNibbleTrackLength(long track) const; + int GetNibbleTrackOffset(long track) const; + int GetNibbleTrackFormatLength(void) const { + /* return length to use when formatting for 16 sectors */ + if (fPhysical == kPhysicalFormatNib525_6656) + return kTrackLenNib525; + else if (fPhysical == kPhysicalFormatNib525_6384) + return kTrackLenNb2525; + else if (fPhysical == kPhysicalFormatNib525_Var) { + if (fFileFormat == kFileFormatTrackStar || + fFileFormat == kFileFormatFDI) + { + return kTrackLenNb2525; // use minimum possible + } + } + assert(false); + return -1; + } + int GetNibbleTrackAllocLength(void) const { + /* return length to allocate when creating an image */ + if (fPhysical == kPhysicalFormatNib525_Var && + (fFileFormat == kFileFormatTrackStar || + fFileFormat == kFileFormatFDI)) + { + // use maximum possible + return kTrackLenTrackStar525; + } + return GetNibbleTrackFormatLength(); + } + DIError LoadNibbleTrack(long track, long* pTrackLen); + DIError SaveNibbleTrack(void); + int FindNibbleSectorStart(const CircularBufferAccess& buffer, + int track, int sector, const NibbleDescr* pNibbleDescr, int* pVol); + void DecodeAddr(const CircularBufferAccess& buffer, int offset, + short* pVol, short* pTrack, short* pSector, short* pChksum); + inline uint16_t ConvFrom44(uint8_t val1, uint8_t val2) { + return ((val1 << 1) | 0x01) & val2; + } + DIError DecodeNibbleData(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr); + void EncodeNibbleData(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const; + DIError DecodeNibble62(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr); + void EncodeNibble62(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const; + DIError DecodeNibble53(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr); + void EncodeNibble53(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const; + int TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, int* pVol); + DIError AnalyzeNibbleData(void); + inline uint8_t Conv44(uint16_t val, bool first) const { + if (first) + return (val >> 1) | 0xaa; + else + return val | 0xaa; + } + DIError FormatNibbles(GenericFD* pGFD) const; + + static const uint8_t kMacHighASCII[]; + + /* + * 3.5" nibble access + */ + static int FindNextSector35(const CircularBufferAccess& buffer, int start, + int cyl, int head, int* pSector); + static bool DecodeNibbleSector35(const CircularBufferAccess& buffer, + int start, uint8_t* sectorBuf, uint8_t* readChecksum, + uint8_t* calcChecksum); + static bool UnpackChecksum35(const CircularBufferAccess& buffer, + int offset, uint8_t* checksumBuf); + static void EncodeNibbleSector35(const uint8_t* sectorData, + uint8_t* outBuf); + + /* static data tables */ + static uint8_t kDiskBytes53[32]; + static uint8_t kDiskBytes62[64]; + static uint8_t kInvDiskBytes53[256]; + static uint8_t kInvDiskBytes62[256]; + enum { kInvInvalidValue = 0xff }; + +private: // some C++ stuff to block behavior we don't support + DiskImg& operator=(const DiskImg&); + DiskImg(const DiskImg&); +}; + + + +/* + * Disk filesystem class, roughly equivalent to a GS/OS FST. This is an + * abstract base class. + * + * Static functions know how to access a DiskImg and figure out what kind + * of image we have. Once known, the appropriate sub-class can be + * instantiated. + * + * We maintain a linear list of files to make it easy for applications to + * traverse the full set of files. Sometimes this makes it hard for us to + * update internally (especially HFS). With some minor cleverness it + * should be possible to switch to a tree structure while retaining the + * linear "get next" API. This would be a big help for ProDOS and HFS. + * + * NEED: some notification mechanism for changes to files and/or block + * editing of the disk (especially with regard to open sub-volumes). If + * a disk volume open for file-by-file viewing is modified with the disk + * editor, we should close the file when the disk editor exits. + * + * NEED? disk utilities, such as "disk crunch", for Pascal volumes. Could + * be written externally, but might as well keep fs knowledge embedded. + * + * MISSING: there is no way to override the image analyzer when working + * with sub-volumes. Actually, there is; it just has to happen *after* + * the DiskFS has been created. We should provide an approach that either + * stifles the DiskFS creation, or allows us to override and replace the + * internal DiskFS so we can pop up a sub-volume list, show what we *think* + * is there, and then let the user pick a volume and pick overrides (mainly + * for use in the disk editor). Not sure if we want the changes to "stick"; + * we probably do. Q: does the "scan for sub-volumes" attribute propagate + * recursively to each sub-sub-volume? Probably. + * + * NOTE to self: should make "test" functions more lax when called + * from here, on the assumption that the caller is knowledgeable. Perhaps + * an independent "strictness" variable that can be cranked down through + * multiple calls to AnalyzeImage?? + */ +class DISKIMG_API DiskFS { +public: + /* + * Information about volume usage. + * + * Each "chunk" is a track/sector on a DOS disk or a block on a ProDOS + * or Pascal disk. CP/M really ought to use 1K blocks, but for + * convenience we're just using 512-byte blocks (it's up to the CP/M + * code to set two "chunks" per block). + * + * NOTE: the current DOS/ProDOS/Pascal code is sloppy when it comes to + * keeping this structure up to date. HFS doesn't use it at all. This + * has always been a low-priority feature. + */ + class DISKIMG_API VolumeUsage { + public: + VolumeUsage(void) { + fByBlocks = false; + fTotalChunks = -1; + fNumSectors = -1; + //fFreeChunks = -1; + fList = NULL; + fListSize = -1; + } + ~VolumeUsage(void) { + delete[] fList; + } + + /* + * These values MUST fit in five bits. + * + * Suggested disk map colors: + * 0 = unknown (color-key pink) + * 1 = conflict (medium-red) + * 2 = boot loader (dark-blue) + * 3 = volume dir (light-blue) + * 4 = subdir (medium-blue) + * 5 = user data (medium-green) + * 6 = user index blocks (light-green) + * 7 = embedded filesys (yellow) + * + * THOUGHT: Add flag for I/O error (nibble images) -- requires + * automatic disk verify pass. (Or maybe could be done manually + * on request?) + * + * unused --> black + * marked-used-but-not-used --> dark-red + * used-but-not-marked-used --> orange + */ + typedef enum { + kChunkPurposeUnknown = 0, + kChunkPurposeConflict = 1, // two or more different things + kChunkPurposeSystem = 2, // boot blocks, volume bitmap + kChunkPurposeVolumeDir = 3, // volume dir (or only dir) + kChunkPurposeSubdir = 4, // ProDOS sub-directory + kChunkPurposeUserData = 5, // file on this filesystem + kChunkPurposeFileStruct = 6, // index blocks, T/S lists + kChunkPurposeEmbedded = 7, // embedded filesystem + // how about: outside range claimed by disk, e.g. fTotalBlocks on + // 800K ProDOS disk in a 32MB CFFA volume? + } ChunkPurpose; + + typedef struct ChunkState { + bool isUsed; + bool isMarkedUsed; + ChunkPurpose purpose; // only valid if isUsed is set + } ChunkState; + + // initialize, configuring for either blocks or sectors + DIError Create(long numBlocks); + DIError Create(long numTracks, long numSectors); + bool GetInitialized(void) const { return (fList != NULL); } + + // return the number of chunks on this disk + long GetNumChunks(void) const { return fTotalChunks; } + + // return the number of unallocated chunks, taking into account + // both the free-chunk bitmap (if any) and the actual usage + long GetActualFreeChunks(void) const; + + // return the state of the specified chunk + DIError GetChunkState(long block, ChunkState* pState) const; + DIError GetChunkState(long track, long sector, + ChunkState* pState) const; + + // set the state of a particular chunk (should only be done by + // the DiskFS sub-classes) + DIError SetChunkState(long block, const ChunkState* pState); + DIError SetChunkState(long track, long sector, + const ChunkState* pState); + + void Dump(void) const; // debugging + + private: + DIError GetChunkStateIdx(int idx, ChunkState* pState) const; + DIError SetChunkStateIdx(int idx, const ChunkState* pState); + inline char StateToChar(ChunkState* pState) const; + + /* + * Chunk state is stored as a set of bits in one byte: + * + * 0-4: how is block used (only has meaning if bit 6 is set) + * 5: for nibble images, indicates the block or sector is unreadable + * 6: is block used by something (0=no, 1=yes) + * 7: is block marked "in use" by system map (0=no, 1=yes) + * + * [ Consider reducing "purpose" to 0-3 and adding bad block bit for + * nibble images and physical media.] + */ + enum { + kChunkPurposeMask = 0x1f, // ChunkPurpose enum + kChunkDamagedFlag = 0x20, + kChunkUsedFlag = 0x40, + kChunkMarkedUsedFlag = 0x80, + }; + + bool fByBlocks; + long fTotalChunks; + long fNumSectors; // only valid if !fByBlocks + //long fFreeChunks; + uint8_t* fList; + int fListSize; + }; // end of VolumeUsage class + + /* + * List of sub-volumes. The SubVolume owns the DiskImg and DiskFS + * that are handed to it, so they can be deleted when the SubVolume + * is deleted as part of destroying the parent. + */ + class SubVolume { + public: + SubVolume(void) : fpDiskImg(NULL), fpDiskFS(NULL), + fpPrev(NULL), fpNext(NULL) {} + ~SubVolume(void) { + delete fpDiskFS; // must close first; may need flush to DiskImg + delete fpDiskImg; + } + + void Create(DiskImg* pDiskImg, DiskFS* pDiskFS) { + assert(pDiskImg != NULL); + assert(pDiskFS != NULL); + fpDiskImg = pDiskImg; + fpDiskFS = pDiskFS; + } + + DiskImg* GetDiskImg(void) const { return fpDiskImg; } + DiskFS* GetDiskFS(void) const { return fpDiskFS; } + + SubVolume* GetPrev(void) const { return fpPrev; } + void SetPrev(SubVolume* pSubVol) { fpPrev = pSubVol; } + SubVolume* GetNext(void) const { return fpNext; } + void SetNext(SubVolume* pSubVol) { fpNext = pSubVol; } + + private: + DiskImg* fpDiskImg; + DiskFS* fpDiskFS; + + SubVolume* fpPrev; + SubVolume* fpNext; + }; // end of SubVolume class + + + + /* + * Start of DiskFS declarations. + */ +public: + typedef enum SubScanMode { + kScanSubUnknown = 0, + kScanSubDisabled, + kScanSubEnabled, + kScanSubContainerOnly, + } SubScanMode; + + + DiskFS(void) { + fpA2Head = fpA2Tail = NULL; + fpSubVolumeHead = fpSubVolumeTail = NULL; + fpImg = NULL; + fScanForSubVolumes = kScanSubDisabled; + + fParmTable[kParm_CreateUnique] = 0; + fParmTable[kParmProDOS_AllowLowerCase] = 1; + fParmTable[kParmProDOS_AllocSparse] = 1; + } + virtual ~DiskFS(void) { + DeleteSubVolumeList(); + DeleteFileList(); + SetDiskImg(NULL); + } + + /* + * Static FSFormat-analysis functions, called by the DiskImg AnalyzeImage + * function. Capable of determining with a high degree of accuracy + * what format the disk is in, yet remaining flexible enough to + * function correctly with variations (like DOS3.3 disks with + * truncated TOCs and ProDOS images from hard drives). + * + * The "pOrder" and "pFormat" arguments are in/out. Set them to the + * appropriate "unknown" enum value on entry. If something is known + * of the order or format, put that in instead; in some cases this will + * bias the proceedings, which is useful for efficiency and for making + * overrides work correctly. + * + * On success, these return kDIErrNone and set "*pOrder". On failure, + * they return nonzero and leave "*pOrder" unmodified. + * + * Each DiskFS sub-class should declare a TestFS function. It's not + * virtual void here because, since it's called before the DiskFS is + * created, it must be static. + */ + typedef enum FSLeniency { kLeniencyNot, kLeniencyVery } FSLeniency; + //static DIError TestFS(const DiskImg* pImg, DiskImg::SectorOrder* pOrder, + // DiskImg::FSFormat* pFormat, FSLeniency leniency); + + /* + * Load the disk contents and (if enabled) scan for sub-volumes. + * + * If "headerOnly" is set, we just do a quick scan of the volume header + * to get basic information. The deep file scan is skipped (but can + * be done later). (Sub-classes can choose to ignore the flag and + * always do the full scan; this is an optimization.) Guaranteed to + * set the volume name and volume block/sector count. + * + * If a progress callback is set up, this can return with a "cancelled" + * result, which should not be treated as a failure. + */ + typedef enum { kInitUnknown = 0, kInitHeaderOnly, kInitFull } InitMode; + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) = 0; + + /* + * Format the disk with the appropriate filesystem, creating all filesystem + * structures and (when appropriate) boot blocks. + */ + virtual DIError Format(DiskImg* pDiskImg, const char* volName) + { return kDIErrNotSupported; } + + /* + * Pass in a full path to normalize, and a buffer to copy the output + * into. On entry "pNormalizedBufLen" holds the length of the buffer. + * On exit, it holds the size of the buffer required to hold the + * normalized string. If the buffer is NULL or isn't big enough, no part + * of the normalized path will be copied into the buffer, and a specific + * error (kDIErrDataOverrun) will be returned. + * + * Generally speaking, the normalization process involves separating + * the pathname into its components, modifying each filename as needed + * to make it legal on the current filesystem, and then reassembling + * the components. + * + * The input and output strings are Mac OS Roman. + */ + virtual DIError NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) + { return kDIErrNotSupported; } + + + /* + * Create a file. The CreateParms struct specifies the full set of file + * details. To remain FS-agnostic, use the NufxLib constants + * (kNuStorageDirectory, kNuAccessUnlocked, etc). They match up with + * their ProDOS equivalents, and I promise to make them work right. + * + * On success, the file exists as a fully-formed, zero-length file. A + * pointer to the relevant A2File structure is returned. + */ + enum { + /* valid values for CreateParms; must match ProDOS enum */ + kStorageSeedling = 1, + kStorageExtended = 5, + kStorageDirectory = 13, + }; + typedef struct CreateParms { + const char* pathName; // full pathname for file on disk image + char fssep; + int storageType; // determines normal, subdir, or forked + uint32_t fileType; + uint32_t auxType; + uint32_t access; + time_t createWhen; + time_t modWhen; + } CreateParms; + virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile) + { return kDIErrNotSupported; } + + /* + * Delete a file from the disk. + */ + virtual DIError DeleteFile(A2File* pFile) + { return kDIErrNotSupported; } + + /* + * Rename a file. + */ + virtual DIError RenameFile(A2File* pFile, const char* newName) + { return kDIErrNotSupported; } + + /* + * Alter file attributes. + */ + virtual DIError SetFileInfo(A2File* pFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) + { return kDIErrNotSupported; } + + /* + * Rename a volume. Also works for changing the disk volume number. + */ + virtual DIError RenameVolume(const char* newName) + { return kDIErrNotSupported; } + + + // Accessor + DiskImg* GetDiskImg(void) const { return fpImg; } + + // Test file and volume names (and volume numbers) + // [these need to be static functions for some things... hmm] + //virtual bool IsValidFileName(const char* name) const { return false; } + //virtual bool IsValidVolumeName(const char* name) const { return false; } + + // Return the disk volume name, or NULL if there isn't one. + virtual const char* GetVolumeName(void) const = 0; + + // Return a printable string identifying the FS type and volume + virtual const char* GetVolumeID(void) const = 0; + + // Return the "bare" volume name. For formats where the volume name + // is actually a number (e.g. DOS 3.3), this returns just the number. + // For formats without a volume name or number (e.g. CP/M), this returns + // NULL, indicating that any attempt to change the volume name will fail. + virtual const char* GetBareVolumeName(void) const = 0; + + // Returns "false" if we only support read-only access to this FS type + virtual bool GetReadWriteSupported(void) const = 0; + + // Returns "true" if the filesystem shows evidence of damage. + virtual bool GetFSDamaged(void) const = 0; + + // Returns number of blocks recognized by the filesystem, or -1 if the + // FS isn't block-oriented (e.g. DOS 3.2/3.3) + virtual long GetFSNumBlocks(void) const { return -1; } + + // Get the next file in the list. Start by passing in NULL to get the + // head of the list. Returns NULL when the end of the list is reached. + A2File* GetNextFile(A2File* pCurrent) const; + + // Get a count of the files and directories on this disk. + long GetFileCount(void) const; + + /* + * Find a file by case-insensitive pathname. Assumes fssep=':'. The + * compare function can be overridden for systems like HFS, where "case + * insensitive" has a different meaning because of the native + * character set. + * + * The A2File* returned should not be deleted. + */ + typedef int (*StringCompareFunc)(const char* str1, const char* str2); + A2File* GetFileByName(const char* pathName, StringCompareFunc func = NULL); + + // This controls scanning for sub-volumes; must be set before Initialize(). + SubScanMode GetScanForSubVolumes(void) const { return fScanForSubVolumes; } + void SetScanForSubVolumes(SubScanMode val) { fScanForSubVolumes = val; } + + // some constants for non-ProDOS filesystems to use + enum { kFileAccessLocked = 0x01, kFileAccessUnlocked = 0xc3 }; + + + /* + * We use this as a filename separator character (i.e. between pathname + * components) in all filenames. It's useful to standardize on this + * so that behavior is consistent across all disk and archive formats. + * + * The choice of ':' is good because it's invalid in filenames on + * Windows, Mac OS, GS/OS, and pretty much anywhere else we could be + * running except for UNIX. It's valid under DOS 3.3, but since you + * can't have subdirectories under DOS there's no risk of confusion. + */ + enum { kDIFssep = ':' }; + + + /* + * Return the volume use map. This is a non-const function because + * it might need to do a "just-in-time" usage map update. It returns + * const to keep non-DiskFS classes from altering the map. + */ + const VolumeUsage* GetVolumeUsageMap(void) { + if (fVolumeUsage.GetInitialized()) + return &fVolumeUsage; + else + return NULL; + } + + /* + * Return the total space and free space, in either blocks or sectors + * as appropriate. "*pUnitSize" will be kBlockSize or kSectorSize. + */ + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const = 0; + + + /* + * Get the next volume in the list. Start by passing in NULL to get the + * head of the list. Returns NULL when the end of the list is reached. + */ + SubVolume* GetNextSubVolume(const SubVolume* pCurrent) const; + + /* + * Set some parameters to tell the DiskFS how to operate. Some of + * these are FS-specific, some may be general. + * + * Parameters are set in the current object and all sub-volume objects. + * + * The enum is part of the interface and must be rigidly defined, but + * it is also used to size an array so it can't be too sparse. + */ + typedef enum DiskFSParameter { + kParmUnknown = 0, + + kParm_CreateUnique = 1, // make new filenames unique + + kParmProDOS_AllowLowerCase = 10, // allow lower case and spaces + kParmProDOS_AllocSparse = 11, // don't store empty blocks + + kParmMax // must be last entry + } DiskFSParameter; + long GetParameter(DiskFSParameter parm); + void SetParameter(DiskFSParameter parm, long val); + + /* + * Flush changed data. + * + * The individual filesystems shouldn't generally do any caching; if + * they do, we would want a virtual "FlushFS()" that gets called by + * Flush. The better answer is to cache in DiskImg, which works for + * everything, and knows if the underlying storage is already in RAM. + * + * For the most part this just needs to recursively flush the DiskImg + * objects in all of the sub-volumes and then the current volume. This + * is a no-op in most cases, but if the archive is compressed this will + * cause a new, compressed archive to be created. + * + * The "mode" value determines whether or not we do "heavy" flushes. It's + * very handy to be able to do "slow" flushes for anything that is being + * written directly to disk (as opposed to being run through Deflate), + * so that the UI doesn't indicate that they're partially written when + * in fact they're fully written. + */ + DIError Flush(DiskImg::FlushMode mode); + + /* + * Set the read-only flag on our DiskImg and those of our subvolumes. + * Used to ensure that a DiskFS with un-flushed data can be deleted + * without corrupting the volume. + */ + void SetAllReadOnly(bool val); + + // debug dump + void DumpFileList(void); + +protected: + /* + * Set the DiskImg pointer. Updates the reference count in DiskImg. + */ + void SetDiskImg(DiskImg* pImg); + + // once added, we own the pDiskImg and the pDiskFS (DO NOT pass the + // same DiskImg or DiskFS in more than once!). Note this copies the + // fParmTable and other stuff (fScanForSubVolumes) from parent to child. + void AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS); + // add files to fpA2Head/fpA2Tail + void AddFileToList(A2File* pFile); + // only need for hierarchical filesystems; insert file after pPrev + void InsertFileInList(A2File* pFile, A2File* pPrev); + // delete an entry + void DeleteFileFromList(A2File* pFile); + + // scan for damaged or suspicious files + void ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious); + + // pointer to the DiskImg structure underlying this filesystem + DiskImg* fpImg; + + VolumeUsage fVolumeUsage; + SubScanMode fScanForSubVolumes; + + +private: + A2File* SkipSubdir(A2File* pSubdir); + void CopyInheritables(DiskFS* pNewFS); + void DeleteFileList(void); + void DeleteSubVolumeList(void); + + long fParmTable[kParmMax]; // for DiskFSParameter + + A2File* fpA2Head; + A2File* fpA2Tail; + SubVolume* fpSubVolumeHead; + SubVolume* fpSubVolumeTail; + +private: + DiskFS& operator=(const DiskFS&); + DiskFS(const DiskFS&); +}; + + +/* + * Apple II file class, representing a file on an Apple II volume. This is an + * abstract base class. + * + * There is a different sub-class for each filesystem type. The A2File object + * encapsulates all of the knowledge required to read a file from a disk + * image. + * + * The prev/next pointers, used to maintain a linked list of files, are only + * accessible from DiskFS functions. At some point we may want to rearrange + * the way this is handled, e.g. by not maintaining a list at all, so it's + * important that everything go through DiskFS requests. + * + * The FSFormat is made an explicit member, because sub-classes may not + * understand exactly where the file came from (e.g. was it DOS3.2 or + * DOS 3.3). Somebody might care. + * + * + * NOTE TO SELF: open files need to be generalized. Right now the internal + * implementations only allow a single open, which is okay for our purposes + * but bad for a general FS implementation. As it stands, you can't even + * open both forks at the same time on ProDOS/HFS. + * + * UMMM: The handling of "damaged" files could be more consistent. + */ +class DISKIMG_API A2File { +public: + friend class DiskFS; + + A2File(DiskFS* pDiskFS) : fpDiskFS(pDiskFS) { + fpPrev = fpNext = NULL; + fFileQuality = kQualityGood; + } + virtual ~A2File(void) {} + + /* + * All Apple II files have certain characteristics, of which ProDOS + * is roughly a superset. (Yes, you can have HFS on a IIgs, but + * all that fancy creator type stuff is decidedly Mac-centric. Still, + * we want to assume 4-byte file and aux types.) + * + * NEED: something distinguishing between files and disk images? + * + * NOTE: there is no guarantee that GetPathName will return unique values + * (duplicates are possible). We don't guarantee that you won't get an + * empty string back (it's valid to have an empty filename in the dir in + * DOS 3.3, and it's possible for other filesystems to be damaged). + * + * The filename returned is defined to be null-terminated Mac OS Roman. + * For most filesystems this is automatic, as filenames are restricted + * to ASCII, but for DOS 3.3 it requires stripping high bits. It also + * means that embedded nulls will be lost. + * + * The original unmodified filename is available through GetRawFileName, + * which can be optionally passed a size_t pointer to get the size + * of the raw file name, in case it has embedded nulls. + * + * We do guarantee that the contents of subdirectories are grouped + * together. This makes it much easier to construct a hierarchy out of + * the linear list. This becomes important when dynamically adding + * files to a disk. + */ + virtual const char* GetFileName(void) const = 0; // name of this file + virtual const char* GetPathName(void) const = 0; // full path + virtual const char* GetRawFileName(size_t* size = NULL) const; // get unmodified file name + virtual char GetFssep(void) const = 0; // '\0' if none + virtual uint32_t GetFileType(void) const = 0; + virtual uint32_t GetAuxType(void) const = 0; + virtual uint32_t GetAccess(void) const = 0; // ProDOS-style perms + virtual time_t GetCreateWhen(void) const = 0; + virtual time_t GetModWhen(void) const = 0; + virtual di_off_t GetDataLength(void) const = 0; // len of data fork + virtual di_off_t GetDataSparseLength(void) const = 0; // len w/o sparse areas + virtual di_off_t GetRsrcLength(void) const = 0; // len or -1 if no rsrc + virtual di_off_t GetRsrcSparseLength(void) const = 0; // len or -1 if no rsrc + virtual DiskImg::FSFormat GetFSFormat(void) const { + return fpDiskFS->GetDiskImg()->GetFSFormat(); + } + virtual bool IsDirectory(void) const { return false; } + virtual bool IsVolumeDirectory(void) const { return false; } + + /* + * Open a file. Treat the A2FileDescr like an fd. + */ + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) = 0; + + /* + * This is called by the A2FileDescr object when somebody invokes Close(). + * The A2File object should remove the A2FileDescr from its list of open + * files and delete the storage. The implementation must not call the + * A2FileDescr's Close function, since that would cause a recursive loop. + * + * This should not be called by an application. + */ + virtual void CloseDescr(A2FileDescr* pOpenFile) = 0; + + /* + * This is only useful for hierarchical filesystems like ProDOS, + * where the order of items in the linear list is significant. It + * allows an unambiguous determination of which subdir a file resides + * in, even if somebody has sector-edited the filesystem so that two + * subdirs have the same name. (It's also a bit speedier to compare + * than pathname substrings would be.) + */ + virtual A2File* GetParent(void) const { return NULL; } + + /* + * Returns "true" if either fork of the file is open, "false" if not. + */ + virtual bool IsFileOpen(void) const = 0; + + virtual void Dump(void) const = 0; // debugging + + typedef enum FileQuality { + kQualityUnknown = 0, + kQualityGood, + kQualitySuspicious, + kQualityDamaged, + } FileQuality; + virtual FileQuality GetQuality(void) const { return fFileQuality; } + virtual void SetQuality(FileQuality quality); + virtual void ResetQuality(void); + + DiskFS* GetDiskFS(void) const { return fpDiskFS; } + +protected: + DiskFS* fpDiskFS; + virtual void SetParent(A2File* pParent) { /* do nothing */ } + +private: + A2File* GetPrev(void) const { return fpPrev; } + void SetPrev(A2File* pFile) { fpPrev = pFile; } + A2File* GetNext(void) const { return fpNext; } + void SetNext(A2File* pFile) { fpNext = pFile; } + + // Set when file structure is damaged and application should not try + // to open the file. + FileQuality fFileQuality; + + A2File* fpPrev; + A2File* fpNext; + + +private: + A2File& operator=(const A2File&); + A2File(const A2File&); +}; + + +/* + * Abstract representation of an open file. This contains all active state + * and all information required to read and write a file. + * + * Do not delete these objects; instead, invoke the Close method, so that they + * can be removed from the parents' list of open files. + * TODO: consider making the destructor "protected" + */ +class DISKIMG_API A2FileDescr { +public: + A2FileDescr(A2File* pFile) : + fpFile(pFile), + fProgressUpdateFunc(NULL), + fProgressUpdateMax(0), + fProgressUpdateState(NULL) + {} + virtual ~A2FileDescr(void) { fpFile = NULL; /*paranoia*/ } + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) = 0; + virtual DIError Write(const void* buf, size_t len, size_t* pActual = NULL) = 0; + virtual DIError Seek(di_off_t offset, DIWhence whence) = 0; + virtual di_off_t Tell(void) = 0; + virtual DIError Close(void) = 0; + + virtual DIError GetStorage(long sectorIdx, long* pTrack, long* pSector) + const = 0; + virtual DIError GetStorage(long blockIdx, long* pBlock) + const = 0; + virtual long GetSectorCount(void) const = 0; + virtual long GetBlockCount(void) const = 0; + + virtual DIError Rewind(void) { return Seek(0, kSeekSet); } + + A2File* GetFile(void) const { return fpFile; } + + /* + * Progress update callback mechanism. Pass in the length or (for writes) + * expected length of the file. This invokes the callback with the + * lengths and some pointers. + * + * If the progress callback returns "true", progress continues. If it + * returns "false", the read or write function will return with + * kDIErrCancelled. + */ + typedef bool (*ProgressUpdater)(A2FileDescr* pFile, di_off_t max, + di_off_t current, void* vState); + void SetProgressUpdater(ProgressUpdater func, di_off_t max, void* state) { + fProgressUpdateFunc = func; + fProgressUpdateMax = max; + fProgressUpdateState = state; + } + void ClearProgressUpdater(void) { + fProgressUpdateFunc = NULL; + } + +protected: + A2File* fpFile; + + /* + * Internal utility functions for mapping blocks to sectors and vice-versa. + */ + virtual void TrackSectorToBlock(long track, long sector, long* pBlock, + bool* pSecondHalf) const + { + int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack(); + assert(track < fpFile->GetDiskFS()->GetDiskImg()->GetNumTracks()); + assert(sector < numSectPerTrack); + long dblBlock = track * numSectPerTrack + sector; + *pBlock = dblBlock / 2; + *pSecondHalf = (dblBlock & 0x01) != 0; + } + virtual void BlockToTrackSector(long block, bool secondHalf, long* pTrack, + long* pSector) const + { + assert(block < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks()); + int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack(); + int dblBlock = block * 2; + if (secondHalf) + dblBlock++; + *pTrack = dblBlock / numSectPerTrack; + *pSector = dblBlock % numSectPerTrack; + } + + /* + * Call this from FS-specific read/write functions on successful + * completion (and perhaps more often for filesystems with potentially + * large files, e.g. ProDOS/HFS). + * + * Test the return value; if "false", user wishes to cancel the op, and + * long read or write calls should return immediately. + */ + inline bool UpdateProgress(di_off_t current) { + if (fProgressUpdateFunc != NULL) { + return (*fProgressUpdateFunc)(this, fProgressUpdateMax, current, + fProgressUpdateState); + } else { + return true; + } + } + +private: + A2FileDescr& operator=(const A2FileDescr&); + A2FileDescr(const A2FileDescr&); + + /* storage for progress update goodies */ + ProgressUpdater fProgressUpdateFunc; + di_off_t fProgressUpdateMax; + void* fProgressUpdateState; +}; + +} // namespace DiskImgLib + +#endif /*DISKIMG_DISKIMG_H*/ diff --git a/diskimg/DiskImgDetail.h b/diskimg/DiskImgDetail.h new file mode 100644 index 0000000..d8b6669 --- /dev/null +++ b/diskimg/DiskImgDetail.h @@ -0,0 +1,3320 @@ +/* + * CiderPress + * Copyright (C) 2009 by CiderPress authors. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Sub-classes of the base classes defined in DiskImg.h. + * + * Most applications will not need to include this file, because the + * polymorphic interfaces do everything they need. If something needs to + * examine the actual directory structure of a file, it can do so through + * these declarations. + */ +#ifndef DISKIMG_DISKIMGDETAIL_H +#define DISKIMG_DISKIMGDETAIL_H + +#include "../nufxlib/NufxLib.h" +#include + +#include "DiskImg.h" + +#ifndef EXCISE_GPL_CODE +# include "../libhfs/hfs.h" +#endif + +namespace DiskImgLib { + +/* + * =========================================================================== + * Outer wrappers + * =========================================================================== + */ + +/* + * Outer wrapper class, representing a compression utility or archive + * format that must be stripped away so we can get to the Apple II stuff. + * + * Outer wrappers usually have a filename embedded in them, representing + * the original name of the file. We want to use the extension from this + * name when evaluating the file contents. Usually. + */ +class OuterWrapper { +public: + OuterWrapper(void) {} + virtual ~OuterWrapper(void) {} + + // all sub-classes should have one of these + //static DIError Test(GenericFD* pGFD, long outerLength); + + // open the file and prepare to access it; fills out return values + // NOTE: pGFD must be a GFDFile. + virtual DIError Load(GenericFD* pOuterGFD, di_off_t outerLength, bool readOnly, + di_off_t* pWrapperLength, GenericFD** ppWrapperGFD) = 0; + + virtual DIError Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD, + di_off_t wrapperLength) = 0; + + // set on recoverable errors, like a CRC failure + virtual bool IsDamaged(void) const = 0; + + // indicate that we don't have a "fast" flush + virtual bool HasFastFlush(void) const { return false; } + + virtual const char* GetExtension(void) const = 0; + +private: + OuterWrapper& operator=(const OuterWrapper&); + OuterWrapper(const OuterWrapper&); +}; + +class OuterGzip : public OuterWrapper { +public: + OuterGzip(void) { fWrapperDamaged = false; } + virtual ~OuterGzip(void) {} + + static DIError Test(GenericFD* pGFD, di_off_t outerLength); + virtual DIError Load(GenericFD* pGFD, di_off_t outerLength, bool readOnly, + di_off_t* pTotalLength, GenericFD** ppNewGFD) override; + virtual DIError Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD, + di_off_t wrapperLength) override; + + virtual bool IsDamaged(void) const override { return fWrapperDamaged; } + + virtual const char* GetExtension(void) const override { return NULL; } + +private: + DIError ExtractGzipImage(gzFile gzfp, char** pBuf, di_off_t* pLength); + DIError CloseGzip(void); + + // Largest possible ProDOS volume; quite a bit to hold in RAM. Add a + // little extra for .hdv format. + enum { kMaxUncompressedSize = kGzipMax +256 }; + + bool fWrapperDamaged; +}; + +class OuterZip : public OuterWrapper { +public: + OuterZip(void) : fStoredFileName(NULL), fExtension(NULL) {} + virtual ~OuterZip(void) { + delete[] fStoredFileName; + delete[] fExtension; + } + + static DIError Test(GenericFD* pGFD, di_off_t outerLength); + virtual DIError Load(GenericFD* pGFD, di_off_t outerLength, bool readOnly, + di_off_t* pTotalLength, GenericFD** ppNewGFD) override; + virtual DIError Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD, + di_off_t wrapperLength) override; + + virtual bool IsDamaged(void) const override { return false; } + + virtual const char* GetExtension(void) const override { return fExtension; } + +private: + class LocalFileHeader { + public: + LocalFileHeader(void) : + fVersionToExtract(0), + fGPBitFlag(0), + fCompressionMethod(0), + fLastModFileTime(0), + fLastModFileDate(0), + fCRC32(0), + fCompressedSize(0), + fUncompressedSize(0), + fFileNameLength(0), + fExtraFieldLength(0), + fFileName(NULL) + {} + virtual ~LocalFileHeader(void) { delete[] fFileName; } + + DIError Read(GenericFD* pGFD); + DIError Write(GenericFD* pGFD); + void SetFileName(const char* name); + + // uint32_t fSignature; + uint16_t fVersionToExtract; + uint16_t fGPBitFlag; + uint16_t fCompressionMethod; + uint16_t fLastModFileTime; + uint16_t fLastModFileDate; + uint32_t fCRC32; + uint32_t fCompressedSize; + uint32_t fUncompressedSize; + uint16_t fFileNameLength; + uint16_t fExtraFieldLength; + uint8_t* fFileName; + // extra field + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void Dump(void) const; + }; + + class CentralDirEntry { + public: + CentralDirEntry(void) : + fVersionMadeBy(0), + fVersionToExtract(0), + fGPBitFlag(0), + fCompressionMethod(0), + fLastModFileTime(0), + fLastModFileDate(0), + fCRC32(0), + fCompressedSize(0), + fUncompressedSize(0), + fFileNameLength(0), + fExtraFieldLength(0), + fFileCommentLength(0), + fDiskNumberStart(0), + fInternalAttrs(0), + fExternalAttrs(0), + fLocalHeaderRelOffset(0), + fFileName(NULL), + fFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] fFileName; + delete[] fFileComment; + } + + DIError Read(GenericFD* pGFD); + DIError Write(GenericFD* pGFD); + void SetFileName(const char* name); + + // uint32_t fSignature; + uint16_t fVersionMadeBy; + uint16_t fVersionToExtract; + uint16_t fGPBitFlag; + uint16_t fCompressionMethod; + uint16_t fLastModFileTime; + uint16_t fLastModFileDate; + uint32_t fCRC32; + uint32_t fCompressedSize; + uint32_t fUncompressedSize; + uint16_t fFileNameLength; + uint16_t fExtraFieldLength; + uint16_t fFileCommentLength; + uint16_t fDiskNumberStart; + uint16_t fInternalAttrs; + uint32_t fExternalAttrs; + uint32_t fLocalHeaderRelOffset; + uint8_t* fFileName; + // extra field + uint8_t* fFileComment; // alloc with new[] + + void Dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + fDiskNumber(0), + fDiskWithCentralDir(0), + fNumEntries(0), + fTotalNumEntries(0), + fCentralDirSize(0), + fCentralDirOffset(0), + fCommentLen(0) + {} + virtual ~EndOfCentralDir(void) {} + + DIError ReadBuf(const uint8_t* buf, int len); + DIError Write(GenericFD* pGFD); + + // uint32_t fSignature; + uint16_t fDiskNumber; + uint16_t fDiskWithCentralDir; + uint16_t fNumEntries; + uint16_t fTotalNumEntries; + uint32_t fCentralDirSize; + uint32_t fCentralDirOffset; // offset from first disk + uint16_t fCommentLen; + // archive comment + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + }; + + void Dump(void) const; + }; + + enum { + kDataDescriptorSignature = 0x08074b50, + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + kZipFssep = '/', + kDefaultVersion = 20, + kMaxUncompressedSize = kGzipMax +256, + }; + enum { + kCompressStored = 0, // no compression + //kCompressShrunk = 1, + //kCompressImploded = 6, + kCompressDeflated = 8, // standard deflate + }; + + static DIError ReadCentralDir(GenericFD* pGFD, di_off_t outerLength, + CentralDirEntry* pDirEntry); + DIError ExtractZipEntry(GenericFD* pOuterGFD, CentralDirEntry* pCDE, + uint8_t** pBuf, di_off_t* pLength); + DIError InflateGFDToBuffer(GenericFD* pGFD, unsigned long compSize, + unsigned long uncompSize, uint8_t* buf); + DIError DeflateGFDToGFD(GenericFD* pDst, GenericFD* pSrc, di_off_t length, + di_off_t* pCompLength, uint32_t* pCRC); + +private: + void SetExtension(const char* ext); + void SetStoredFileName(const char* name); + void GetMSDOSTime(uint16_t* pDate, uint16_t* pTime); + void DOSTime(time_t when, uint16_t* pDate, uint16_t* pTime); + + char* fStoredFileName; + char* fExtension; +}; + + +/* + * =========================================================================== + * Image wrappers + * =========================================================================== + */ + +/* + * Image wrapper class, representing the format of the Windows files. + * Might be "raw" data, might be data with a header, might be a complex + * or compressed format that must be extracted to a buffer. + */ +class ImageWrapper { +public: + ImageWrapper(void) {} + virtual ~ImageWrapper(void) {} + + // all sub-classes should have one of these + // static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + + // open the file and prepare to access it; fills out return values + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) = 0; + + // fill out the wrapper, using the specified parameters + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) = 0; + + // push altered data to the wrapper GFD + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) = 0; + + // set the storage name (used by some formats) + virtual void SetStorageName(const char* name) { + // default implementation + assert(false); + } + + // indicate that we have a "fast" flush + virtual bool HasFastFlush(void) const = 0; + + // set by "Prep" on recoverable errors, like a CRC failure, for some fmts + virtual bool IsDamaged(void) const { return false; } + + // if this wrapper format includes a file comment, return it + //virtual const char* GetComment(void) const { return NULL; } + + /* + * Some additional goodies required for accessing variable-length nibble + * tracks in TrackStar images. A default implementation is provided and + * used for everything but TrackStar. + */ + virtual int GetNibbleTrackLength(DiskImg::PhysicalFormat physical, int track) const + { + if (physical == DiskImg::kPhysicalFormatNib525_6656) + return kTrackLenNib525; + else if (physical == DiskImg::kPhysicalFormatNib525_6384) + return kTrackLenNb2525; + else { + assert(false); + return -1; + } + } + virtual void SetNibbleTrackLength(int track, int length) { /*do nothing*/ } + virtual int GetNibbleTrackOffset(DiskImg::PhysicalFormat physical, int track) const + { + if (physical == DiskImg::kPhysicalFormatNib525_6656 || + physical == DiskImg::kPhysicalFormatNib525_6384) + { + /* fixed-length tracks */ + return GetNibbleTrackLength(physical, 0) * track; + } else { + assert(false); + return -1; + } + } + // TrackStar images can have more, but otherwise all nibble images have 35 + virtual int GetNibbleNumTracks(void) const + { + return kTrackCount525; + } + +private: + ImageWrapper& operator=(const ImageWrapper&); + ImageWrapper(const ImageWrapper&); +}; + + +class Wrapper2MG : public ImageWrapper { +public: + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return true; } + //virtual const char* GetComment(void) const { return NULL; } + // (need to hold TwoImgHeader in the struct, rather than as temp, or + // need to copy the comment out into Wrapper2MG storage e.g. StorageName) +}; + +class WrapperNuFX : public ImageWrapper { +public: + WrapperNuFX(void) : fpArchive(NULL), fThreadIdx(0), fStorageName(NULL), + fCompressType(kNuThreadFormatLZW2) + {} + virtual ~WrapperNuFX(void) { CloseNuFX(); delete[] fStorageName; } + + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return false; } + + void SetStorageName(const char* name) { + delete[] fStorageName; + if (name != NULL) { + fStorageName = new char[strlen(name)+1]; + strcpy(fStorageName, name); + } else + fStorageName = NULL; + } + void SetCompressType(NuThreadFormat format) { fCompressType = format; } + +private: + enum { kDefaultStorageFssep = ':' }; + static NuResult ErrMsgHandler(NuArchive* pArchive, void* vErrorMessage); + static DIError OpenNuFX(const char* pathName, NuArchive** ppArchive, + NuThreadIdx* pThreadIdx, long* pLength, bool readOnly); + DIError GetNuFXDiskImage(NuArchive* pArchive, NuThreadIdx threadIdx, + long length, char** ppData); + static char* GenTempPath(const char* path); + DIError CloseNuFX(void); + void UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime); + + NuArchive* fpArchive; + NuThreadIdx fThreadIdx; + char* fStorageName; + NuThreadFormat fCompressType; +}; + +class WrapperDiskCopy42 : public ImageWrapper { +public: + WrapperDiskCopy42(void) : fStorageName(NULL), fBadChecksum(false) + {} + virtual ~WrapperDiskCopy42(void) { delete[] fStorageName; } + + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + void SetStorageName(const char* name) { + delete[] fStorageName; + if (name != NULL) { + fStorageName = new char[strlen(name)+1]; + strcpy(fStorageName, name); + } else + fStorageName = NULL; + } + + virtual bool HasFastFlush(void) const override { return false; } + virtual bool IsDamaged(void) const override { return fBadChecksum; } + +private: + typedef struct DC42Header DC42Header; + static void DumpHeader(const DC42Header* pHeader); + void InitHeader(DC42Header* pHeader); + static int ReadHeader(GenericFD* pGFD, DC42Header* pHeader); + DIError WriteHeader(GenericFD* pGFD, const DC42Header* pHeader); + static DIError ComputeChecksum(GenericFD* pGFD, + uint32_t* pChecksum); + + char* fStorageName; + bool fBadChecksum; +}; + +class WrapperDDD : public ImageWrapper { +public: + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return false; } + + enum { + kMaxDDDZeroCount = 4, // 3 observed, 4 suspected + }; + +private: + class BitBuffer; + enum { + kNumTracks = 35, + kNumSectors = 16, + kSectorSize = 256, + kTrackLen = kNumSectors * kSectorSize, + }; + + static DIError CheckForRuns(GenericFD* pGFD); + static DIError Unpack(GenericFD* pGFD, GenericFD** ppNewGFD, + short* pDiskVolNum); + + static DIError UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD, + short* pDiskVolNum); + static bool UnpackTrack(BitBuffer* pBitBuffer, uint8_t* trackBuf); + static DIError PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD, + short diskVolNum); + static void PackTrack(const uint8_t* trackBuf, BitBuffer* pBitBuf); + static void ComputeFreqCounts(const uint8_t* trackBuf, + uint16_t* freqCounts); + static void ComputeFavorites(uint16_t* freqCounts, + uint8_t* favorites); + + short fDiskVolumeNum; +}; + +class WrapperSim2eHDV : public ImageWrapper { +public: + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return true; } +}; + +class WrapperTrackStar : public ImageWrapper { +public: + enum { + kTrackStarNumTracks = 40, + kFileTrackStorageLen = 6656, + kMaxTrackLen = kFileTrackStorageLen - (128+1+2), // header + footer + kCommentFieldLen = 0x2e, + }; + + WrapperTrackStar(void) : fStorageName(NULL) { + memset(&fNibbleTrackInfo, 0, sizeof(fNibbleTrackInfo)); + fNibbleTrackInfo.numTracks = -1; + } + virtual ~WrapperTrackStar(void) { delete[] fStorageName; } + + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return false; } + + virtual void SetStorageName(const char* name) override + { + delete[] fStorageName; + if (name != NULL) { + fStorageName = new char[strlen(name)+1]; + strcpy(fStorageName, name); + } else + fStorageName = NULL; + } + +private: + static DIError VerifyTrack(int track, const uint8_t* trackBuf); + DIError Unpack(GenericFD* pGFD, GenericFD** ppNewGFD); + DIError UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD); + + int fImageTracks; + char* fStorageName; + + /* + * Data structure for managing nibble images with variable-length tracks. + */ + typedef struct { + int numTracks; // should be 35 or 40 + int length[kMaxNibbleTracks525]; + int offset[kMaxNibbleTracks525]; + } NibbleTrackInfo; + NibbleTrackInfo fNibbleTrackInfo; // count and lengths for variable formats + + // nibble images can have variable-length data fields + virtual int GetNibbleTrackLength(DiskImg::PhysicalFormat physical, int track) const + { + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(fNibbleTrackInfo.numTracks > 0); + + return fNibbleTrackInfo.length[track]; + } + virtual void SetNibbleTrackLength(int track, int length); +#if 0 + { + assert(track >= 0); + assert(length > 0 && length <= kMaxTrackLen); + assert(track < fNibbleTrackInfo.numTracks); + + fNibbleTrackInfo.length[track] = length; + } +#endif + virtual int GetNibbleTrackOffset(DiskImg::PhysicalFormat physical, int track) const + { + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(fNibbleTrackInfo.numTracks > 0); + + return fNibbleTrackInfo.offset[track]; + } + virtual int GetNibbleNumTracks(void) const + { + return kTrackStarNumTracks; + } +}; + +class WrapperFDI : public ImageWrapper { +public: + WrapperFDI(void) : fHeaderBuf(), fImageTracks(0), fStorageName(NULL) {} + virtual ~WrapperFDI(void) {} + + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return false; } + + enum { + kSignatureLen = 27, + kCreatorLen = 30, + kCommentLen = 80, + }; + +private: + static const char* kFDIMagic; + + /* what type of disk is this? */ + typedef enum DiskType { + kDiskType8 = 0, + kDiskType525 = 1, + kDiskType35 = 2, + kDiskType3 = 3 + } DiskType; + + /* + * Contents of FDI header. + */ + typedef struct FDIHeader { + char signature[kSignatureLen+1]; + char creator[kCreatorLen+1]; + // CR + LF + char comment[kCommentLen+1]; + // MS-DOS EOF + uint16_t version; + uint16_t lastTrack; + uint8_t lastHead; + uint8_t type; // DiskType enum + uint8_t rotSpeed; + uint8_t flags; + uint8_t tpi; + uint8_t headWidth; + uint16_t reserved; + // track descriptors follow, at byte 152 + } FDIHeader; + + /* + * Header for pulse-index streams track. + */ + typedef struct PulseIndexHeader { + long numPulses; + long avgStreamLen; + int avgStreamCompression; + long minStreamLen; + int minStreamCompression; + long maxStreamLen; + int maxStreamCompression; + long idxStreamLen; + int idxStreamCompression; + + uint32_t* avgStream; // 4 bytes/pulse + uint32_t* minStream; // 4 bytes/pulse; optional + uint32_t* maxStream; // 4 bytes/pulse; optional + uint32_t* idxStream; // 2 bytes/pulse; optional? + } PulseIndexHeader; + + enum { + kTrackDescrOffset = 152, + kMaxHeads = 2, + kMaxHeaderBlockTracks = 180, // max 90 double-sided cylinders + kMinHeaderLen = 512, + kMinVersion = 0x0200, // v2.0 + + kMaxNibbleTracks35 = 80, // 80 double-sided tracks + kNibbleBufLen = 10240, // max seems to be a little under 10K + kBitBufferSize = kNibbleBufLen + (kNibbleBufLen / 4), + + kMaxSectors35 = 12, // max #of sectors per track + //kBytesPerSector35 = 512, // bytes per sector on 3.5" disk + + kPulseStreamDataOffset = 16, // start of header to avg stream + + kBitRate525 = 250000, // 250Kbits/sec + }; + + /* meaning of the two-bit compression format value */ + typedef enum CompressedFormat { + kCompUncompressed = 0, + kCompHuffman = 1, + } CompressedFormat; + + /* node in the Huffman tree */ + typedef struct HuffNode { + uint16_t val; + struct HuffNode* left; + struct HuffNode* right; + } HuffNode; + + /* + * Keep a copy of the header around while we work. None of the formats + * we're interested in have more than kMaxHeaderBlockTracks tracks in + * them, so we don't need anything beyond the initial 512-byte header. + */ + uint8_t fHeaderBuf[kMinHeaderLen]; + + static void UnpackHeader(const uint8_t* headerBuf, FDIHeader* hdr); + static void DumpHeader(const FDIHeader* pHdr); + + DIError Unpack525(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls, + int numHeads); + DIError Unpack35(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls, + int numHeads, LinearBitmap** ppBadBlockMap); + DIError PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD); + + DIError UnpackDisk525(GenericFD* pGFD, GenericFD* pNewGFD, int numCyls, + int numHeads); + DIError UnpackDisk35(GenericFD* pGFD, GenericFD* pNewGFD, int numCyls, + int numHeads, LinearBitmap* pBadBlockMap); + void GetTrackInfo(int trk, int* pType, int* pLength256); + + int BitRate35(int trk); + void FixBadNibbles(uint8_t* nibbleBuf, long nibbleLen); + bool DecodePulseTrack(const uint8_t* inputBuf, long inputLen, + int bitRate, uint8_t* nibbleBuf, long* pNibbleLen); + bool UncompressPulseStream(const uint8_t* inputBuf, long inputLen, + uint32_t* outputBuf, long numPulses, int format, int bytesPerPulse); + bool ExpandHuffman(const uint8_t* inputBuf, long inputLen, + uint32_t* outputBuf, long numPulses); + const uint8_t* HuffExtractTree(const uint8_t* inputBuf, + HuffNode* pNode, uint8_t* pBits, uint8_t* pBitMask); + const uint8_t* HuffExtractValues16(const uint8_t* inputBuf, + HuffNode* pNode); + const uint8_t* HuffExtractValues8(const uint8_t* inputBuf, + HuffNode* pNode); + void HuffFreeNodes(HuffNode* pNode); + uint32_t HuffSignExtend16(uint32_t val); + uint32_t HuffSignExtend8(uint32_t val); + bool ConvertPulseStreamsToNibbles(PulseIndexHeader* pHdr, int bitRate, + uint8_t* nibbleBuf, long* pNibbleLen); + bool ConvertPulsesToBits(const uint32_t* avgStream, + const uint32_t* minStream, const uint32_t* maxStream, + const uint32_t* idxStream, int numPulses, int maxIndex, + int indexOffset, uint32_t totalAvg, int bitRate, + uint8_t* outputBuf, int* pOutputLen); + int MyRand(void); + bool ConvertBitsToNibbles(const uint8_t* bitBuffer, int bitCount, + uint8_t* nibbleBuf, long* pNibbleLen); + + + int fImageTracks; + char* fStorageName; + + + /* + * Data structure for managing nibble images with variable-length tracks. + */ + typedef struct { + int numTracks; // expect 35 or 40 for 5.25" + int length[kMaxNibbleTracks525]; + int offset[kMaxNibbleTracks525]; + } NibbleTrackInfo; + NibbleTrackInfo fNibbleTrackInfo; // count and lengths for variable formats + + // nibble images can have variable-length data fields + virtual int GetNibbleTrackLength(DiskImg::PhysicalFormat physical, int track) const + { + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(fNibbleTrackInfo.numTracks > 0); + + return fNibbleTrackInfo.length[track]; + } + virtual void SetNibbleTrackLength(int track, int length); + virtual int GetNibbleTrackOffset(DiskImg::PhysicalFormat physical, int track) const + { + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(fNibbleTrackInfo.numTracks > 0); + + return fNibbleTrackInfo.offset[track]; + } + virtual int GetNibbleNumTracks(void) const + { + return fNibbleTrackInfo.numTracks; + } +}; + + +class WrapperUnadornedNibble : public ImageWrapper { +public: + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return true; } +}; + +class WrapperUnadornedSector : public ImageWrapper { +public: + static DIError Test(GenericFD* pGFD, di_off_t wrappedLength); + virtual DIError Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) override; + virtual DIError Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) override; + virtual DIError Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) override; + virtual bool HasFastFlush(void) const override { return true; } +}; + + +/* + * =========================================================================== + * Non-FS DiskFSs + * =========================================================================== + */ + +/* + * A "raw" disk, i.e. no filesystem is known. Useful as a placeholder + * for applications that demand a DiskFS object even when the filesystem + * isn't known. + */ +class DISKIMG_API DiskFSUnknown : public DiskFS { +public: + DiskFSUnknown(void) : DiskFS() { + strcpy(fDiskVolumeName, "[Unknown]"); + strcpy(fDiskVolumeID, "Unknown FS"); + } + virtual ~DiskFSUnknown(void) {} + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) { + SetDiskImg(pImg); + return kDIErrNone; + } + + virtual const char* GetVolumeName(void) const override { return fDiskVolumeName; } + virtual const char* GetVolumeID(void) const override { return fDiskVolumeID; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + + // Use this if *something* is known about the filesystem, e.g. the + // partition type on a MacPart disk. + void SetVolumeInfo(const char* descr) { + if (strlen(descr) > kMaxVolumeName) + return; + + fDiskVolumeName[0] = '['; + strcpy(fDiskVolumeName+1, descr); + strcat(fDiskVolumeName, "]"); + strcpy(fDiskVolumeID, "Unknown FS - "); + strcat(fDiskVolumeID, descr); + } + +private: + enum { kMaxVolumeName = 64 }; + + char fDiskVolumeName[kMaxVolumeName+3]; + char fDiskVolumeID[kMaxVolumeName + 20]; +}; + + +/* + * Generic "container" DiskFS class. Contains some common functions shared + * among classes that are just containers for other filesystems. This class + * is not expected to be instantiated. + * + * TODO: create a common OpenSubVolume() function. + */ +class DISKIMG_API DiskFSContainer : public DiskFS { +public: + DiskFSContainer(void) : DiskFS() {} + virtual ~DiskFSContainer(void) {} + +protected: + virtual const char* GetDebugName(void) = 0; + virtual DIError CreatePlaceholder(long startBlock, long numBlocks, + const char* partName, const char* partType, + DiskImg** ppNewImg, DiskFS** ppNewFS); + virtual void SetVolumeUsageMap(void); +}; + +/* + * UNIDOS disk, an 800K floppy with two 400K DOS 3.3 volumes on it. + * + * The disk itself has no files; instead, it has two embedded sub-volumes. + */ +class DISKIMG_API DiskFSUNIDOS : public DiskFSContainer { +public: + DiskFSUNIDOS(void) : DiskFSContainer() {} + virtual ~DiskFSUNIDOS(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + static DIError TestWideFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[UNIDOS]"; } + virtual const char* GetVolumeID(void) const override { return "[UNIDOS]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "UNIDOS"; } + DIError Initialize(void); + DIError OpenSubVolume(int idx); +}; + +/* + * OzDOS disk, an 800K floppy with two 400K DOS 3.3 volumes on it. They + * put the files for disk 1 in the odd sectors and the files for disk 2 + * in the even sectors (the top and bottom halves of a 512-byte block). + * + * The disk itself has no files; instead, it has two embedded sub-volumes. + * Because of the funky layout, we have to use the "sector pairing" feature + * of DiskImg to treat this like a DOS 3.3 disk. + */ +class DISKIMG_API DiskFSOzDOS : public DiskFSContainer { +public: + DiskFSOzDOS(void) : DiskFSContainer() {} + virtual ~DiskFSOzDOS(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[OzDOS]"; } + virtual const char* GetVolumeID(void) const override { return "[OzDOS]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "OzDOS"; } + DIError Initialize(void); + DIError OpenSubVolume(int idx); +}; + +/* + * CFFA volume. A potentially very large volume with multiple partitions. + * + * This DiskFS is just a container that describes the position and sizes + * of the sub-volumes. + */ +class DISKIMG_API DiskFSCFFA : public DiskFSContainer { +public: + DiskFSCFFA(void) : DiskFSContainer() {} + virtual ~DiskFSCFFA(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[CFFA]"; } + virtual const char* GetVolumeID(void) const override { return "[CFFA]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "CFFA"; } + + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder, + DiskImg::FSFormat* pFormatFound); + static DIError OpenSubVolume(DiskImg* pImg, long startBlock, + long numBlocks, bool scanOnly, DiskImg** ppNewImg, DiskFS** ppNewFS); + DIError Initialize(void); + DIError FindSubVolumes(void); + DIError AddVolumeSeries(int start, int count, long blocksPerVolume, + long& startBlock, long& totalBlocksLeft); + + enum { + kMinInterestingBlocks = 65536 + 1024, // less than this, ignore + kEarlyVolExpectedSize = 65536, // 32MB in 512-byte blocks + kOneGB = 1024*1024*(1024/512), // 1GB in 512-byte blocks + }; +}; + + +/* + * Macintosh-style partitioned disk image. + * + * This DiskFS is just a container that describes the position and sizes + * of the sub-volumes. + */ +class DISKIMG_API DiskFSMacPart : public DiskFSContainer { +public: + DiskFSMacPart(void) : DiskFSContainer() {} + virtual ~DiskFSMacPart(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[MacPartition]"; } + virtual const char* GetVolumeID(void) const override { return "[MacPartition]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "MacPart"; } + + struct PartitionMap; // fwd + struct DriverDescriptorRecord; // fwd + static void UnpackDDR(const uint8_t* buf, + DriverDescriptorRecord* pDDR); + static void DumpDDR(const DriverDescriptorRecord* pDDR); + static void UnpackPartitionMap(const uint8_t* buf, + PartitionMap* pMap); + static void DumpPartitionMap(long block, const PartitionMap* pMap); + + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder); + DIError OpenSubVolume(const PartitionMap* pMap); + DIError Initialize(void); + DIError FindSubVolumes(void); + + enum { + kMinInterestingBlocks = 2048, // less than this, ignore + kDDRSignature = 0x4552, // 'ER' + kPartitionSignature = 0x504d, // 'PM' + }; +}; + + +/* + * Partitioning for Joachim Lange's MicroDrive card. + * + * This DiskFS is just a container that describes the position and sizes + * of the sub-volumes. + */ +class DISKIMG_API DiskFSMicroDrive : public DiskFSContainer { +public: + DiskFSMicroDrive(void) : DiskFSContainer() {} + virtual ~DiskFSMicroDrive(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[MicroDrive]"; } + virtual const char* GetVolumeID(void) const override { return "[MicroDrive]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "MicroDrive"; } + + struct PartitionMap; // fwd + static void UnpackPartitionMap(const uint8_t* buf, + PartitionMap* pMap); + static void DumpPartitionMap(const PartitionMap* pMap); + + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder); + DIError OpenSubVolume(long startBlock, long numBlocks); + DIError OpenVol(int idx, long startBlock, long numBlocks); + DIError Initialize(void); + DIError FindSubVolumes(void); + + enum { + kMinInterestingBlocks = 2048, // less than this, ignore + kPartitionSignature = 0xccca, // 'JL' in little-endian high-ASCII + }; +}; + + +/* + * Partitioning for Parsons Engineering FocusDrive card. + * + * This DiskFS is just a container that describes the position and sizes + * of the sub-volumes. + */ +class DISKIMG_API DiskFSFocusDrive : public DiskFSContainer { +public: + DiskFSFocusDrive(void) : DiskFSContainer() {} + virtual ~DiskFSFocusDrive(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "[FocusDrive]"; } + virtual const char* GetVolumeID(void) const override { return "[FocusDrive]"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + virtual const char* GetDebugName(void) override { return "FocusDrive"; } + + struct PartitionMap; // fwd + static void UnpackPartitionMap(const uint8_t* buf, + const uint8_t* nameBuf, PartitionMap* pMap); + static void DumpPartitionMap(const PartitionMap* pMap); + + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder); + DIError OpenSubVolume(long startBlock, long numBlocks, + const char* name); + DIError OpenVol(int idx, long startBlock, long numBlocks, + const char* name); + DIError Initialize(void); + DIError FindSubVolumes(void); + + enum { + kMinInterestingBlocks = 2048, // less than this, ignore + }; +}; + + +/* + * =========================================================================== + * DOS 3.2/3.3 + * =========================================================================== + */ + +class A2FileDOS; + +/* + * DOS 3.2/3.3 disk. + */ +class DISKIMG_API DiskFSDOS33 : public DiskFS { +public: + DiskFSDOS33(void) : + DiskFS(), + fFirstCatTrack(0), + fFirstCatSector(0), + fVTOCVolumeNumber(0), + fVTOCNumTracks(0), + fVTOCNumSectors(0), + fDiskVolumeNum(0), + fDiskVolumeName(), + fDiskVolumeID(), + fVTOC(), + fVTOCLoaded(false), + fCatalogSectors(), + fDiskIsGood(false) + {} + virtual ~DiskFSDOS33(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(initMode); + } + virtual DIError Format(DiskImg* pDiskImg, const char* volName) override; + + virtual const char* GetVolumeName(void) const override { return fDiskVolumeName; } + virtual const char* GetVolumeID(void) const override { return fDiskVolumeID; } + virtual const char* GetBareVolumeName(void) const override { + // this is fragile -- skip over the "DOS" part, return 3 digits + assert(strlen(fDiskVolumeName) > 3); + return fDiskVolumeName+3; + } + virtual bool GetReadWriteSupported(void) const override { return true; } + virtual bool GetFSDamaged(void) const override { return !fDiskIsGood; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override; + virtual DIError NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) override; + virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile) override; + virtual DIError DeleteFile(A2File* pFile) override; + virtual DIError RenameFile(A2File* pFile, const char* newName) override; + virtual DIError SetFileInfo(A2File* pFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) override; + virtual DIError RenameVolume(const char* newName) override; + + /* + * Unique to DOS 3.3 disks. + */ + int GetDiskVolumeNum(void) const { return fDiskVolumeNum; } + void SetDiskVolumeNum(int val); + + static bool IsValidFileName(const char* name); + static bool IsValidVolumeName(const char* name); + + // utility function + static void LowerASCII(uint8_t* buf, long len); + //static void ReplaceFssep(char* str, char replacement); + + enum { + kMinTracks = 17, // need to put the catalog track here + kMaxTracks = 50, + kMaxCatalogSectors = 64, // two tracks on a 32-sector disk + }; + + /* a T/S pair */ + typedef struct TrackSector { + char track; + char sector; + } TrackSector; + + friend class A2FDDOS; // for Write + +private: + DIError Initialize(InitMode initMode); + DIError ReadVTOC(void); + void UpdateVolumeNum(void); + void DumpVTOC(void); + void SetSectorUsage(long track, long sector, + VolumeUsage::ChunkPurpose purpose); + void FixVolumeUsageMap(void); + DIError ReadCatalog(void); + DIError ProcessCatalogSector(int catTrack, int catSect, + const uint8_t* sctBuf); + DIError GetFileLengths(void); + DIError ComputeLength(A2FileDOS* pFile, const TrackSector* tsList, + int tsCount); + DIError TrimLastSectorUp(A2FileDOS* pFile, TrackSector lastTS); + void MarkFileUsage(A2FileDOS* pFile, TrackSector* tsList, int tsCount, + TrackSector* indexList, int indexCount); + //DIError TrimLastSectorDown(A2FileDOS* pFile, uint16_t* tsBuf, + // int maxZeroCount); + void DoNormalizePath(const char* name, char fssep, char* outBuf); + DIError MakeFileNameUnique(char* fileName); + DIError GetFreeCatalogEntry(TrackSector* pCatSect, int* pCatEntry, + uint8_t* sctBuf, A2FileDOS** ppPrevEntry); + void CreateDirEntry(uint8_t* sctBuf, int catEntry, + const char* fileName, TrackSector* pTSSect, uint8_t fileType, + int access); + void FreeTrackSectors(TrackSector* pList, int count); + + bool CheckDiskIsGood(void); + + DIError WriteDOSTracks(int sectPerTrack); + + DIError ScanVolBitmap(void); + DIError LoadVolBitmap(void); + DIError SaveVolBitmap(void); + void FreeVolBitmap(void); + DIError AllocSector(TrackSector* pTS); + DIError CreateEmptyBlockMap(bool withDOS); + bool GetSectorUseEntry(long track, int sector) const; + void SetSectorUseEntry(long track, int sector, bool inUse); + inline uint32_t GetVTOCEntry(const uint8_t* pVTOC, long track) const; + + // Largest interesting volume is 400K (50 tracks, 32 sectors), but + // we may be looking at it in 16-sector mode, so max tracks is 100. + enum { + kMaxInterestingTracks = 100, + kSectorSize = 256, + kDefaultVolumeNum = 254, + kMaxExtensionLen = 4, // used when normalizing; ".gif" is 4 + }; + + // DOS track images, for initializing disk images + static const uint8_t gDOS33Tracks[]; + static const uint8_t gDOS32Tracks[]; + + /* some fields from the VTOC */ + int fFirstCatTrack; + int fFirstCatSector; + int fVTOCVolumeNumber; + int fVTOCNumTracks; + int fVTOCNumSectors; + + /* private data */ + int fDiskVolumeNum; // usually 254 + char fDiskVolumeName[7]; // "DOS" + num, e.g. "DOS001", "DOS254" + char fDiskVolumeID[32]; // sizeof "DOS 3.3 Volume " +3 +1 + uint8_t fVTOC[kSectorSize]; + bool fVTOCLoaded; + + /* + * There are some things we need to be careful of when reading the + * catalog track, like bad links and infinite loops. By storing a list + * of known good catalog sectors, we only have to handle that stuff once. + * The catalog doesn't grow or shrink, so this never needs to be updated. + */ + TrackSector fCatalogSectors[kMaxCatalogSectors]; + + bool fDiskIsGood; +}; + +/* + * File descriptor for an open DOS file. + */ +class DISKIMG_API A2FDDOS : public A2FileDescr { +public: + A2FDDOS(A2File* pFile) : + A2FileDescr(pFile), + fTSList(NULL), + fTSCount(0), + fIndexList(NULL), + fIndexCount(0), + fOffset(0), + fOpenEOF(0), + fOpenSectorsUsed(0), + fModified(false) + {} + virtual ~A2FDDOS(void) { + delete[] fTSList; + delete[] fIndexList; + //fTSList = fIndexList = NULL; + } + + //typedef DiskFSDOS33::TrackSector TrackSector; + + friend class A2FileDOS; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + typedef DiskFSDOS33::TrackSector TrackSector; + + TrackSector* fTSList; // T/S entries for data sectors + int fTSCount; + TrackSector* fIndexList; // T/S entries for T/S list sectors + int fIndexCount; + di_off_t fOffset; // current position in file + + di_off_t fOpenEOF; // how big the file currently is + long fOpenSectorsUsed; // how many sectors it occupies + bool fModified; // if modified, update stuff on Close + + void DumpTSList(void) const; +}; + +/* + * Holds DOS files. Works for DOS33, DOS32, and "wide" DOS implementations. + * + * The embedded address and length fields found in Applesoft, Integer, and + * Binary files are quietly skipped over with the fDataOffset field when + * files are read. + * + * THOUGHT: have "get filename" and "get raw filename" interfaces? There + * are no directories, so maybe we don't care about "raw pathname"?? Might + * be better to always return the "raw" value and let the caller deal with + * things like high ASCII. + */ +class DISKIMG_API A2FileDOS : public A2File { +public: + A2FileDOS(DiskFS* pDiskFS); + virtual ~A2FileDOS(void); + + // assorted constants + enum { + kMaxFileName = 30, + }; + typedef enum { + kTypeUnknown = -1, + kTypeText = 0x00, // 'T' + kTypeInteger = 0x01, // 'I' + kTypeApplesoft = 0x02, // 'A' + kTypeBinary = 0x04, // 'B' + kTypeS = 0x08, // 'S' + kTypeReloc = 0x10, // 'R' + kTypeA = 0x20, // 'A' + kTypeB = 0x40, // 'B' + + kTypeLocked = 0x80 // bitwise OR with previous values + } FileType; + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual const char* GetRawFileName(size_t* size = NULL) const override; + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override; + virtual uint32_t GetAuxType(void) const override { return fAuxType; } + virtual uint32_t GetAccess(void) const override; + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override { return 0; } + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fSparseLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + void Dump(void) const; + + friend class DiskFSDOS33; + friend class A2FDDOS; + +private: + typedef DiskFSDOS33::TrackSector TrackSector; + + /* + * Contents of directory entry. + * + * We don't hold deleted or unused entries, so fTSListTrack is always + * valid. + */ + short fTSListTrack; // (could use TrackSector here) + short fTSListSector; + uint16_t fLengthInSectors; + bool fLocked; + char fRawFileName[kMaxFileName + 1]; // "raw" version + char fFileName[kMaxFileName+1]; // "fixed" version + FileType fFileType; + + TrackSector fCatTS; // track/sector for our catalog entry + int fCatEntryNum; // entry number within cat sector + + // these are computed or determined from the file contents + uint16_t fAuxType; // addr for bin, etc. + uint16_t fDataOffset; // 0/2/4, for 'A'/'B'/'I' with embedded len + di_off_t fLength; // file length, in bytes + di_off_t fSparseLength; // file length, factoring sparse out + + void FixFilename(void); + + DIError LoadTSList(TrackSector** pTSList, int* pTSCount, + TrackSector** pIndexList = NULL, int* pIndexCount = NULL); + static FileType ConvertFileType(long prodosType, di_off_t fileLen); + static bool IsValidType(long prodosType); + static void MakeDOSName(char* buf, const char* name); + static void TrimTrailingSpaces(char* filename); + + DIError ExtractTSPairs(const uint8_t* sctBuf, TrackSector* tsList, + int* pLastNonZero); + + A2FDDOS* fpOpenFile; +}; + + +/* + * =========================================================================== + * ProDOS + * =========================================================================== + */ + +class A2FileProDOS; + +/* + * ProDOS disk. + * + * THOUGHT: it would be undesirable for the CiderPress UI, but it would + * make things somewhat easier internally if we treated the volume dir + * like a subdirectory under which everything else sits, instead of special- + * casing it like we do. This is awkward because volume dirs have names + * under ProDOS, giving every pathname an extra component that they don't + * really need. We can never treat the volume dir purely as a subdir, + * because it can't expand beyond 51 files, but the storage_type in the + * header is sufficient to identify it as such (assuming the disk isn't + * broken). Certain operations, such as changing the file type or aux type, + * simply aren't possible on a volume dir, and deleting a volume dir doesn't + * make sense. So in some respects we simply trade one kind of special-case + * behavior for another. + */ +class DISKIMG_API DiskFSProDOS : public DiskFS { +public: + DiskFSProDOS(void) : + fVolumeName(), + fVolumeID(), + fAccess(0), + fCreateWhen(0), + fModWhen(0), + fBitMapPointer(0), + fTotalBlocks(0), + fVolDirFileCount(0), + fBlockUseMap(NULL), + fDiskIsGood(false), + fEarlyDamage(false) + {} + virtual ~DiskFSProDOS(void) { + if (fBlockUseMap != NULL) { + assert(false); // unexpected + delete[] fBlockUseMap; + } + } + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(initMode); + } + virtual DIError Format(DiskImg* pDiskImg, const char* volName) override; + virtual DIError NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) override; + virtual DIError CreateFile(const CreateParms* pParms, + A2File** ppNewFile) override; + virtual DIError DeleteFile(A2File* pFile) override; + virtual DIError RenameFile(A2File* pFile, const char* newName) override; + virtual DIError SetFileInfo(A2File* pFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) override; + virtual DIError RenameVolume(const char* newName) override; + + // assorted constants + enum { + kMaxVolumeName = 15, + }; + typedef uint32_t ProDate; + + virtual const char* GetVolumeName(void) const override { return fVolumeName; } + virtual const char* GetVolumeID(void) const override { return fVolumeID; } + virtual const char* GetBareVolumeName(void) const override { return fVolumeName; } + virtual bool GetReadWriteSupported(void) const override { return true; } + virtual bool GetFSDamaged(void) const override { return !fDiskIsGood; } + virtual long GetFSNumBlocks(void) const override { return fTotalBlocks; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override; + + //A2FileProDOS* GetVolDir(void) const { return fpVolDir; } + + static bool IsValidFileName(const char* name); + static bool IsValidVolumeName(const char* name); + static uint16_t GenerateLowerCaseBits(const char* upperName, + const char* lowerName, bool forAppleWorks); + static void GenerateLowerCaseName(const char* upperName, + char* lowerNameNoTerm, uint16_t lcFlags, bool fromAppleWorks); + + friend class A2FDProDOS; + +private: + struct DirHeader; + + enum { kMaxExtensionLen = 4 }; // used when normalizing; ".gif" is 4 + + DIError Initialize(InitMode initMode); + DIError LoadVolHeader(void); + DIError DetermineVolDirLen(uint16_t nextBlock, uint16_t* pBlocksUsed); + void SetVolumeID(void); + void DumpVolHeader(void); + DIError ScanVolBitmap(void); + DIError LoadVolBitmap(void); + DIError SaveVolBitmap(void); + void FreeVolBitmap(void); + long AllocBlock(void); + int GetNumBitmapBlocks(void) const { + /* use fTotalBlocks rather than GetNumBlocks() */ + assert(fTotalBlocks > 0); + const int kBitsPerBlock = 512 * 8; + int numBlocks = (fTotalBlocks + kBitsPerBlock-1) / kBitsPerBlock; + return numBlocks; + } + DIError CreateEmptyBlockMap(void); + bool GetBlockUseEntry(long block) const; + void SetBlockUseEntry(long block, bool inUse); + bool ScanForExtraEntries(void) const; + + void SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose); + DIError GetDirHeader(const uint8_t* blkBuf, DirHeader* pHeader); + DIError RecursiveDirAdd(A2File* pParent, uint16_t dirBlock, + const char* basePath, int depth); + DIError SlurpEntries(A2File* pParent, const DirHeader* pHeader, + const uint8_t* blkBuf, bool skipFirst, int* pCount, + const char* basePath, uint16_t thisBlock, int depth); + DIError ReadExtendedInfo(A2FileProDOS* pFile); + DIError ScanFileUsage(void); + void ScanBlockList(long blockCount, uint16_t* blockList, + long indexCount, uint16_t* indexList, long* pSparseCount); + DIError ScanForSubVolumes(void); + DIError FindSubVolume(long blockStart, long blockCount, + DiskImg** ppDiskImg, DiskFS** ppDiskFS); + void MarkSubVolumeBlocks(long block, long count); + + A2File* FindFileByKeyBlock(A2File* pStart, uint16_t keyBlock); + DIError AllocInitialFileStorage(const CreateParms* pParms, + const char* upperName, uint16_t dirBlock, int dirEntrySlot, + long* pKeyBlock, int* pBlocksUsed, int* pNewEOF); + DIError WriteBootBlocks(void); + DIError DoNormalizePath(const char* path, char fssep, + char** pNormalizedPath); + void UpperCaseName(char* upperName, const char* name); + bool CheckDiskIsGood(void); + DIError AllocDirEntry(A2FileDescr* pOpenSubdir, uint8_t** ppDir, + long* pDirLen, uint8_t** ppDirEntry, uint16_t* pDirKeyBlock, + int* pDirEntrySlot, uint16_t* pDirBlock); + uint8_t* GetPrevDirEntry(uint8_t* buf, uint8_t* ptr); + DIError MakeFileNameUnique(const uint8_t* dirBuf, long dirLen, + char* fileName); + bool NameExistsInDir(const uint8_t* dirBuf, long dirLen, + const char* fileName); + + DIError FreeBlocks(long blockCount, uint16_t* blockList); + DIError RegeneratePathName(A2FileProDOS* pFile); + + /* some items from the volume header */ + char fVolumeName[kMaxVolumeName+1]; + char fVolumeID[kMaxVolumeName + 16]; // add "ProDOS /" + uint8_t fAccess; + ProDate fCreateWhen; + ProDate fModWhen; + uint16_t fBitMapPointer; + uint16_t fTotalBlocks; + //uint16_t fPrevBlock; + //uint16_t fNextBlock; + //uint8_t fVersion; + //uint8_t fMinVersion; + //uint8_t fEntryLength; + //uint8_t fEntriesPerBlock; + uint16_t fVolDirFileCount; + +// A2FileProDOS* fpVolDir; // a "fake" file entry for the volume dir + + /* + * This is a working copy of the block use map from blocks 6+. It should + * be loaded when we're about to modify files on the disk and freed + * immediately afterward. The goal is to facilitate speedy updates to the + * bitmap without creating problems if the application decides to modify + * one of the bitmap blocks directly (e.g. with the disk sector editor). + * It should never be held across calls. + */ + uint8_t* fBlockUseMap; + + /* + * Set this if the disk is "perfect". If it's not, we disallow write + * access for safety reasons. + */ + bool fDiskIsGood; + + /* set if something fixes damage so CheckDiskIsGood can't see it */ + bool fEarlyDamage; +}; + +/* + * File descriptor for an open ProDOS file. + * + * This only represents one fork. + */ +class DISKIMG_API A2FDProDOS : public A2FileDescr { +public: + A2FDProDOS(A2File* pFile) : + A2FileDescr(pFile), + fModified(false), + fBlockCount(0), + fBlockList(NULL), + fOpenEOF(0), + fOpenBlocksUsed(0), + fOpenStorageType(0), + fOpenRsrcFork(false), + fOffset(0) + {} + virtual ~A2FDProDOS(void) { + delete[] fBlockList; + fBlockList = NULL; + } + + friend class A2FileProDOS; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + + void DumpBlockList(void) const; + +private: + bool IsEmptyBlock(const uint8_t* blk); + DIError WriteDirectory(const void* buf, size_t len, size_t* pActual); + + /* state for open files */ + bool fModified; + long fBlockCount; + uint16_t* fBlockList; + di_off_t fOpenEOF; // current EOF + uint16_t fOpenBlocksUsed; // #of block used by open piece + int fOpenStorageType; + bool fOpenRsrcFork; // is this the resource fork? + di_off_t fOffset; // current file offset +}; + +/* + * Holds a ProDOS file. + */ +class DISKIMG_API A2FileProDOS : public A2File { +public: + A2FileProDOS(DiskFS* pDiskFS) : + A2File(pDiskFS), + fParentDirBlock(0), + fParentDirIdx(0), + fSparseDataEof(0), + fSparseRsrcEof(0), + fPathName(NULL), + fpOpenFile(NULL), + fpParent(NULL) + {} + virtual ~A2FileProDOS(void) { + delete fpOpenFile; + delete[] fPathName; + } + + typedef DiskFSProDOS::ProDate ProDate; + + /* assorted constants */ + enum { + kMaxFileName = 15, + kFssep = ':', + kInvalidBlockNum = 1, // boot block, can't be in file + kMaxBlocksPerIndex = 256, + }; + /* ProDOS access permissions */ + enum { + kAccessRead = 0x01, + kAccessWrite = 0x02, + kAccessInvisible = 0x04, + kAccessBackup = 0x20, + kAccessRename = 0x40, + kAccessDelete = 0x80 + }; + /* contents of a directory entry */ + typedef struct DirEntry { + int storageType; + char fileName[kMaxFileName+1]; // shows lower case + uint8_t fileType; + uint16_t keyPointer; + uint16_t blocksUsed; + uint32_t eof; + ProDate createWhen; + uint8_t version; + uint8_t minVersion; + uint8_t access; + uint16_t auxType; + ProDate modWhen; + uint16_t headerPointer; + } DirEntry; + typedef struct ExtendedInfo { + uint8_t storageType; + uint16_t keyBlock; + uint16_t blocksUsed; + uint32_t eof; + } ExtendedInfo; + typedef enum StorageType { + kStorageDeleted = 0, /* indicates deleted file */ + kStorageSeedling = 1, /* <= 512 bytes */ + kStorageSapling = 2, /* < 128KB */ + kStorageTree = 3, /* < 16MB */ + kStoragePascalVolume = 4, /* see ProDOS technote 25 */ + kStorageExtended = 5, /* forked */ + kStorageDirectory = 13, + kStorageSubdirHeader = 14, + kStorageVolumeDirHeader = 15, + } StorageType; + + static bool IsRegularFile(int type) { + return (type == kStorageSeedling || type == kStorageSapling || + type == kStorageTree); + } + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fDirEntry.fileName; } + virtual const char* GetPathName(void) const override { return fPathName; } + virtual char GetFssep(void) const override { return kFssep; } + virtual uint32_t GetFileType(void) const override { return fDirEntry.fileType; } + virtual uint32_t GetAuxType(void) const override { return fDirEntry.auxType; } + virtual uint32_t GetAccess(void) const override { return fDirEntry.access; } + virtual time_t GetCreateWhen(void) const override; + virtual time_t GetModWhen(void) const override; + virtual di_off_t GetDataLength(void) const override { + if (GetQuality() == kQualityDamaged) + return 0; + if (fDirEntry.storageType == kStorageExtended) + return fExtData.eof; + else + return fDirEntry.eof; + } + virtual di_off_t GetRsrcLength(void) const override { + if (fDirEntry.storageType == kStorageExtended) { + if (GetQuality() == kQualityDamaged) + return 0; + else + return fExtRsrc.eof; + } else + return -1; + } + virtual di_off_t GetDataSparseLength(void) const override { + if (GetQuality() == kQualityDamaged) + return 0; + else + return fSparseDataEof; + } + virtual di_off_t GetRsrcSparseLength(void) const override { + if (GetQuality() == kQualityDamaged) + return 0; + else + return fSparseRsrcEof; + } + virtual bool IsDirectory(void) const override { + return (fDirEntry.storageType == kStorageDirectory || + fDirEntry.storageType == kStorageVolumeDirHeader); + } + virtual bool IsVolumeDirectory(void) const override { + return (fDirEntry.storageType == kStorageVolumeDirHeader); + } + + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + virtual void SetParent(A2File* pParent) override { fpParent = pParent; } + virtual A2File* GetParent(void) const override { return fpParent; } + + static char NameToLower(char ch); + static void InitDirEntry(DirEntry* pEntry, const uint8_t* entryBuf); + + virtual void Dump(void) const override; + + /* directory entry contents for this file */ + DirEntry fDirEntry; + + /* pointer to directory entry (update dir if file size or dates change) */ + uint16_t fParentDirBlock; // directory block + int fParentDirIdx; // index in dir block + + /* these are only valid if storageType == kStorageExtended */ + ExtendedInfo fExtData; + ExtendedInfo fExtRsrc; + + void SetPathName(const char* basePath, const char* fileName); + static time_t ConvertProDate(ProDate proDate); + static ProDate ConvertProDate(time_t unixDate); + + /* returns "true" if AppleWorks aux type is used for lower-case name */ + static bool UsesAppleWorksAuxType(uint8_t fileType) { + return (fileType >= 0x19 && fileType <= 0x1b); + } + +#if 0 + /* change fPathName; should only be used by DiskFS rename */ + void SetPathName(const char* name) { + delete[] fPathName; + if (name == NULL) { + fPathName = NULL; + } else { + fPathName = new char[strlen(name)+1]; + if (fPathName != NULL) + strcpy(fPathName, name); + } + } +#endif + + DIError LoadBlockList(int storageType, uint16_t keyBlock, + long eof, long* pBlockCount, uint16_t** pBlockList, + long* pIndexBlockCount=NULL, uint16_t** pIndexBlockList=NULL); + DIError LoadDirectoryBlockList(uint16_t keyBlock, + long eof, long* pBlockCount, uint16_t** pBlockList); + + /* fork lengths without sparseness */ + di_off_t fSparseDataEof; + di_off_t fSparseRsrcEof; + +private: + DIError LoadIndexBlock(uint16_t block, uint16_t* list, + int maxCount); + DIError ValidateBlockList(const uint16_t* list, long count); + + char* fPathName; // full pathname to file on this volume + + A2FDProDOS* fpOpenFile; // only one fork can be open at a time + A2File* fpParent; +}; + + +/* + * =========================================================================== + * Pascal + * =========================================================================== + */ + +/* + * Pascal disk. + * + * There is no allocation map or file index blocks, just a linear collection + * of files with contiguous blocks. + */ +class A2FilePascal; + +class DISKIMG_API DiskFSPascal : public DiskFS { +public: + DiskFSPascal(void) : + fStartBlock(0), + fNextBlock(0), + fVolumeName(), + fVolumeID(), + fTotalBlocks(0), + fNumFiles(0), + fAccessWhen(0), + fDateSetWhen(0), + fStuff1(0), + fStuff2(0), + fDiskIsGood(false), + fEarlyDamage(false), + fDirectory(NULL) + {} + virtual ~DiskFSPascal(void) { + if (fDirectory != NULL) { + assert(false); // unexpected + delete[] fDirectory; + } + } + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + virtual DIError Format(DiskImg* pDiskImg, const char* volName) override; + + // assorted constants + enum { + kMaxVolumeName = 7, + kDirectoryEntryLen = 26, + }; + typedef uint16_t PascalDate; + + virtual const char* GetVolumeName(void) const override { return fVolumeName; } + virtual const char* GetVolumeID(void) const override { return fVolumeID; } + virtual const char* GetBareVolumeName(void) const override { return fVolumeName; } + virtual bool GetReadWriteSupported(void) const override { return true; } + virtual bool GetFSDamaged(void) const override { return !fDiskIsGood; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override; + virtual DIError NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) override; + virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile) override; + virtual DIError DeleteFile(A2File* pFile) override; + virtual DIError RenameFile(A2File* pFile, const char* newName) override; + virtual DIError SetFileInfo(A2File* pFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) override; + virtual DIError RenameVolume(const char* newName) override; + + static bool IsValidVolumeName(const char* name); + static bool IsValidFileName(const char* name); + + uint16_t GetTotalBlocks(void) const { return fTotalBlocks; } + + friend class A2FDPascal; + +private: + DIError Initialize(void); + DIError LoadVolHeader(void); + void SetVolumeID(void); + void DumpVolHeader(void); + DIError LoadCatalog(void); + DIError SaveCatalog(void); + void FreeCatalog(void); + DIError ProcessCatalog(void); + DIError ScanFileUsage(void); + void SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose); + DIError WriteBootBlocks(void); + bool CheckDiskIsGood(void); + void DoNormalizePath(const char* name, char fssep, char* outBuf); + DIError MakeFileNameUnique(char* fileName); + DIError FindLargestFreeArea(int *pPrevIdx, A2FilePascal** ppPrevFile); + uint8_t* FindDirEntry(A2FilePascal* pFile); + + enum { kMaxExtensionLen = 5 }; // used when normalizing; ".code" is 4 + + /* some items from the volume header */ + uint16_t fStartBlock; // first block of dir hdr; always 2 + uint16_t fNextBlock; // i.e. first block with data + char fVolumeName[kMaxVolumeName+1]; + char fVolumeID[kMaxVolumeName + 16]; // add "Pascal ___:" + uint16_t fTotalBlocks; + uint16_t fNumFiles; + PascalDate fAccessWhen; // PascalDate last access + PascalDate fDateSetWhen; // PascalDate last date setting + uint16_t fStuff1; // + uint16_t fStuff2; // + + /* other goodies */ + bool fDiskIsGood; + bool fEarlyDamage; + + /* + * Pascal disks have one fixed-size directory. The contents aren't + * divided into blocks, which means you can't always edit an entry + * by loading a single block from disk and writing it back. Also, + * deleted entries are squeezed out, so if we delete an entry we + * have to reshuffle the entries below it. + * + * We want to keep the copy on disk synced up, so we don't hold on + * to this longer than necessary. Possibly less efficient that way; + * if it becomes a problem it's easy enough to change the behavior. + */ + uint8_t* fDirectory; +}; + +/* + * File descriptor for an open Pascal file. + */ +class DISKIMG_API A2FDPascal : public A2FileDescr { +public: + A2FDPascal(A2File* pFile) : + A2FileDescr(pFile), + fOffset(0), + fOpenEOF(0), + fOpenBlocksUsed(0), + fModified(0) + {} + virtual ~A2FDPascal(void) { + /* nothing to clean up */ + } + + friend class A2FilePascal; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + di_off_t fOffset; // where we are + di_off_t fOpenEOF; // how big the file currently is + long fOpenBlocksUsed; // how many blocks it occupies + bool fModified; // if modified, update dir on Close +}; + +/* + * File on a Pascal disk. + */ +class DISKIMG_API A2FilePascal : public A2File { +public: + A2FilePascal(DiskFS* pDiskFS) : + A2File(pDiskFS), + fStartBlock(0), + fNextBlock(0), + fFileType(A2FilePascal::FileType::kTypeUntyped), + fFileName(), + fBytesRemaining(0), + fModWhen(0), + fLength(0), + fpOpenFile(NULL) + {} + virtual ~A2FilePascal(void) { + /* this comes back and calls CloseDescr */ + if (fpOpenFile != NULL) + fpOpenFile->Close(); + } + + typedef DiskFSPascal::PascalDate PascalDate; + + // assorted constants + enum { + kMaxFileName = 15, + }; + typedef enum FileType { + kTypeUntyped = 0, // NON + kTypeXdsk = 1, // BAD (bad blocks) + kTypeCode = 2, // PCD + kTypeText = 3, // PTX + kTypeInfo = 4, // ? + kTypeData = 5, // PDA + kTypeGraf = 6, // ? + kTypeFoto = 7, // FOT? (hires image) + kTypeSecurdir = 8 // ?? + } FileType; + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override; + virtual uint32_t GetAuxType(void) const override { return 0; } + virtual uint32_t GetAccess(void) const override { return DiskFS::kFileAccessUnlocked; } + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override; + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** pOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + + virtual void Dump(void) const override; + + static time_t ConvertPascalDate(PascalDate pascalDate); + static A2FilePascal::PascalDate ConvertPascalDate(time_t unixDate); + static A2FilePascal::FileType ConvertFileType(long prodosType); + + /* fields pulled out of directory block */ + uint16_t fStartBlock; + uint16_t fNextBlock; + FileType fFileType; + char fFileName[kMaxFileName+1]; + uint16_t fBytesRemaining; + PascalDate fModWhen; + + /* derived fields */ + di_off_t fLength; + + /* note to self: don't try to store a directory offset here; they shift + every time you add or delete a file */ + +private: + A2FileDescr* fpOpenFile; +}; + + +/* + * =========================================================================== + * CP/M + * =========================================================================== + */ + +/* + * CP/M disk. + * + * We really ought to be using 1K blocks here, since that's the native + * CP/M format, but there's little value in making an exception for such + * a rarely used Apple II format. + * + * There is no allocation map or file index blocks, just a single 2K + * directory filled with files that have up to 16 1K blocks each. If + * a file is longer than 16K, a second entry with the identical name + * and user number is made. These "extents" may be sparse, so it's + * necessary to use the "records" field to determine the actual file length. + */ +class A2FileCPM; +class DISKIMG_API DiskFSCPM : public DiskFS { +public: + DiskFSCPM(void) : fDirEntry(), fDiskIsGood(false) {} + virtual ~DiskFSCPM(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return "CP/M"; } + virtual const char* GetVolumeID(void) const override { return "CP/M"; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return !fDiskIsGood; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + + // assorted constants + enum { + kDirectoryEntryLen = 32, + kVolDirBlock = 24, // ProDOS block where volume dir starts + kDirFileNameLen = 11, // 8+3 without the '.' + kFullDirSize = 2048, // blocks 0 and 1 + kDirEntryBlockCount = 16, // #of blocks held in dir slot + kNumDirEntries = kFullDirSize/kDirectoryEntryLen, + kExtentsInLowByte = 32, + + kDirEntryFlagContinued = 0x8000, // "flags" word + }; + + // Contents of the raw 32-byte directory entry. + // + // From http://www.seasip.demon.co.uk/Cpm/format31.html + // + // UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... + // AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ + // + // If the high bit of T1 is set, the file is read-only. If the high + // bit of T2 is set, the file is a "system" file. + // + // An entry with UU=0x20 indicates a CP/M 3.1 disk label entry. + // An entry with UU=0x21 indicates a time stamp entry (2.x or 3.x). + // + // Files larger than (1024 * 16) have multiple "extent" entries, i.e. + // entries with the same user number and file name. + typedef struct DirEntry { + uint8_t userNumber; // 0-15 or 0-31 (usually 0), e5=unused + uint8_t fileName[kDirFileNameLen+1]; + uint16_t extent; // extent (EX + S2 * 32) + uint8_t S1; // Last Record Byte Count (app-specific) + uint8_t records; // #of 128-byte records in this extent + uint8_t blocks[kDirEntryBlockCount]; + bool readOnly; + bool system; + bool badBlockList; // set if block list is damaged + } DirEntry; + + static long CPMToProDOSBlock(long cpmBlock) { + return kVolDirBlock + (cpmBlock*2); + } + +private: + DIError Initialize(void); + DIError ReadCatalog(void); + DIError ScanFileUsage(void); + void SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose); + void FormatName(char* dstBuf, const char* srcBuf); + DIError ComputeLength(A2FileCPM* pFile); + bool CheckDiskIsGood(void); + + // the full set of raw dir entries + DirEntry fDirEntry[kNumDirEntries]; + + bool fDiskIsGood; +}; + +/* + * File descriptor for an open CP/M file. + */ +class DISKIMG_API A2FDCPM : public A2FileDescr { +public: + A2FDCPM(A2File* pFile) : + A2FileDescr(pFile), + fOffset(0), + fBlockCount(0), + fBlockList(NULL) + {} + virtual ~A2FDCPM(void) { + delete fBlockList; + fBlockList = NULL; + } + + friend class A2FileCPM; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + //bool fOpen; + di_off_t fOffset; + long fBlockCount; + uint8_t* fBlockList; +}; + +/* + * File on a CP/M disk. + */ +class DISKIMG_API A2FileCPM : public A2File { +public: + typedef DiskFSCPM::DirEntry DirEntry; + + A2FileCPM(DiskFS* pDiskFS, DirEntry* pDirEntry) : + A2File(pDiskFS), + fFileName(), + fReadOnly(false), + fLength(0), + fDirIdx(0), + fpDirEntry(pDirEntry), + fpOpenFile(NULL) + { + fDirIdx = -1; + fpOpenFile = NULL; + } + virtual ~A2FileCPM(void) { + delete fpOpenFile; + } + + // assorted constants + enum { + kMaxFileName = 12, // 8+3 including '.' + }; + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override { return 0; } + virtual uint32_t GetAuxType(void) const override { return 0; } + virtual uint32_t GetAccess(void) const override { + if (fReadOnly) + return DiskFS::kFileAccessLocked; + else + return DiskFS::kFileAccessUnlocked; + } + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override { return 0; } + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + virtual void Dump(void) const override; + + /* fields pulled out of directory block */ + char fFileName[kMaxFileName+1]; + bool fReadOnly; + + /* derived fields */ + di_off_t fLength; + int fDirIdx; // index into fDirEntry for part #1 + + DIError GetBlockList(long* pBlockCount, uint8_t* blockBuf) const; + +private: + const DirEntry* fpDirEntry; + A2FileDescr* fpOpenFile; +}; + + +/* + * =========================================================================== + * RDOS + * =========================================================================== + */ + +/* + * RDOS disk. + * + * There is no allocation map or file index blocks, just a linear collection + * of files with contiguous sectors. Very similar to Pascal. + * + * The one interesting quirk is the "converted 13-sector disk" format, where + * only 13 of 16 sectors are actually used. The linear sector addressing + * must take that into account. + */ +class A2FileRDOS; +class DISKIMG_API DiskFSRDOS : public DiskFS { +public: + DiskFSRDOS(void) : fVolumeName(), fOurSectPerTrack(0) {} + virtual ~DiskFSRDOS(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + virtual const char* GetVolumeName(void) const override { return fVolumeName; } + virtual const char* GetVolumeID(void) const override { return fVolumeName; } + virtual const char* GetBareVolumeName(void) const override { return NULL; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + + int GetOurSectPerTrack(void) const { return fOurSectPerTrack; } + +private: + static DIError TestCommon(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + FSLeniency leniency, DiskImg::FSFormat* pFormatFound); + + DIError Initialize(void); + DIError ReadCatalog(void); + DIError ScanFileUsage(void); + void SetSectorUsage(long track, long sector, + VolumeUsage::ChunkPurpose purpose); + + char fVolumeName[10]; // e.g. "RDOS 3.3" + int fOurSectPerTrack; +}; + +/* + * File descriptor for an open RDOS file. + */ +class DISKIMG_API A2FDRDOS : public A2FileDescr { +public: + A2FDRDOS(A2File* pFile) : A2FileDescr(pFile) { + fOffset = 0; + } + virtual ~A2FDRDOS(void) { + /* nothing to clean up */ + } + + friend class A2FileRDOS; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + /* RDOS is unique in that it can put 13-sector disks on 16-sector tracks */ + inline int GetOurSectPerTrack(void) const { + DiskFSRDOS* pDiskFS = (DiskFSRDOS*) fpFile->GetDiskFS(); + return pDiskFS->GetOurSectPerTrack(); + } + + //bool fOpen; + di_off_t fOffset; +}; + +/* + * File on an RDOS disk. + */ +class DISKIMG_API A2FileRDOS : public A2File { +public: + A2FileRDOS(DiskFS* pDiskFS) : + A2File(pDiskFS), + fFileName(), + fRawFileName(), + fFileType(A2FileRDOS::FileType::kTypeUnknown), + fNumSectors(0), + fLoadAddr(0), + fLength(0), + fStartSector(0), + fpOpenFile(NULL) + {} + virtual ~A2FileRDOS(void) { + delete fpOpenFile; + } + + // assorted constants + enum { + kMaxFileName = 24, + }; + typedef enum FileType { + kTypeUnknown = 0, + kTypeApplesoft, // 'A' + kTypeBinary, // 'B' + kTypeText, // 'T' + } FileType; + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual const char* GetRawFileName(size_t* size = NULL) const override; + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override; + virtual uint32_t GetAuxType(void) const override { return fLoadAddr; } + virtual uint32_t GetAccess(void) const override { return DiskFS::kFileAccessUnlocked; } + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override { return 0; }; + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + void FixFilename(void); + virtual void Dump(void) const override; + + /* fields pulled out of directory block */ + char fFileName[kMaxFileName+1]; + char fRawFileName[kMaxFileName + 1]; + FileType fFileType; + uint16_t fNumSectors; + uint16_t fLoadAddr; + uint16_t fLength; + uint16_t fStartSector; + +private: + void TrimTrailingSpaces(char* filename); + + A2FileDescr* fpOpenFile; +}; + + +/* + * =========================================================================== + * HFS + * =========================================================================== + */ + +/* + * HFS disk. + */ +class A2FileHFS; +class DISKIMG_API DiskFSHFS : public DiskFS { +public: + DiskFSHFS(void) : + fVolumeName(), + fVolumeID(), + fTotalBlocks(0), + fAllocationBlockSize(0), + fNumAllocationBlocks(0), + fCreatedDateTime(0), + fModifiedDateTime(0), + fNumFiles(0), + fNumDirectories(0), + fLocalTimeOffset(-1), + fDiskIsGood(true) + { +#ifndef EXCISE_GPL_CODE + fHfsVol = NULL; +#endif + } + virtual ~DiskFSHFS(void) { +#ifndef EXCISE_GPL_CODE + hfs_callback_close(fHfsVol); + fHfsVol = (hfsvol*) 0xcdaaaacd; +#endif + } + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(initMode); + } + +#ifndef EXCISE_GPL_CODE + /* these are optional, defined as no-ops in the parent class */ + virtual DIError Format(DiskImg* pDiskImg, const char* volName) override; + virtual DIError NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) override; + virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile) override; + virtual DIError DeleteFile(A2File* pFile) override; + virtual DIError RenameFile(A2File* pFile, const char* newName) override; + virtual DIError SetFileInfo(A2File* pFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) override; + virtual DIError RenameVolume(const char* newName); +#endif + + // assorted constants + enum { + kMaxVolumeName = 27, + kMaxExtensionLen = 4, // used when normalizing; ".gif" is 4 + }; + + /* mandatory functions */ + virtual const char* GetVolumeName(void) const override { return fVolumeName; } + virtual const char* GetVolumeID(void) const override { return fVolumeID; } + virtual const char* GetBareVolumeName(void) const override { return fVolumeName; } + virtual bool GetReadWriteSupported(void) const override { return true; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual long GetFSNumBlocks(void) const override { return fTotalBlocks; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override; + +#ifndef EXCISE_GPL_CODE + hfsvol* GetHfsVol(void) const { return fHfsVol; } +#endif + + // utility function, used by app + static bool IsValidVolumeName(const char* name); + static bool IsValidFileName(const char* name); + +private: + enum { + // Macintosh 32-bit dates start in 1904, everybody else starts in + // 1970. Take the Mac date and adjust it 66 years plus 17 leap days. + // The annoying part is that HFS stores dates in local time, which + // means it's impossible to know absolutely when a file was modified. + // libhfs converts timestamps to the current time zone, so that a + // file written January 1st 2006 at 6pm in London will appear to have + // been written January 1st 2006 at 6pm in San Francisco if you + // happen to be sitting in California. + // + // This was fixed in HFS+, but we have to deal with it for now. The + // value below converts the date to local time in Greenwich; the + // current GMT offset and daylight saving time must be added to it. + // + // Curiously, the volume dates shown by Cmd-I on the volume on my + // Quadra are off by an hour, even though the file dates match. + kDateTimeOffset = (1970 - 1904) * 60 * 60 * 24 * 365 + + (60 * 60 * 24 * 17), + + kExpectedMinBlocks = 1440, // ignore volumes under 720K + }; + + struct MasterDirBlock; // fwd + static void UnpackMDB(const uint8_t* buf, MasterDirBlock* pMDB); + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder); + + DIError Initialize(InitMode initMode); + DIError LoadVolHeader(void); + void SetVolumeID(void); + void DumpVolHeader(void); + void SetVolumeUsageMap(void); + +#ifdef EXCISE_GPL_CODE + void CreateFakeFile(void); +#else + DIError RecursiveDirAdd(A2File* pParent, const char* basePath, int depth); + //void Sanitize(uint8_t* str); + DIError DoNormalizePath(const char* path, char fssep, + char** pNormalizedPath); + static int CompareMacFileNames(const char* str1, const char* str2); + DIError RegeneratePathName(A2FileHFS* pFile); + DIError MakeFileNameUnique(const char* pathName, char** pUniqueName); + + /* libhfs stuff */ + static unsigned long LibHFSCB(void* vThis, int op, unsigned long arg1, + void* arg2); + hfsvol* fHfsVol; +#endif + + + /* some items from the volume header */ + char fVolumeName[kMaxVolumeName+1]; + char fVolumeID[kMaxVolumeName + 8]; // add "HFS :" + uint32_t fTotalBlocks; + uint32_t fAllocationBlockSize; + uint32_t fNumAllocationBlocks; + uint32_t fCreatedDateTime; + uint32_t fModifiedDateTime; + uint32_t fNumFiles; + uint32_t fNumDirectories; + + long fLocalTimeOffset; + bool fDiskIsGood; +}; + +/* + * File descriptor for an open HFS file. + */ +class DISKIMG_API A2FDHFS : public A2FileDescr { +public: +#ifdef EXCISE_GPL_CODE + A2FDHFS(A2File* pFile, void* unused) + : A2FileDescr(pFile), fOffset(0) + {} +#else + A2FDHFS(A2File* pFile, hfsfile* pHfsFile) + : A2FileDescr(pFile), fHfsFile(pHfsFile), fModified(false) + {} +#endif + virtual ~A2FDHFS(void) { +#ifndef EXCISE_GPL_CODE + if (fHfsFile != NULL) + hfs_close(fHfsFile); +#endif + } + + friend class A2FileHFS; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: +#ifdef EXCISE_GPL_CODE + di_off_t fOffset; +#else + hfsfile* fHfsFile; + bool fModified; +#endif +}; + +/* + * File on an HFS disk. + */ +class DISKIMG_API A2FileHFS : public A2File { +public: + A2FileHFS(DiskFS* pDiskFS) : + A2File(pDiskFS), + fIsDir(false), + fIsVolumeDir(false), + fType(0), + fCreator(0), + fFileName(), + fPathName(NULL), + fDataLength(0), + fRsrcLength(0), + fCreateWhen(0), + fModWhen(0), + fAccess(0), + fpOpenFile(NULL) + { +#ifdef EXCISE_GPL_CODE + fFakeFileBuf = NULL; +#else + fpParent = NULL; + //fOrigPathName = NULL; +#endif + } + virtual ~A2FileHFS(void) { + delete fpOpenFile; + delete[] fPathName; +#ifdef EXCISE_GPL_CODE + delete[] fFakeFileBuf; +#else + //delete[] fOrigPathName; +#endif + } + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fPathName; } + virtual char GetFssep(void) const override { return kFssep; } + virtual uint32_t GetFileType(void) const override; + virtual uint32_t GetAuxType(void) const override; + virtual uint32_t GetAccess(void) const override { return fAccess; } + virtual time_t GetCreateWhen(void) const override { return fCreateWhen; } + virtual time_t GetModWhen(void) const override { return fModWhen; } + virtual di_off_t GetDataLength(void) const override { return fDataLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fDataLength; } + virtual di_off_t GetRsrcLength(void) const override { return fRsrcLength; } + virtual di_off_t GetRsrcSparseLength(void) const override { return fRsrcLength; } + virtual bool IsDirectory(void) const override { return fIsDir; } + virtual bool IsVolumeDirectory(void) const override { return fIsVolumeDir; } + + virtual DIError Open(A2FileDescr** pOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + enum { + kMaxFileName = 31, + kFssep = ':', + kPdosType = 0x70646f73, // 'pdos' + }; + + void SetPathName(const char* basePath, const char* fileName); + virtual void Dump(void) const override; + +#ifdef EXCISE_GPL_CODE + void SetFakeFile(void* buf, long len) { + assert(len > 0); + if (fFakeFileBuf != NULL) + delete[] fFakeFileBuf; + fFakeFileBuf = new char[len]; + memcpy(fFakeFileBuf, buf, len); + fDataLength = len; + } + const void* GetFakeFileBuf(void) const { return fFakeFileBuf; } +#else + void InitEntry(const hfsdirent* dirEntry); + void SetOrigPathName(const char* pathName); + virtual void SetParent(A2File* pParent) override { fpParent = pParent; } + virtual A2File* GetParent(void) const override { return fpParent; } + char* GetLibHFSPathName(void) const; + static void ConvertTypeToHFS(uint32_t fileType, uint32_t auxType, + char* pType, char* pCreator); +#endif + + bool fIsDir; + bool fIsVolumeDir; + uint32_t fType; + uint32_t fCreator; + char fFileName[kMaxFileName+1]; + char* fPathName; + di_off_t fDataLength; + di_off_t fRsrcLength; + time_t fCreateWhen; + time_t fModWhen; + uint32_t fAccess; + +private: +#ifdef EXCISE_GPL_CODE + char* fFakeFileBuf; +#else + //char* fOrigPathName; + A2File* fpParent; +#endif + A2FileDescr* fpOpenFile; // only one fork can be open at a time +}; + + +/* + * =========================================================================== + * Gutenberg + * =========================================================================== + */ + +class A2FileGutenberg; + +/* + * Gutenberg disk. + */ +class DISKIMG_API DiskFSGutenberg : public DiskFS { +public: + DiskFSGutenberg(void) : DiskFS(), + fFirstCatTrack(0), + fFirstCatSector(0), + fVTOCVolumeNumber(0), + fVTOCNumTracks(0), + fVTOCNumSectors(0), + fDiskVolumeName(), + fDiskVolumeID(), + fVTOC(), + fVTOCLoaded(false), + fCatalogSectors(), + fDiskIsGood(false) + {} + virtual ~DiskFSGutenberg(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(initMode); + } + + virtual const char* GetVolumeName(void) const override { return fDiskVolumeName; } + virtual const char* GetVolumeID(void) const override { return fDiskVolumeID; } + virtual const char* GetBareVolumeName(void) const override { + return fDiskVolumeName; + } + virtual bool GetReadWriteSupported(void) const override { return true; } + virtual bool GetFSDamaged(void) const override { return !fDiskIsGood; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override; + + //static bool IsValidFileName(const char* name); + //static bool IsValidVolumeName(const char* name); + + // utility function + static void LowerASCII(uint8_t* buf, long len); + //static void ReplaceFssep(char* str, char replacement); + + enum { + kMinTracks = 17, // need to put the catalog track here + kMaxTracks = 50, + kMaxCatalogSectors = 64, // two tracks on a 32-sector disk + }; + + /* a T/S pair */ + typedef struct TrackSector { + char track; + char sector; + } TrackSector; + + friend class A2FDGutenberg; // for Write + +private: + DIError Initialize(InitMode initMode); + DIError ReadCatalog(void); + DIError ProcessCatalogSector(int catTrack, int catSect, + const uint8_t* sctBuf); + DIError GetFileLengths(void); + + bool CheckDiskIsGood(void); + + // Largest interesting volume is 400K (50 tracks, 32 sectors), but + // we may be looking at it in 16-sector mode, so max tracks is 100. + enum { + kMaxInterestingTracks = 100, + kSectorSize = 256, + kDefaultVolumeNum = 254, + kMaxExtensionLen = 4, // used when normalizing; ".gif" is 4 + }; + + /* some fields from the VTOC */ + int fFirstCatTrack; + int fFirstCatSector; + int fVTOCVolumeNumber; + int fVTOCNumTracks; + int fVTOCNumSectors; + + /* private data */ + char fDiskVolumeName[10]; // + char fDiskVolumeID[11+12+1]; // sizeof "Gutenberg: " + 12 + null + uint8_t fVTOC[kSectorSize]; + bool fVTOCLoaded; + + /* + * There are some things we need to be careful of when reading the + * catalog track, like bad links and infinite loops. By storing a list + * of known good catalog sectors, we only have to handle that stuff once. + * The catalog doesn't grow or shrink, so this never needs to be updated. + */ + TrackSector fCatalogSectors[kMaxCatalogSectors]; + + bool fDiskIsGood; +}; + +/* + * File descriptor for an open Gutenberg file. + */ +class DISKIMG_API A2FDGutenberg : public A2FileDescr { +public: + A2FDGutenberg(A2File* pFile) : + A2FileDescr(pFile), + fTSCount(0), + fOffset(0), + fOpenEOF(0), + fOpenSectorsUsed(0), + fModified(false) + {} + virtual ~A2FDGutenberg(void) {} + + friend class A2FileGutenberg; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + typedef DiskFSGutenberg::TrackSector TrackSector; + + int fTSCount; + di_off_t fOffset; // current position in file + + di_off_t fOpenEOF; // how big the file currently is + long fOpenSectorsUsed; // how many sectors it occupies + bool fModified; // if modified, update stuff on Close + + void DumpTSList(void) const; +}; + +/* + * Holds Gutenberg files. + * + */ +class DISKIMG_API A2FileGutenberg : public A2File { +public: + A2FileGutenberg(DiskFS* pDiskFS); + virtual ~A2FileGutenberg(void); + + // assorted constants + enum { + kMaxFileName = 12, + }; + typedef enum { + kTypeText = 0x00, // 'T' + } FileType; + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override; + virtual uint32_t GetAuxType(void) const override { return fAuxType; } + virtual uint32_t GetAccess(void) const override { return DiskFS::kFileAccessUnlocked; } + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override { return 0; } + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fSparseLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + void Dump(void) const; + + typedef DiskFSGutenberg::TrackSector TrackSector; + + /* + * Contents of directory entry. + * + * We don't hold deleted or unused entries, so fTSListTrack is always + * valid. + */ + short fTrack; // (could use TrackSector here) + short fSector; + uint16_t fLengthInSectors; + bool fLocked; + char fFileName[kMaxFileName+1]; // "fixed" version + FileType fFileType; + + TrackSector fCatTS; // track/sector for our catalog entry + int fCatEntryNum; // entry number within cat sector + + // these are computed or determined from the file contents + uint16_t fAuxType; // addr for bin, etc. + short fDataOffset; // for 'A'/'B'/'I' with embedded len + di_off_t fLength; // file length, in bytes + di_off_t fSparseLength; // file length, factoring sparse out + + void FixFilename(void); + + static void MakeDOSName(char* buf, const char* name); + static void TrimTrailingSpaces(char* filename); + +private: + A2FDGutenberg* fpOpenFile; +}; + + +/* + * =========================================================================== + * FAT (including FAT12, FAT16, and FAT32) + * =========================================================================== + */ + +/* + * MS-DOS FAT disk. + * + * This is currently just the minimum necessary to properly recognize + * the disk. + */ +class A2FileFAT; +class DISKIMG_API DiskFSFAT : public DiskFS { +public: + DiskFSFAT(void) : + fVolumeName(), + fVolumeID(), + fTotalBlocks(0) + {} + virtual ~DiskFSFAT(void) {} + + static DIError TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency); + + virtual DIError Initialize(DiskImg* pImg, InitMode initMode) override { + SetDiskImg(pImg); + return Initialize(); + } + + // assorted constants + enum { + kMaxVolumeName = 11, + }; + + virtual const char* GetVolumeName(void) const override { return fVolumeName; } + virtual const char* GetVolumeID(void) const override { return fVolumeID; } + virtual const char* GetBareVolumeName(void) const override { return fVolumeName; } + virtual bool GetReadWriteSupported(void) const override { return false; } + virtual bool GetFSDamaged(void) const override { return false; } + virtual long GetFSNumBlocks(void) const override { return fTotalBlocks; } + virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const override + { return kDIErrNotSupported; } + +private: + enum { + kExpectedMinBlocks = 720, // ignore volumes under 360K + }; + + struct MasterBootRecord; // fwd + struct BootSector; + static bool UnpackMBR(const uint8_t* buf, MasterBootRecord* pOut); + static bool UnpackBootSector(const uint8_t* buf, BootSector* pOut); + static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder); + + DIError Initialize(void); + DIError LoadVolHeader(void); + void DumpVolHeader(void); + void SetVolumeUsageMap(void); + void CreateFakeFile(void); + + /* some items from the volume header */ + char fVolumeName[kMaxVolumeName+1]; + char fVolumeID[kMaxVolumeName + 8]; // add "FAT %s:" + uint32_t fTotalBlocks; +}; + +/* + * File descriptor for an open FAT file. + */ +class DISKIMG_API A2FDFAT : public A2FileDescr { +public: + A2FDFAT(A2File* pFile) : A2FileDescr(pFile) { + fOffset = 0; + } + virtual ~A2FDFAT(void) { + /* nothing to clean up */ + } + + friend class A2FileFAT; + + virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) override; + virtual DIError Write(const void* buf, size_t len, + size_t* pActual = NULL) override; + virtual DIError Seek(di_off_t offset, DIWhence whence) override; + virtual di_off_t Tell(void) override; + virtual DIError Close(void) override; + + virtual long GetSectorCount(void) const override; + virtual long GetBlockCount(void) const override; + virtual DIError GetStorage(long sectorIdx, long* pTrack, + long* pSector) const override; + virtual DIError GetStorage(long blockIdx, long* pBlock) const override; + +private: + di_off_t fOffset; +}; + +/* + * File on a FAT disk. + */ +class DISKIMG_API A2FileFAT : public A2File { +public: + A2FileFAT(DiskFS* pDiskFS) : + A2File(pDiskFS), + fFileName(), + fLength(0), + fFakeFileBuf(NULL), + fpOpenFile(NULL) + {} + virtual ~A2FileFAT(void) { + delete fpOpenFile; + delete[] fFakeFileBuf; + } + + /* + * Implementations of standard interfaces. + */ + virtual const char* GetFileName(void) const override { return fFileName; } + virtual const char* GetPathName(void) const override { return fFileName; } + virtual char GetFssep(void) const override { return '\0'; } + virtual uint32_t GetFileType(void) const override { return 0; }; + virtual uint32_t GetAuxType(void) const override { return 0; } + virtual uint32_t GetAccess(void) const override { return DiskFS::kFileAccessUnlocked; } + virtual time_t GetCreateWhen(void) const override { return 0; } + virtual time_t GetModWhen(void) const override { return 0; } + virtual di_off_t GetDataLength(void) const override { return fLength; } + virtual di_off_t GetDataSparseLength(void) const override { return fLength; } + virtual di_off_t GetRsrcLength(void) const override { return -1; } + virtual di_off_t GetRsrcSparseLength(void) const override { return -1; } + + virtual DIError Open(A2FileDescr** pOpenFile, bool readOnly, + bool rsrcFork = false) override; + virtual void CloseDescr(A2FileDescr* pOpenFile) override { + assert(pOpenFile == fpOpenFile); + delete fpOpenFile; + fpOpenFile = NULL; + } + virtual bool IsFileOpen(void) const override { return fpOpenFile != NULL; } + + enum { kMaxFileName = 31 }; + + virtual void Dump(void) const override; + + void SetFakeFile(void* buf, long len) { + assert(len > 0); + if (fFakeFileBuf != NULL) + delete[] fFakeFileBuf; + fFakeFileBuf = new char[len]; + memcpy(fFakeFileBuf, buf, len); + fLength = len; + } + const void* GetFakeFileBuf(void) const { return fFakeFileBuf; } + + char fFileName[kMaxFileName+1]; + di_off_t fLength; + +private: + char* fFakeFileBuf; + //long fFakeFileLen; + A2FileDescr* fpOpenFile; +}; + +} // namespace DiskImgLib + +#endif /*DISKIMG_DISKIMGDETAIL_H*/ diff --git a/diskimg/DiskImgPriv.h b/diskimg/DiskImgPriv.h new file mode 100644 index 0000000..a506ab0 --- /dev/null +++ b/diskimg/DiskImgPriv.h @@ -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 +#include +// "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(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*/ diff --git a/diskimg/FAT.cpp b/diskimg/FAT.cpp new file mode 100644 index 0000000..7fd9cf1 --- /dev/null +++ b/diskimg/FAT.cpp @@ -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; +} diff --git a/diskimg/FDI.cpp b/diskimg/FDI.cpp new file mode 100644 index 0000000..671c7ab --- /dev/null +++ b/diskimg/FDI.cpp @@ -0,0 +1,1522 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Support for Formatted Disk Image (FDI) format. + * + * Based on the v2.0 spec and "fdi2raw.c". The latter was released under + * version 2 of the GPL, so this code may be subject to it. + * + * (Note: I tend to abuse the term "nibble" here. Instead of 4 bits, I + * use it to refer to 8 bits of "nibblized" data. Sorry.) + * + * THOUGHT: we have access to the self-sync byte data. We could use this + * to pretty easily convert a track to 6656-byte format, which would allow + * conversion to .NIB instead of .APP. This would probably need to be + * specified as a global preference (how to open .FDI), though we could + * just drag the self-sync flags around in a parallel data structure and + * invent a format-conversion API. The former seems easier, and should + * be easy to explain in the UI. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + + +/* + * =========================================================================== + * FDI compression functions + * =========================================================================== + */ + +/* + * Pack a disk image with FDI. + */ +DIError WrapperFDI::PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD) +{ + DIError dierr = kDIErrGeneric; // not yet + return dierr; +} + + +/* + * =========================================================================== + * FDI expansion functions + * =========================================================================== + */ + +/* + * Unpack an FDI-encoded disk image from "pGFD" to a new memory buffer + * created in "*ppNewGFD". The output is a collection of variable-length + * nibble tracks. + * + * "pNewGFD" will need to hold (kTrackAllocSize * numCyls * numHeads) + * bytes of data. + * + * Fills in "fNibbleTrackInfo". + */ +DIError WrapperFDI::UnpackDisk525(GenericFD* pGFD, GenericFD* pNewGFD, + int numCyls, int numHeads) +{ + DIError dierr = kDIErrNone; + uint8_t nibbleBuf[kNibbleBufLen]; + uint8_t* inputBuf = NULL; + bool goodTracks[kMaxNibbleTracks525]; + int inputBufLen = -1; + int badTracks = 0; + int trk, type, length256; + long nibbleLen; + bool result; + + assert(numHeads == 1); + memset(goodTracks, false, sizeof(goodTracks)); + + dierr = pGFD->Seek(kMinHeaderLen, kSeekSet); + if (dierr != kDIErrNone) { + LOGI("FDI: track seek failed (offset=%d)", kMinHeaderLen); + goto bail; + } + + for (trk = 0; trk < numCyls * numHeads; trk++) { + GetTrackInfo(trk, &type, &length256); + LOGI("%2d.%d: t=0x%02x l=%d (%d)", trk / numHeads, trk % numHeads, + type, length256, length256 * 256); + + /* if we have data to read, read it */ + if (length256 > 0) { + if (length256 * 256 > inputBufLen) { + /* allocate or increase the size of the input buffer */ + delete[] inputBuf; + inputBufLen = length256 * 256; + inputBuf = new uint8_t[inputBufLen]; + if (inputBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + } + + dierr = pGFD->Read(inputBuf, length256 * 256); + if (dierr != kDIErrNone) + goto bail; + } else { + assert(type == 0x00); + } + + /* figure out what we want to do with this track */ + switch (type) { + case 0x00: + /* blank track */ + badTracks++; + memset(nibbleBuf, 0xff, sizeof(nibbleBuf)); + nibbleLen = kTrackLenNb2525; + break; + case 0x80: + case 0x90: + case 0xa0: + case 0xb0: + /* low-level pulse-index */ + nibbleLen = kNibbleBufLen; + result = DecodePulseTrack(inputBuf, length256*256, kBitRate525, + nibbleBuf, &nibbleLen); + if (!result) { + /* something failed in the decoder; fake it */ + badTracks++; + memset(nibbleBuf, 0xff, sizeof(nibbleBuf)); + nibbleLen = kTrackLenNb2525; + } else { + goodTracks[trk] = true; + } + if (nibbleLen > kTrackAllocSize) { + LOGI(" FDI: decoded %ld nibbles, buffer is only %d", + nibbleLen, kTrackAllocSize); + dierr = kDIErrBadRawData; + goto bail; + } + break; + default: + LOGI("FDI: unexpected track type 0x%04x", type); + dierr = kDIErrUnsupportedImageFeature; + goto bail; + } + + fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize; + fNibbleTrackInfo.length[trk] = nibbleLen; + FixBadNibbles(nibbleBuf, nibbleLen); + dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet); + if (dierr != kDIErrNone) + goto bail; + dierr = pNewGFD->Write(nibbleBuf, nibbleLen); + if (dierr != kDIErrNone) + goto bail; + LOGI(" FDI: track %d: wrote %ld nibbles", trk, nibbleLen); + + //offset += 256 * length256; + //break; // DEBUG DEBUG + } + + LOGI(" FDI: %d of %d tracks bad or blank", + badTracks, numCyls * numHeads); + if (badTracks > (numCyls * numHeads) / 2) { + LOGI("FDI: too many bad tracks"); + dierr = kDIErrBadRawData; + goto bail; + } + + /* + * For convenience we want this to be 35 or 40 tracks. Start by + * reducing trk to 35 if there are no good tracks at 35+. + */ + bool want40; + int i; + + want40 = false; + for (i = kTrackCount525; i < kMaxNibbleTracks525; i++) { + if (goodTracks[i]) { + want40 = true; + break; + } + } + if (!want40 && trk > kTrackCount525) { + LOGI(" FDI: no good tracks past %d, reducing from %d", + kTrackCount525, trk); + trk = kTrackCount525; // nothing good out there, roll back + } + + /* + * Now pad us *up* to 35 if we have fewer than that. + */ + memset(nibbleBuf, 0xff, sizeof(nibbleBuf)); + for ( ; trk < kMaxNibbleTracks525; trk++) { + if (trk == kTrackCount525) + break; + + fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize; + fNibbleTrackInfo.length[trk] = kTrackLenNb2525; + fNibbleTrackInfo.numTracks++; + + dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet); + if (dierr != kDIErrNone) + goto bail; + dierr = pNewGFD->Write(nibbleBuf, nibbleLen); + if (dierr != kDIErrNone) + goto bail; + } + + assert(trk == kTrackCount525 || trk == kMaxNibbleTracks525); + fNibbleTrackInfo.numTracks = trk; + +bail: + delete[] inputBuf; + return dierr; +} + +/* + * Unpack an FDI-encoded disk image from "pGFD" to 800K of ProDOS-ordered + * 512-byte blocks in "pNewGFD". + * + * We could keep the 12-byte "tags" on each block, but they were never + * really used in the Apple II world. + * + * We also need to set up a "bad block" map to identify parts that we had + * trouble unpacking. + */ +DIError WrapperFDI::UnpackDisk35(GenericFD* pGFD, GenericFD* pNewGFD, int numCyls, + int numHeads, LinearBitmap* pBadBlockMap) +{ + DIError dierr = kDIErrNone; + uint8_t nibbleBuf[kNibbleBufLen]; + uint8_t* inputBuf = NULL; + uint8_t outputBuf[kMaxSectors35 * kBlockSize]; // 6KB + int inputBufLen = -1; + int badTracks = 0; + int trk, type, length256; + long nibbleLen; + bool result; + + assert(numHeads == 2); + + dierr = pGFD->Seek(kMinHeaderLen, kSeekSet); + if (dierr != kDIErrNone) { + LOGI("FDI: track seek failed (offset=%d)", kMinHeaderLen); + goto bail; + } + + pNewGFD->Rewind(); + + for (trk = 0; trk < numCyls * numHeads; trk++) { + GetTrackInfo(trk, &type, &length256); + LOGI("%2d.%d: t=0x%02x l=%d (%d)", trk / numHeads, trk % numHeads, + type, length256, length256 * 256); + + /* if we have data to read, read it */ + if (length256 > 0) { + if (length256 * 256 > inputBufLen) { + /* allocate or increase the size of the input buffer */ + delete[] inputBuf; + inputBufLen = length256 * 256; + inputBuf = new uint8_t[inputBufLen]; + if (inputBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + } + + dierr = pGFD->Read(inputBuf, length256 * 256); + if (dierr != kDIErrNone) + goto bail; + } else { + assert(type == 0x00); + } + + /* figure out what we want to do with this track */ + switch (type) { + case 0x00: + /* blank track */ + badTracks++; + memset(nibbleBuf, 0xff, sizeof(nibbleBuf)); + nibbleLen = kTrackLenNb2525; + break; + case 0x80: + case 0x90: + case 0xa0: + case 0xb0: + /* low-level pulse-index */ + nibbleLen = kNibbleBufLen; + result = DecodePulseTrack(inputBuf, length256*256, + BitRate35(trk/numHeads), nibbleBuf, &nibbleLen); + if (!result) { + /* something failed in the decoder; fake it */ + badTracks++; + memset(nibbleBuf, 0xff, sizeof(nibbleBuf)); + nibbleLen = kTrackLenNb2525; + } + if (nibbleLen > kNibbleBufLen) { + LOGI(" FDI: decoded %ld nibbles, buffer is only %d", + nibbleLen, kTrackAllocSize); + dierr = kDIErrBadRawData; + goto bail; + } + break; + default: + LOGI("FDI: unexpected track type 0x%04x", type); + dierr = kDIErrUnsupportedImageFeature; + goto bail; + } + + LOGI(" FDI: track %d got %ld nibbles", trk, nibbleLen); + + /* + fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize; + fNibbleTrackInfo.length[trk] = nibbleLen; + dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet); + if (dierr != kDIErrNone) + goto bail; + dierr = pNewGFD->Write(nibbleBuf, nibbleLen); + if (dierr != kDIErrNone) + goto bail; + */ + + dierr = DiskImg::UnpackNibbleTrack35(nibbleBuf, nibbleLen, outputBuf, + trk / numHeads, trk % numHeads, pBadBlockMap); + if (dierr != kDIErrNone) + goto bail; + + dierr = pNewGFD->Write(outputBuf, + kBlockSize * DiskImg::SectorsPerTrack35(trk / numHeads)); + if (dierr != kDIErrNone) { + LOGI("FDI: failed writing disk blocks (%d * %d)", + kBlockSize, DiskImg::SectorsPerTrack35(trk / numHeads)); + goto bail; + } + } + + //fNibbleTrackInfo.numTracks = numCyls * numHeads; + +bail: + delete[] inputBuf; + return dierr; +} + +/* + * Return the approximate bit rate for the specified cylinder, in bits/sec. + */ +int WrapperFDI::BitRate35(int cyl) +{ + if (cyl >= 0 && cyl <= 15) + return 375000; // 394rpm + else if (cyl <= 31) + return 343750; // 429rpm + else if (cyl <= 47) + return 312500; // 472rpm + else if (cyl <= 63) + return 281250; // 525rpm + else if (cyl <= 79) + return 250000; // 590rpm + else { + LOGI(" FDI: invalid 3.5 cylinder %d", cyl); + return 250000; + } +} + +/* + * Fix any obviously-bad nibble values. + * + * This should be unlikely, but if we find several zeroes in a row due to + * garbled data from the drive, it can happen. We clean it up here so that, + * when we convert to another format (e.g. TrackStar), we don't flunk a + * simple high-bit screening test. + * + * (We could be more rigorous and test against valid disk bytes, but that's + * probably excessive.) + */ +void WrapperFDI::FixBadNibbles(uint8_t* nibbleBuf, long nibbleLen) +{ + int badCount = 0; + + while (nibbleLen--) { + if ((*nibbleBuf & 0x80) == 0) { + badCount++; + *nibbleBuf = 0xff; + } + nibbleBuf++; + } + + if (badCount != 0) { + LOGI(" FDI: fixed %d bad nibbles", badCount); + } +} + + +/* + * Get the info for the Nth track. The track number is used as an index + * into the track descriptor table. + * + * Returns the track type and amount of data (/256). + */ +void WrapperFDI::GetTrackInfo(int trk, int* pType, int* pLength256) +{ + uint16_t trackDescr; + trackDescr = fHeaderBuf[kTrackDescrOffset + trk * 2] << 8 | + fHeaderBuf[kTrackDescrOffset + trk * 2 +1]; + + *pType = (trackDescr & 0xff00) >> 8; + *pLength256 = trackDescr & 0x00ff; + + switch (trackDescr & 0xf000) { + case 0x0000: + /* high-level type */ + switch (trackDescr & 0xff00) { + case 0x0000: + /* blank track */ + break; + default: + /* miscellaneous high-level type */ + break; + } + break; + case 0x8000: + case 0x9000: + case 0xa000: + case 0xb000: + /* low-level type, length is 14 bits */ + *pType = (trackDescr & 0xc000) >> 8; + *pLength256 = trackDescr & 0x3fff; + break; + case 0xc000: + case 0xd000: + /* mid-level format, value in 0n00 holds a bit rate index */ + break; + case 0xe000: + case 0xf000: + /* raw MFM; for 0xf000, the value in 0n00 holds a bit rate index */ + break; + default: + LOGI("Unexpected trackDescr 0x%04x", trackDescr); + *pType = 0x7e; // return an invalid value + *pLength256 = 0; + break; + } +} + + +/* + * Convert a track encoded as one or more pulse streams to nibbles. + * + * This decompresses the pulse streams in "inputBuf", then converts them + * to nibble form in "nibbleBuf". + * + * "*pNibbleLen" should hold the maximum size of the buffer. On success, + * it will hold the actual number of bytes used. + * + * Returns "true" on success, "false" on failure. + */ +bool WrapperFDI::DecodePulseTrack(const uint8_t* inputBuf, long inputLen, + int bitRate, uint8_t* nibbleBuf, long* pNibbleLen) +{ + const int kSizeValueMask = 0x003fffff; + const int kSizeCompressMask = 0x00c00000; + const int kSizeCompressShift = 22; + PulseIndexHeader hdr; + uint32_t val; + bool result = false; + + memset(&hdr, 0, sizeof(hdr)); + + hdr.numPulses = GetLongBE(&inputBuf[0x00]); + val = Get24BE(&inputBuf[0x04]); + hdr.avgStreamLen = val & kSizeValueMask; + hdr.avgStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift; + val = Get24BE(&inputBuf[0x07]); + hdr.minStreamLen = val & kSizeValueMask; + hdr.minStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift; + val = Get24BE(&inputBuf[0x0a]); + hdr.maxStreamLen = val & kSizeValueMask; + hdr.maxStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift; + val = Get24BE(&inputBuf[0x0d]); + hdr.idxStreamLen = val & kSizeValueMask; + hdr.idxStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift; + + if (hdr.numPulses < 64 || hdr.numPulses > 131072) { + /* should be about 40,000 */ + LOGI(" FDI: bad pulse count %ld in track", hdr.numPulses); + return false; + } + + /* advance past the 16 hdr bytes; now pointing at "average" stream */ + inputBuf += kPulseStreamDataOffset; + + LOGI(" pulses: %ld", hdr.numPulses); + //LOGI(" avg: len=%d comp=%d", hdr.avgStreamLen, hdr.avgStreamCompression); + //LOGI(" min: len=%d comp=%d", hdr.minStreamLen, hdr.minStreamCompression); + //LOGI(" max: len=%d comp=%d", hdr.maxStreamLen, hdr.maxStreamCompression); + //LOGI(" idx: len=%d comp=%d", hdr.idxStreamLen, hdr.idxStreamCompression); + + /* + * Uncompress or endian-swap the pulse streams. + */ + hdr.avgStream = new uint32_t[hdr.numPulses]; + if (hdr.avgStream == NULL) + goto bail; + if (!UncompressPulseStream(inputBuf, hdr.avgStreamLen, hdr.avgStream, + hdr.numPulses, hdr.avgStreamCompression, 4)) + { + goto bail; + } + inputBuf += hdr.avgStreamLen; + + if (hdr.minStreamLen > 0) { + hdr.minStream = new uint32_t[hdr.numPulses]; + if (hdr.minStream == NULL) + goto bail; + if (!UncompressPulseStream(inputBuf, hdr.minStreamLen, hdr.minStream, + hdr.numPulses, hdr.minStreamCompression, 4)) + { + goto bail; + } + inputBuf += hdr.minStreamLen; + } + if (hdr.maxStreamLen > 0) { + hdr.maxStream = new uint32_t[hdr.numPulses]; + if (!UncompressPulseStream(inputBuf, hdr.maxStreamLen, hdr.maxStream, + hdr.numPulses, hdr.maxStreamCompression, 4)) + { + goto bail; + } + inputBuf += hdr.maxStreamLen; + } + if (hdr.idxStreamLen > 0) { + hdr.idxStream = new uint32_t[hdr.numPulses]; + if (!UncompressPulseStream(inputBuf, hdr.idxStreamLen, hdr.idxStream, + hdr.numPulses, hdr.idxStreamCompression, 2)) + { + goto bail; + } + inputBuf += hdr.idxStreamLen; + } + + /* + * Convert the pulse streams to a nibble stream. + */ + result = ConvertPulseStreamsToNibbles(&hdr, bitRate, nibbleBuf, pNibbleLen); + // fall through with result + +bail: + /* clean up */ + if (hdr.avgStream != NULL) + delete[] hdr.avgStream; + if (hdr.minStream != NULL) + delete[] hdr.minStream; + if (hdr.maxStream != NULL) + delete[] hdr.maxStream; + if (hdr.idxStream != NULL) + delete[] hdr.idxStream; + return result; +} + +/* + * Uncompress, or at least endian-swap, the input data. + * + * "inputLen" is the length in bytes of the input stream. For an uncompressed + * stream this should be equal to numPulses*bytesPerPulse, for a compressed + * stream it's the length of the compressed data. + * + * "bytesPerPulse" indicates the width of the input data. This will usually + * be 4, but is 2 for the index stream. The output is always 4 bytes/pulse. + * For Huffman-compressed data, it appears that the input is always 4 bytes. + * + * Returns "true" if all went well, "false" if we hit something that we + * couldn't handle. + */ +bool WrapperFDI::UncompressPulseStream(const uint8_t* inputBuf, long inputLen, + uint32_t* outputBuf, long numPulses, int format, int bytesPerPulse) +{ + assert(bytesPerPulse == 2 || bytesPerPulse == 4); + + /* + * Sample code has a snippet that says: if the format is "uncompressed" + * but inputLen < (numPulses*2), treat it as compressed. This may be + * for handling some badly-formed images. Not currently doing it here. + */ + + if (format == kCompUncompressed) { + int i; + + LOGE("NOT TESTED"); // remove this when we've tested it + + if (inputLen != numPulses * bytesPerPulse) { + LOGI(" FDI: got unc inputLen=%ld, outputLen=%ld", + inputLen, numPulses * bytesPerPulse); + return false; + } + if (bytesPerPulse == 2) { + for (i = 0; i < numPulses; i++) { + *outputBuf++ = GetShortBE(inputBuf); + inputBuf += 2; + } + } else { + for (i = 0; i < numPulses; i++) { + *outputBuf++ = GetLongBE(inputBuf); + inputBuf += 4; + } + } + } else if (format == kCompHuffman) { + if (!ExpandHuffman(inputBuf, inputLen, outputBuf, numPulses)) + return false; + //LOGI(" FDI: Huffman expansion succeeded"); + } else { + LOGI(" FDI: got weird compression format %d", format); + return false; + } + + return true; +} + +/* + * Expand a Huffman-compressed stream. + * + * The code takes bit-slices across the entire input and compresses them + * separately with a static Huffman variant. + * + * "outputBuf" is expected to hold "numPulses" entries. + * + * This implementation is based on the fdi2raw code. + */ +bool WrapperFDI::ExpandHuffman(const uint8_t* inputBuf, long inputLen, + uint32_t* outputBuf, long numPulses) +{ + HuffNode root; + const uint8_t* origInputBuf = inputBuf; + bool signExtend, sixteenBits; + int i, subStreamShift; + uint8_t bits; + uint8_t bitMask; + + memset(outputBuf, 0, numPulses * sizeof(uint32_t)); + subStreamShift = 1; + + while (subStreamShift != 0) { + if (inputBuf - origInputBuf >= inputLen) { + LOGI(" FDI: overran input(1)"); + return false; + } + + /* decode the sub-stream header */ + bits = *inputBuf++; + subStreamShift = bits & 0x7f; // low-order bit number + signExtend = (bits & 0x80) != 0; + bits = *inputBuf++; + sixteenBits = (bits & 0x80) != 0; // ignore redundant high-order + + //LOGI(" FDI: shift=%d ext=%d sixt=%d", + // subStreamShift, signExtend, sixteenBits); + + /* decode the Huffman tree structure */ + root.left = NULL; + root.right = NULL; + bitMask = 0; + inputBuf = HuffExtractTree(inputBuf, &root, &bits, &bitMask); + + //LOGI(" after tree: off=%d", inputBuf - origInputBuf); + + /* extract the Huffman node values */ + if (sixteenBits) + inputBuf = HuffExtractValues16(inputBuf, &root); + else + inputBuf = HuffExtractValues8(inputBuf, &root); + + if (inputBuf - origInputBuf >= inputLen) { + LOGI(" FDI: overran input(2)"); + return false; + } + //LOGI(" after values: off=%d", inputBuf - origInputBuf); + + /* decode the data over all pulses */ + bitMask = 0; + for (i = 0; i < numPulses; i++) { + uint32_t outVal; + const HuffNode* pCurrent = &root; + + /* chase down the tree until we hit a leaf */ + /* (note: nodes have two kids or none) */ + while (true) { + if (pCurrent->left == NULL) { + break; + } else { + bitMask >>= 1; + if (bitMask == 0) { + bitMask = 0x80; + bits = *inputBuf++; + } + if (bits & bitMask) + pCurrent = pCurrent->right; + else + pCurrent = pCurrent->left; + } + } + + outVal = outputBuf[i]; + if (signExtend) { + if (sixteenBits) + outVal |= HuffSignExtend16(pCurrent->val) << subStreamShift; + else + outVal |= HuffSignExtend8(pCurrent->val) << subStreamShift; + } else { + outVal |= pCurrent->val << subStreamShift; + } + outputBuf[i] = outVal; + } + HuffFreeNodes(root.left); + HuffFreeNodes(root.right); + } + + if (inputBuf - origInputBuf != inputLen) { + LOGI(" FDI: warning: Huffman input %ld vs. %ld", + inputBuf - origInputBuf, inputLen); + return false; + } + + return true; +} + + +/* + * Recursively extract the Huffman tree structure for this sub-stream. + */ +const uint8_t* WrapperFDI::HuffExtractTree(const uint8_t* inputBuf, + HuffNode* pNode, uint8_t* pBits, uint8_t* pBitMask) +{ + uint8_t val; + + if (*pBitMask == 0) { + *pBits = *inputBuf++; + *pBitMask = 0x80; + } + val = *pBits & *pBitMask; + (*pBitMask) >>= 1; + + //LOGI(" val=%d", val); + + if (val != 0) { + assert(pNode->left == NULL); + assert(pNode->right == NULL); + return inputBuf; + } else { + pNode->left = new HuffNode; + memset(pNode->left, 0, sizeof(HuffNode)); + inputBuf = HuffExtractTree(inputBuf, pNode->left, pBits, pBitMask); + pNode->right = new HuffNode; + memset(pNode->right, 0, sizeof(HuffNode)); + return HuffExtractTree(inputBuf, pNode->right, pBits, pBitMask); + } +} + +/* + * Recursively get the 16-bit values for our Huffman tree from the stream. + */ +const uint8_t* WrapperFDI::HuffExtractValues16(const uint8_t* inputBuf, + HuffNode* pNode) +{ + if (pNode->left == NULL) { + pNode->val = (*inputBuf++) << 8; + pNode->val |= *inputBuf++; + return inputBuf; + } else { + inputBuf = HuffExtractValues16(inputBuf, pNode->left); + return HuffExtractValues16(inputBuf, pNode->right); + } +} + +/* + * Recursively get the 8-bit values for our Huffman tree from the stream. + */ +const uint8_t* WrapperFDI::HuffExtractValues8(const uint8_t* inputBuf, + HuffNode* pNode) +{ + if (pNode->left == NULL) { + pNode->val = *inputBuf++; + return inputBuf; + } else { + inputBuf = HuffExtractValues8(inputBuf, pNode->left); + return HuffExtractValues8(inputBuf, pNode->right); + } +} + +/* + * Recursively free up the current node and all nodes beneath it. + */ +void WrapperFDI::HuffFreeNodes(HuffNode* pNode) +{ + if (pNode != NULL) { + HuffFreeNodes(pNode->left); + HuffFreeNodes(pNode->right); + delete pNode; + } + +} + +/* + * Sign-extend a 16-bit value to 32 bits. + */ +uint32_t WrapperFDI::HuffSignExtend16(uint32_t val) +{ + if (val & 0x8000) + val |= 0xffff0000; + return val; +} + +/* + * Sign-extend an 8-bit value to 32 bits. + */ +uint32_t WrapperFDI::HuffSignExtend8(uint32_t val) +{ + if (val & 0x80) + val |= 0xffffff00; + return val; +} + + +/* use these to extract values from the index stream */ +#define ZeroStateCount(_val) (((_val) >> 8) & 0xff) +#define OneStateCount(_val) ((_val) & 0xff) + +/* + * Convert our collection of pulse streams into (what we hope will be) + * Apple II nibble form. + * + * This modifies the contents of the minStream, maxStream, and idxStream + * arrays. + * + * "*pNibbleLen" should hold the maximum size of the buffer. On success, + * it will hold the actual number of bytes used. + */ +bool WrapperFDI::ConvertPulseStreamsToNibbles(PulseIndexHeader* pHdr, int bitRate, + uint8_t* nibbleBuf, long* pNibbleLen) +{ + uint32_t* fakeIdxStream = NULL; + bool result = false; + int i; + + /* + * Stream pointers. If we don't have a stream, fake it. + */ + uint32_t* avgStream; + uint32_t* minStream; + uint32_t* maxStream; + uint32_t* idxStream; + + avgStream = pHdr->avgStream; + if (pHdr->minStream != NULL && pHdr->maxStream != NULL) { + minStream = pHdr->minStream; + maxStream = pHdr->maxStream; + + /* adjust the values in the min/max streams */ + for (i = 0; i < pHdr->numPulses; i++) { + maxStream[i] = avgStream[i] + minStream[i] - maxStream[i]; + minStream[i] = avgStream[i] - minStream[i]; + } + } else { + minStream = pHdr->avgStream; + maxStream = pHdr->avgStream; + } + + if (pHdr->idxStream != NULL) + idxStream = pHdr->idxStream; + else { + /* + * The UAE sample code has some stuff to fake it. The code there + * is broken, so I'm guessing it has never been used, but I'm going + * to replicate it here (and probably never test it either). This + * assumes that the original was written for a big-endian machine. + */ + LOGI(" FDI: HEY: using fake index stream"); + DebugBreak(); + fakeIdxStream = new uint32_t[pHdr->numPulses]; + if (fakeIdxStream == NULL) { + LOGI(" FDI: unable to alloc fake idx stream"); + goto bail; + } + for (i = 1; i < pHdr->numPulses; i++) + fakeIdxStream[i] = 0x0200; // '1' for two, '0' for zero + fakeIdxStream[0] = 0x0101; // '1' for one, '0' for one + + idxStream = fakeIdxStream; + } + + /* + * Compute a value for maxIndex. + */ + uint32_t maxIndex; + + maxIndex = 0; + for (i = 0; i < pHdr->numPulses; i++) { + uint32_t sum; + + /* add up the two single-byte values in the index stream */ + sum = ZeroStateCount(idxStream[i]) + OneStateCount(idxStream[i]); + if (sum > maxIndex) + maxIndex = sum; + } + + /* + * Compute a value for indexOffset. + */ + int indexOffset; + + indexOffset = 0; + for (i = 0; i < pHdr->numPulses && OneStateCount(idxStream[i]) != 0; i++) { + /* "falling edge, replace with ZeroStateCount for rising edge" */ + } + if (i < pHdr->numPulses) { + int start = i; + do { + i++; + if (i >= pHdr->numPulses) + i = 0; // wrapped around + } while (i != start && ZeroStateCount(idxStream[i]) == 0); + if (i != start) { + /* index pulse detected */ + while (i != start && + ZeroStateCount(idxStream[i]) > OneStateCount(idxStream[i])) + { + i++; + if (i >= pHdr->numPulses) + i = 0; + } + if (i != start) + indexOffset = i; /* index position detected */ + } + } + + /* + * Compute totalAvg and weakBits, and rewrite idxStream. + * (We don't actually use weakBits.) + */ + uint32_t totalAvg; + int weakBits; + + totalAvg = weakBits = 0; + for (i = 0; i < pHdr->numPulses; i++) { + unsigned int sum; + sum = ZeroStateCount(idxStream[i]) + OneStateCount(idxStream[i]); + if (sum >= maxIndex) + totalAvg += avgStream[i]; // could this overflow...? + else + weakBits++; + + idxStream[i] = sum; + } + + LOGI(" FDI: maxIndex=%u indexOffset=%d totalAvg=%d weakBits=%d", + maxIndex, indexOffset, totalAvg, weakBits); + + /* + * Take our altered stream values and the stuff we've calculated, + * and convert the pulse values into bits. + */ + uint8_t bitBuffer[kBitBufferSize]; + int bitCount; + + bitCount = kBitBufferSize; + + if (!ConvertPulsesToBits(avgStream, minStream, maxStream, idxStream, + pHdr->numPulses, maxIndex, indexOffset, totalAvg, bitRate, + bitBuffer, &bitCount)) + { + LOGI(" FDI: ConvertPulsesToBits() failed"); + goto bail; + } + + //LOGI(" Got %d bits", bitCount); + if (bitCount < 0) { + LOGI(" FDI: overran output bit buffer"); + goto bail; + } + + /* + * We have a bit stream with the GCR bits as they appear coming out of + * the IWM. Convert it to 8-bit nibble form. + * + * We currently discard self-sync byte information. + */ + if (!ConvertBitsToNibbles(bitBuffer, bitCount, nibbleBuf, pNibbleLen)) + { + LOGI(" FDI: ConvertBitsToNibbles() failed"); + goto bail; + } + + result = true; + +bail: + delete[] fakeIdxStream; + return result; +} + + +/* + * Local data structures. Not worth putting in the header file. + */ +const int kPulseLimitVal = 15; /* "tolerance of 15%" */ + +typedef struct PulseSamples { + uint32_t size; + int numBits; +} PulseSamples; + +class PulseSampleCollection { +public: + PulseSampleCollection(void) { + fArrayIndex = fTotalDiv = -1; + fTotal = 0; + } + ~PulseSampleCollection(void) {} + + void Create(int stdMFM2BitCellSize, int numBits) { + int i; + + fArrayIndex = 0; + fTotal = 0; + fTotalDiv = 0; + for (i = 0; i < kSampleArrayMax; i++) { + // "That is (total track length / 50000) for Amiga double density" + fArray[i].size = stdMFM2BitCellSize; + fTotal += fArray[i].size; + fArray[i].numBits = numBits; + fTotalDiv += fArray[i].numBits; + } + assert(fTotalDiv != 0); + } + + uint32_t GetTotal(void) const { return fTotal; } + int GetTotalDiv(void) const { return fTotalDiv; } + + void AdjustTotal(long val) { fTotal += val; } + void AdjustTotalDiv(int val) { fTotalDiv += val; } + void IncrIndex(void) { + fArrayIndex++; + if (fArrayIndex >= kSampleArrayMax) + fArrayIndex = 0; + } + + PulseSamples* GetCurrentArrayEntry(void) { + return &fArray[fArrayIndex]; + } + + enum { + kSampleArrayMax = 10, + }; + +private: + PulseSamples fArray[kSampleArrayMax]; + int fArrayIndex; + uint32_t fTotal; + int fTotalDiv; +}; + +#define MY_RANDOM +#ifdef MY_RANDOM +/* replace rand() with my function */ +#define rand() MyRand() + +/* + * My psuedo-random number generator, which is even less random than + * rand(). It is, however, consistent across all platforms, and the + * value for RAND_MAX is small enough to avoid some integer overflow + * problems that the code has with (2^31-1) implementations. + */ +#undef RAND_MAX +#define RAND_MAX 32767 +int WrapperFDI::MyRand(void) +{ + const int kNumStates = 31; + const int kQuantum = RAND_MAX / (kNumStates+1); + static int state = 0; + int retVal; + + state++; + if (state == kNumStates) + state = 0; + + retVal = (kQuantum * state) + (kQuantum / 2); + assert(retVal >= 0 && retVal <= RAND_MAX); + return retVal; +} +#endif + +/* + * Convert the pulses we've read to a bit stream. This is a tad complex + * because the FDI scanner was reading a GCR disk with an MFM drive. + * + * Pass the output buffer size in bytes in "*pOutputLen". The actual number + * of *bits* output is returned in it. + * + * This is a fairly direct conversion from the sample code. There's a lot + * here that I haven't taken the time to figure out. + */ +bool WrapperFDI::ConvertPulsesToBits(const uint32_t* avgStream, + const uint32_t* minStream, const uint32_t* maxStream, + const uint32_t* idxStream, int numPulses, int maxIndex, + int indexOffset, uint32_t totalAvg, int bitRate, + uint8_t* outputBuf, int* pOutputLen) +{ + PulseSampleCollection samples; + BitOutputBuffer bitOutput(outputBuf, *pOutputLen); + /* magic numbers, from somewhere */ + const uint32_t kStdMFM2BitCellSize = (totalAvg * 5) / bitRate; + const uint32_t kStdMFM8BitCellSize = (totalAvg * 20) / bitRate; + int mfmMagic = 0; // if set to 1, decode as MFM rather than GCR + bool result = false; + int i; + //int debugCounter = 0; + + /* sample code doesn't do this, but I want consistent results */ + srand(0); + + /* + * "detects a long-enough stable pulse coming just after another + * stable pulse" + */ + i = 1; + while (i < numPulses && + (idxStream[i] < (uint32_t) maxIndex || + idxStream[i-1] < (uint32_t) maxIndex || + minStream[i] < (kStdMFM2BitCellSize - (kStdMFM2BitCellSize / 4)) + )) + { + i++; + } + if (i == numPulses) { + LOGW(" FDI: no stable and long-enough pulse in track"); + goto bail; + } + + /* + * Set up some variables. + */ + int nextI, endOfData, adjust, /*bitOffset,*/ step; + uint32_t refPulse; + long jitter; + + samples.Create(kStdMFM2BitCellSize, 1 + mfmMagic); + nextI = i; + endOfData = i; + i--; + adjust = 0; + //bitOffset = 0; + refPulse = 0; + jitter = 0; + step = -1; + + /* + * Run through the data three times: + * (-1) do stuff + * (0) do more stuff + * (1) output bits + */ + while (step < 2) { + /* + * Calculates the current average bit rate from previously + * decoded data. + */ + uint32_t avgSize; + int kCell8Limit = (kPulseLimitVal * kStdMFM8BitCellSize) / 100; + + /* this is the new average size for one MFM bit */ + avgSize = (samples.GetTotal() << (2 + mfmMagic)) / samples.GetTotalDiv(); + + /* + * Prevent avgSize from getting too far out of whack. + * + * "you can try tighter ranges than 25%, or wider ranges. I would + * probably go for tighter..." + */ + if ((avgSize < kStdMFM8BitCellSize - kCell8Limit) || + (avgSize > kStdMFM8BitCellSize + kCell8Limit)) + { + avgSize = kStdMFM8BitCellSize; + } + + /* + * Get the next long-enough pulse (may require more than one pulse). + */ + uint32_t pulse; + + pulse = 0; + while (pulse < ((avgSize / 4) - (avgSize / 16))) { + uint32_t avgPulse, minPulse, maxPulse; + + /* advance i */ + i++; + if (i >= numPulses) + i = 0; // wrapped around + + /* advance nextI */ + if (i == nextI) { + do { + nextI++; + if (nextI >= numPulses) + nextI = 0; + } while (idxStream[nextI] < (uint32_t) maxIndex); + } + + if (idxStream[i] >= (uint32_t) maxIndex) { + /* stable pulse */ + avgPulse = avgStream[i] - jitter; + minPulse = minStream[i]; + maxPulse = maxStream[i]; + if (jitter >= 0) + maxPulse -= jitter; + else + minPulse -= jitter; + + if (maxStream[nextI] - avgStream[nextI] < avgPulse - minPulse) + minPulse = avgPulse - (maxStream[nextI] - avgStream[nextI]); + if (avgStream[nextI] - minStream[nextI] < maxPulse - avgPulse) + maxPulse = avgPulse + (avgStream[nextI] - minStream[nextI]); + if (minPulse < refPulse) + minPulse = refPulse; + + /* + * This appears to use a pseudo-random number generator + * to dither the signal. This strikes me as highly + * questionable, but I'm trying to recreate what the sample + * code does, and I don't fully understand this stuff. + */ + int randVal; + + randVal = rand(); + if (randVal < (RAND_MAX / 2)) { + if (randVal > (RAND_MAX / 4)) { + if (randVal <= (3 * (RAND_MAX / 8))) + randVal = (2 * randVal) - (RAND_MAX / 4); + else + randVal = (4 * randVal) - RAND_MAX; + } + jitter = 0 - (randVal * (avgPulse - minPulse)) / RAND_MAX; + } else { + randVal -= RAND_MAX / 2; + if (randVal > (RAND_MAX / 4)) { + if (randVal <= (3 * (RAND_MAX / 8))) + randVal = (2 * randVal) - (RAND_MAX / 4); + else + randVal = (4 * randVal) - RAND_MAX; + } + jitter = (randVal * (maxPulse - avgPulse)) / RAND_MAX; + } + avgPulse += jitter; + + if (avgPulse < minPulse || avgPulse > maxPulse) { + /* this is bad -- we're out of bounds */ + LOGI(" FDI: avgPulse out of bounds: avg=%u min=%u max=%u", + avgPulse, minPulse, maxPulse); + } + if (avgPulse < refPulse) { + /* I guess this is also bad */ + LOGI(" FDI: avgPulse < refPulse (%u %u)", + avgPulse, refPulse); + } + pulse += avgPulse - refPulse; + refPulse = 0; + + /* + * If we've reached the end, advance to the next step. + */ + if (i == endOfData) + step++; + } else if ((uint32_t) rand() <= (idxStream[i] * RAND_MAX) / maxIndex) { + /* futz with it */ + int randVal; + + avgPulse = avgStream[i]; + minPulse = minStream[i]; + maxPulse = maxStream[i]; + + randVal = rand(); + if (randVal < (RAND_MAX / 2)) { + if (randVal > (RAND_MAX / 4)) { + if (randVal <= (3 * (RAND_MAX / 8))) + randVal = (2 * randVal) - (RAND_MAX / 4); + else + randVal = (4 * randVal) - RAND_MAX; + } + avgPulse -= (randVal * (avgPulse - minPulse)) / RAND_MAX; + } else { + randVal -= RAND_MAX / 2; + if (randVal > (RAND_MAX / 4)) { + if (randVal <= (3 * (RAND_MAX / 8))) + randVal = (2 * randVal) - (RAND_MAX / 4); + else + randVal = (4 * randVal) - RAND_MAX; + } + avgPulse += (randVal * (maxPulse - avgPulse)) / RAND_MAX; + } + if (avgPulse > refPulse && + avgPulse < (avgStream[nextI] - jitter)) + { + pulse += avgPulse - refPulse; + refPulse = avgPulse; + } + } else { + // do nothing + } + } + + /* + * "gets the size in bits from the pulse width, considering the current + * average bitrate" + * + * "realSize" will end up holding the number of bits we're going + * to output for this pulse. + */ + uint32_t adjustedPulse; + int realSize; + + adjustedPulse = pulse; + realSize = 0; + if (mfmMagic != 0) { + while (adjustedPulse >= avgSize) { + realSize += 4; + adjustedPulse -= avgSize / 2; + } + adjustedPulse <<= 3; + while (adjustedPulse >= ((avgSize * 4) + (avgSize / 4))) { + realSize += 2; + adjustedPulse -= avgSize * 2; + } + if (adjustedPulse >= ((avgSize * 3) + (avgSize / 4))) { + if (adjustedPulse <= ((avgSize * 4) - (avgSize / 4))) { + if ((2* ((adjustedPulse >> 2) - adjust)) <= + ((2 * avgSize) - (avgSize / 4))) + { + realSize += 3; + } else { + realSize += 4; + } + } else { + realSize += 4; + } + } else { + if (adjustedPulse > ((avgSize * 3) - (avgSize / 4))) { + realSize += 3; + } else { + if (adjustedPulse >= ((avgSize * 2) + (avgSize / 4))) { + if ((2 * ((adjustedPulse >> 2) - adjust)) < + (avgSize + (avgSize / 4))) + { + realSize += 2; + } else { + realSize += 3; + } + } else { + realSize += 2; + } + } + } + } else { + /* mfmMagic == 0, whatever that means */ + while (adjustedPulse >= (2 * avgSize)) { + realSize += 4; + adjustedPulse -= avgSize; + } + adjustedPulse <<= 2; + + while (adjustedPulse >= ((avgSize * 3) + (avgSize / 4))) { + realSize += 2; + adjustedPulse -= avgSize * 2; + } + if (adjustedPulse >= ((avgSize * 2) + (avgSize / 4))) { + if (adjustedPulse <= ((avgSize * 3) - (avgSize / 4))) { + if (((adjustedPulse >> 1) - adjust) < + (avgSize + (avgSize / 4))) + { + realSize += 2; + } else { + realSize += 3; + } + } else { + realSize += 3; + } + } else { + if (adjustedPulse > ((avgSize * 2) - (avgSize / 4))) + realSize += 2; + else { + if (adjustedPulse >= (avgSize + (avgSize / 4))) { + if (((adjustedPulse >> 1) - adjust) <= + (avgSize - (avgSize / 4))) + { + realSize++; + } else { + realSize += 2; + } + } else { + realSize++; + } + } + } + } + + /* + * "after one pass to correctly initialize the average bitrate, + * outputs the bits" + */ + if (step == 1) { + int j; + + for (j = realSize; j > 1; j--) + bitOutput.WriteBit(0); + bitOutput.WriteBit(1); + } + + /* + * Prepare for next pulse. + */ + adjust = ((realSize * avgSize) / (4 << mfmMagic)) - pulse; + + PulseSamples* pSamples; + pSamples = samples.GetCurrentArrayEntry(); + samples.AdjustTotal(-(long)pSamples->size); + samples.AdjustTotalDiv(-pSamples->numBits); + pSamples->size = pulse; + pSamples->numBits = realSize; + samples.AdjustTotal(pulse); + samples.AdjustTotalDiv(realSize); + samples.IncrIndex(); + } + + *pOutputLen = bitOutput.Finish(); + LOGI(" FDI: converted pulses to %d bits", *pOutputLen); + result = true; + +bail: + return result; +} + + +/* + * Convert a stream of GCR bits into nibbles. + * + * The stream includes 9-bit and 10-bit self-sync bytes. We need to process + * the bits as if we were an Apple II, shifting bits into a register until + * we get a 1 in the msb. + * + * There is a (roughly) 7 in 8 chance that we will not start out reading + * the stream on a byte boundary. We have to read for a bit to let the + * self-sync bytes do their job. + * + * "*pNibbleLen" should hold the maximum size of the buffer. On success, + * it will hold the actual number of bytes used. + */ +bool WrapperFDI::ConvertBitsToNibbles(const uint8_t* bitBuffer, int bitCount, + uint8_t* nibbleBuf, long* pNibbleLen) +{ + BitInputBuffer inputBuffer(bitBuffer, bitCount); + const uint8_t* nibbleBufStart = nibbleBuf; + long outputBufSize = *pNibbleLen; + bool result = false; + uint8_t val; + bool wrap; + + /* + * Start 3/4 of the way through the buffer. That should give us a + * couple of self-sync zones before we hit the end of the buffer. + */ + inputBuffer.SetStartPosition(3 * (bitCount / 4)); + + /* + * Run until we wrap. We should be in sync by that point. + */ + wrap = false; + while (!wrap) { + val = inputBuffer.GetByte(&wrap); + if ((val & 0x80) == 0) + val = (val << 1) | inputBuffer.GetBit(&wrap); + if ((val & 0x80) == 0) + val = (val << 1) | inputBuffer.GetBit(&wrap); + if ((val & 0x80) == 0) { + // not allowed by GCR encoding, probably garbage between sectors + LOGI(" FDI: WARNING: more than 2 consecutive zeroes (sync)"); + } + } + + /* + * Extract the nibbles. + */ + inputBuffer.ResetBitsConsumed(); + wrap = false; + while (true) { + val = inputBuffer.GetByte(&wrap); + if ((val & 0x80) == 0) + val = (val << 1) | inputBuffer.GetBit(&wrap); + if ((val & 0x80) == 0) + val = (val << 1) | inputBuffer.GetBit(&wrap); + if ((val & 0x80) == 0) { + LOGW(" FDI: WARNING: more than 2 consecutive zeroes (read)"); + } + + if (nibbleBuf - nibbleBufStart >= outputBufSize) { + LOGW(" FDI: bits overflowed nibble buffer"); + goto bail; + } + *nibbleBuf++ = val; + + /* if we wrapped around on this one, we've reached the start point */ + if (wrap) + break; + } + + if (inputBuffer.GetBitsConsumed() != bitCount) { + /* we dropped some or double-counted some */ + LOGW(" FDI: WARNING: consumed %d of %d bits", + inputBuffer.GetBitsConsumed(), bitCount); + } + + LOGI(" FDI: consumed %d of %d (first=0x%02x last=0x%02x)", + inputBuffer.GetBitsConsumed(), bitCount, + *nibbleBufStart, *(nibbleBuf-1)); + + *pNibbleLen = nibbleBuf - nibbleBufStart; + result = true; + +bail: + return result; +} diff --git a/diskimg/FocusDrive.cpp b/diskimg/FocusDrive.cpp new file mode 100644 index 0000000..43b75ce --- /dev/null +++ b/diskimg/FocusDrive.cpp @@ -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; +} diff --git a/diskimg/GenericFD.cpp b/diskimg/GenericFD.cpp new file mode 100644 index 0000000..a0adccc --- /dev/null +++ b/diskimg/GenericFD.cpp @@ -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*/ diff --git a/diskimg/GenericFD.h b/diskimg/GenericFD.h new file mode 100644 index 0000000..a5c4f6c --- /dev/null +++ b/diskimg/GenericFD.h @@ -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__*/ diff --git a/diskimg/Global.cpp b/diskimg/Global.cpp new file mode 100644 index 0000000..f949e60 --- /dev/null +++ b/diskimg/Global.cpp @@ -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); +} diff --git a/diskimg/Gutenberg.cpp b/diskimg/Gutenberg.cpp new file mode 100644 index 0000000..7cc5a5d --- /dev/null +++ b/diskimg/Gutenberg.cpp @@ -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; +} diff --git a/diskimg/HFS.cpp b/diskimg/HFS.cpp new file mode 100644 index 0000000..fb8a7f9 --- /dev/null +++ b/diskimg/HFS.cpp @@ -0,0 +1,2298 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Implementation of the Macintosh HFS filesystem. + * + * Most of the stuff lives in libhfs. To avoid problems that could arise + * from people ejecting floppies or trying to use a disk image while + * CiderPress is still open, we call hfs_flush() to force updates to be + * written. (Even with the "no caching" flag set, the master dir block and + * volume bitmap aren't written until flush is called.) + * + * The libhfs code is licensed under the full GPL, making it awkward to + * use in a commercial product. Support for libhfs can be removed with + * the EXCISE_GPL_CODE ifdefs. A stub will remain that can recognize HFS + * volumes, which is useful when dealing with Apple II hard drive and CFFA + * images. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + + +/* + * =========================================================================== + * DiskFSHFS + * =========================================================================== + */ + +const int kBlkSize = 512; +const int kMasterDirBlock = 2; // also a copy in next-to-last block +const uint16_t kSignature = 0x4244; // or 0xd2d7 for MFS +const int kMaxDirectoryDepth = 128; // not sure what HFS limit is + +//namespace DiskImgLib { + +/* extent descriptor */ +typedef struct ExtDescriptor { + uint16_t xdrStABN; // first allocation block + uint16_t xdrNumABlks; // #of allocation blocks +} ExtDescriptor; +/* extent data record */ +typedef struct ExtDataRec { + ExtDescriptor extDescriptor[3]; +} ExtDataRec; + +/* + * Contents of the HFS MDB. Information comes from "Inside Macintosh: Files", + * chapter 2 ("Data Organization on Volumes"), pages 2-60 to 2-62. + */ +typedef struct DiskFSHFS::MasterDirBlock { + uint16_t drSigWord; // volume signature + uint32_t drCrDate; // date/time of volume creation + uint32_t drLsMod; // date/time of last modification + uint16_t drAtrb; // volume attributes + uint16_t drNmPls; // #of files in root directory + uint16_t drVBMSt; // first block of volume bitmap + uint16_t drAllocPtr; // start of next allocation search + uint16_t drNmAlBlks; // number of allocation blocks in volume + uint32_t drAlBlkSiz; // size (bytes) of allocation blocks + uint32_t drClpSiz; // default clump size + uint16_t drAlBlSt; // first allocation block in volume + uint32_t drNxtCNID; // next unused catalog node ID + uint16_t drFreeBks; // number of unused allocation blocks + uint8_t drVN[28]; // volume name (pascal string) + uint32_t drVolBkUp; // date/time of last backup + uint16_t drVSeqNum; // volume backup sequence number + uint32_t drWrCnt; // volume write count + uint32_t drXTClpSiz; // clump size for extents overflow file + uint32_t drCTClpSiz; // clump size for catalog file + uint16_t drNmRtDirs; // #of directories in root directory + uint32_t drFilCnt; // #of files in volume + uint32_t drDirCnt; // #of directories in volume + uint32_t drFndrInfo[8]; // information used by the Finder + uint16_t drVCSize; // size (blocks) of volume cache + uint16_t drVBMCSize; // size (blocks) of volume bitmap cache + uint16_t drCtlCSize; // size (blocks) of common volume cache + uint32_t drXTFlSize; // size (bytes) of extents overflow file + ExtDataRec drXTExtRec; // extent record for extents overflow file + uint32_t drCTFlSize; // size (bytes) of catalog file + ExtDataRec drCTExtRec; // extent record for catalog file +} MasterDirBlock; + +//}; // namespace DiskImgLib + +/* + * Extract fields from a Master Directory Block. + */ +/*static*/ void DiskFSHFS::UnpackMDB(const uint8_t* buf, MasterDirBlock* pMDB) +{ + pMDB->drSigWord = GetShortBE(&buf[0x00]); + pMDB->drCrDate = GetLongBE(&buf[0x02]); + pMDB->drLsMod = GetLongBE(&buf[0x06]); + pMDB->drAtrb = GetShortBE(&buf[0x0a]); + pMDB->drNmPls = GetShortBE(&buf[0x0c]); + pMDB->drVBMSt = GetShortBE(&buf[0x0e]); + pMDB->drAllocPtr = GetShortBE(&buf[0x10]); + pMDB->drNmAlBlks = GetShortBE(&buf[0x12]); + pMDB->drAlBlkSiz = GetLongBE(&buf[0x14]); + pMDB->drClpSiz = GetLongBE(&buf[0x18]); + pMDB->drAlBlSt = GetShortBE(&buf[0x1c]); + pMDB->drNxtCNID = GetLongBE(&buf[0x1e]); + pMDB->drFreeBks = GetShortBE(&buf[0x22]); + memcpy(pMDB->drVN, &buf[0x24], sizeof(pMDB->drVN)); + pMDB->drVolBkUp = GetLongBE(&buf[0x40]); + pMDB->drVSeqNum = GetShortBE(&buf[0x44]); + pMDB->drWrCnt = GetLongBE(&buf[0x46]); + pMDB->drXTClpSiz = GetLongBE(&buf[0x4a]); + pMDB->drCTClpSiz = GetLongBE(&buf[0x4e]); + pMDB->drNmRtDirs = GetShortBE(&buf[0x52]); + pMDB->drFilCnt = GetLongBE(&buf[0x54]); + pMDB->drDirCnt = GetLongBE(&buf[0x58]); + for (int i = 0; i < (int) NELEM(pMDB->drFndrInfo); i++) + pMDB->drFndrInfo[i] = GetLongBE(&buf[0x5c + i * 4]); + pMDB->drVCSize = GetShortBE(&buf[0x7c]); + pMDB->drVBMCSize = GetShortBE(&buf[0x7e]); + pMDB->drCtlCSize = GetShortBE(&buf[0x80]); + pMDB->drXTFlSize = GetLongBE(&buf[0x82]); + //UnpackExtDataRec(&pMDB->drXTExtRec, &buf[0x86]); // 12 bytes + pMDB->drCTFlSize = GetLongBE(&buf[0x92]); + //UnpackExtDataRec(&pMDB->drXTExtRec, &buf[0x96]); + // next field at 0xa2 +} + +/* + * See if this looks like an HFS volume. + * + * We test a few fields in the master directory block for validity. + */ +/*static*/ DIError DiskFSHFS::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder) +{ + DIError dierr = kDIErrNone; + MasterDirBlock mdb; + uint8_t blkBuf[kBlkSize]; + + dierr = pImg->ReadBlockSwapped(kMasterDirBlock, blkBuf, imageOrder, + DiskImg::kSectorOrderProDOS); + if (dierr != kDIErrNone) + goto bail; + + UnpackMDB(blkBuf, &mdb); + + if (mdb.drSigWord != kSignature) { + dierr = kDIErrFilesystemNotFound; + goto bail; + } + if ((mdb.drAlBlkSiz & 0x1ff) != 0) { + // allocation block size must be a multiple of 512 + LOGI(" HFS: found allocation block size = %u, rejecting", + mdb.drAlBlkSiz); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + if (mdb.drVN[0] == 0 || mdb.drVN[0] > kMaxVolumeName) { + LOGI(" HFS: volume name has len = %d, rejecting", mdb.drVN[0]); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + long minBlocks; + minBlocks = mdb.drNmAlBlks * (mdb.drAlBlkSiz / kBlkSize) + mdb.drAlBlSt + 2; + if (minBlocks > pImg->GetNumBlocks()) { + // We're probably trying to open a 1GB volume as if it were only + // 32MB. Maybe this is a full HFS partition and we're trying to + // see if it's a CFFA image. Whatever the case, we can't do this. + LOGI("HFS: volume exceeds disk image size (%ld vs %ld)", + minBlocks, pImg->GetNumBlocks()); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + // looks good! + +bail: + return dierr; +} + +/* + * Test to see if the image is an HFS disk. + */ +/*static*/ DIError DiskFSHFS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency) +{ + //return kDIErrFilesystemNotFound; // DEBUG DEBUG DEBUG + + /* must be block format, should be at least 720K */ + if (!pImg->GetHasBlocks() || pImg->GetNumBlocks() < kExpectedMinBlocks) + 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::kFormatMacHFS; + return kDIErrNone; + } + } + + LOGI(" HFS didn't find valid FS"); + return kDIErrFilesystemNotFound; +} + +/* + * Load some stuff from the volume header. + */ +DIError DiskFSHFS::LoadVolHeader(void) +{ + DIError dierr = kDIErrNone; + MasterDirBlock mdb; + uint8_t blkBuf[kBlkSize]; + + if (fLocalTimeOffset == -1) { + struct tm* ptm; + struct tm tmWhen; + time_t when; + int isDst; + + when = time(NULL); + isDst = localtime(&when)->tm_isdst; + + ptm = gmtime(&when); + if (ptm != NULL) { + tmWhen = *ptm; // make a copy -- static buffers in time functions + tmWhen.tm_isdst = isDst; + + fLocalTimeOffset = (long) (when - mktime(&tmWhen)); + } else + fLocalTimeOffset = 0; + + LOGI(" HFS computed local time offset = %.3f hours", + fLocalTimeOffset / 3600.0); + } + + dierr = fpImg->ReadBlock(kMasterDirBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + UnpackMDB(blkBuf, &mdb); + + /* + * The minimum size of the volume is "number of allocation blocks" plus + * "first allocation block" (to avoid the OS overhead) plus 2 (because + * there's a backup copy of the MDB in the next-to-last block, and + * nothing at all in the very last block). + * + * This isn't the total size, because on larger volumes there can be + * some padding between the last usable block and the backup MDB. The + * only way to find the MDB is to take the DiskImg's block size and + * subtract 2. + */ + assert((mdb.drAlBlkSiz % kBlkSize) == 0); + fNumAllocationBlocks = mdb.drNmAlBlks; + fAllocationBlockSize = mdb.drAlBlkSiz; + fTotalBlocks = fpImg->GetNumBlocks(); + + uint32_t minBlocks; + minBlocks = mdb.drNmAlBlks * (mdb.drAlBlkSiz / kBlkSize) + mdb.drAlBlSt + 2; + assert(fTotalBlocks >= minBlocks); // verified during fs tests + + int volNameLen; + volNameLen = mdb.drVN[0]; + if (volNameLen > kMaxVolumeName) { + assert(false); // should've been trapped earlier + volNameLen = kMaxVolumeName; + } + memcpy(fVolumeName, &mdb.drVN[1], volNameLen); + fVolumeName[volNameLen] = '\0'; + SetVolumeID(); + + fNumFiles = mdb.drFilCnt; + fNumDirectories = mdb.drDirCnt; + fCreatedDateTime = mdb.drCrDate; + fModifiedDateTime = mdb.drLsMod; + + /* + * Create a "magic" directory entry for the volume directory. This + * must come first in the file list. + */ + A2FileHFS* pFile; + pFile = new A2FileHFS(this); + if (pFile == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + pFile->fIsDir = true; + pFile->fIsVolumeDir = true; + pFile->fType = 0; + pFile->fCreator = 0; + strcpy(pFile->fFileName, fVolumeName); // vol names are shorter than + pFile->SetPathName(":", fVolumeName); // filenames, so it fits + pFile->fDataLength = 0; + pFile->fRsrcLength = -1; + pFile->fCreateWhen = + (time_t) (fCreatedDateTime - kDateTimeOffset) - fLocalTimeOffset; + pFile->fModWhen = + (time_t) (fModifiedDateTime - kDateTimeOffset) - fLocalTimeOffset; + pFile->fAccess = DiskFS::kFileAccessUnlocked; + + //LOGI("GOT *** '%s' '%s'", pFile->fFileName, pFile->fPathName); + + AddFileToList(pFile); + +bail: + return dierr; +} + +/* + * Set the volume ID based on fVolumeName. + */ +void DiskFSHFS::SetVolumeID(void) +{ + strcpy(fVolumeID, "HFS "); + strcat(fVolumeID, fVolumeName); +} + +/* + * Blank out the volume usage map. The HFS volume bitmap is not yet supported. + */ +void DiskFSHFS::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); +} + +/* + * Print some interesting fields to the debug log. + */ +void DiskFSHFS::DumpVolHeader(void) +{ + LOGI("HFS volume header read:"); + LOGI(" volume name = '%s'", fVolumeName); + LOGI(" total blocks = %d (allocSize=%d [x%u], numAllocs=%u)", + fTotalBlocks, fAllocationBlockSize, fAllocationBlockSize / kBlkSize, + fNumAllocationBlocks); + LOGI(" num directories=%d, num files=%d", + fNumDirectories, fNumFiles); + time_t when; + when = (time_t) (fCreatedDateTime - kDateTimeOffset - fLocalTimeOffset); + LOGI(" cre date=0x%08x %.24s", fCreatedDateTime, ctime(&when)); + when = (time_t) (fModifiedDateTime - kDateTimeOffset - fLocalTimeOffset); + LOGI(" mod date=0x%08x %.24s", fModifiedDateTime, ctime(&when)); +} + + +#ifndef EXCISE_GPL_CODE + +/* + * 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 DiskFSHFS::Initialize(InitMode initMode) +{ + DIError dierr = kDIErrNone; + char msg[kMaxVolumeName + 32]; + + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) + goto bail; + DumpVolHeader(); + + if (initMode == kInitHeaderOnly) { + LOGI(" HFS - headerOnly set, skipping file load"); + goto bail; + } + + sprintf(msg, "Scanning %s", fVolumeName); + if (!fpImg->UpdateScanProgress(msg)) { + LOGI(" HFS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + /* + * Open the volume with libhfs. We used to set HFS_OPT_NOCACHE to avoid + * consistency problems and reduce the risk of disk corruption should + * CiderPress fail, but it turns out libhfs doesn't write the volume + * bitmap or master dir block unless explicitly flushed anyway. Since + * the caching helps us a lot when just reading -- 4 seconds vs. 9 for + * a CD-ROM over gigabit Ethernet -- we leave it on, and explicitly + * flush every time we make a change. + */ + fHfsVol = hfs_callback_open(LibHFSCB, this, /*HFS_OPT_NOCACHE |*/ + (fpImg->GetReadOnly() ? HFS_MODE_RDONLY : HFS_MODE_RDWR)); + if (fHfsVol == NULL) { + LOGI("ERROR: hfs_opencallback failed: %s", hfs_error); + return kDIErrGeneric; + } + + /* volume dir is guaranteed to come first; if not, we need a lookup func */ + A2FileHFS* pVolumeDir; + pVolumeDir = (A2FileHFS*) GetNextFile(NULL); + + dierr = RecursiveDirAdd(pVolumeDir, ":", 0); + if (dierr != kDIErrNone) + goto bail; + + SetVolumeUsageMap(); + + /* + * Make sure there's nothing lingering. libhfs will fiddle around with + * the MDB if it looks like the volume wasn't unmounted cleanly last time. + */ + hfs_flush(fHfsVol); + +bail: + return dierr; +} + +/* + * Callback function from libhfs. Can read/write/seek. + * + * This is a little clumsy, but it allows us to maintain a separation from + * the libhfs code (which is GPLed). + * + * Returns -1 on failure. + */ +unsigned long DiskFSHFS::LibHFSCB(void* vThis, int op, unsigned long arg1, void* arg2) +{ + DiskFSHFS* pThis = (DiskFSHFS*) vThis; + unsigned long result = (unsigned long) -1; + + assert(pThis != NULL); + + switch (op) { + case HFS_CB_VOLSIZE: + //LOGI(" HFSCB vol size = %ld blocks", pThis->fTotalBlocks); + result = pThis->fTotalBlocks; + break; + case HFS_CB_READ: // arg1=block, arg2=buffer + //LOGI(" HFSCB read block %lu", arg1); + if (arg1 < pThis->fTotalBlocks && arg2 != NULL) { + DIError err = pThis->fpImg->ReadBlock(arg1, arg2); + if (err == kDIErrNone) + result = 0; + else { + LOGI(" HFSCB read %lu failed", arg1); + } + } + break; + case HFS_CB_WRITE: + LOGI(" HFSCB write block %lu", arg1); + if (arg1 < pThis->fTotalBlocks && arg2 != NULL) { + DIError err = pThis->fpImg->WriteBlock(arg1, arg2); + if (err == kDIErrNone) + result = 0; + else { + LOGI(" HFSCB write %lu failed", arg1); + } + } + break; + case HFS_CB_SEEK: // arg1=block, arg2=unused + /* just verify that the seek is legal */ + //LOGI(" HFSCB seek block %lu", arg1); + if (arg1 < pThis->fTotalBlocks) + result = arg1; + break; + default: + assert(false); + } + + //LOGI("--- HFSCB returning %lu", result); + return result; +} + +/* + * Determine the amount of free space on the disk. + */ +DIError DiskFSHFS::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const +{ + assert(fHfsVol != NULL); + + hfsvolent volEnt; + if (hfs_vstat(fHfsVol, &volEnt) != 0) + return kDIErrGeneric; + + *pTotalUnits = volEnt.totbytes / 512; + *pFreeUnits = volEnt.freebytes / 512; + *pUnitSize = 512; + + return kDIErrNone; +} + +/* + * Recursively traverse the filesystem. + */ +DIError DiskFSHFS::RecursiveDirAdd(A2File* pParent, const char* basePath, int depth) +{ + DIError dierr = kDIErrNone; + hfsdir* dir; + hfsdirent dirEntry; + char* pathBuf = NULL; + int nameOffset; + + /* if we get too deep, assume it's a loop */ + if (depth > kMaxDirectoryDepth) { + dierr = kDIErrDirectoryLoop; + goto bail; + } + + //LOGI(" HFS RecursiveDirAdd '%s'", basePath); + dir = hfs_opendir(fHfsVol, basePath); + if (dir == NULL) { + printf(" HFS unable to open dir '%s'\n", basePath); + LOGI(" HFS unable to open dir '%s'", basePath); + dierr = kDIErrGeneric; + goto bail; + } + + if (strcmp(basePath, ":") == 0) + basePath = ""; + + nameOffset = strlen(basePath) +1; + pathBuf = new char[nameOffset + A2FileHFS::kMaxFileName +1]; + if (pathBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + strcpy(pathBuf, basePath); + pathBuf[nameOffset-1] = A2FileHFS::kFssep; + pathBuf[nameOffset] = '\0'; // not needed + + while (hfs_readdir(dir, &dirEntry) != -1) { + A2FileHFS* pFile; + + pFile = new A2FileHFS(this); + + pFile->InitEntry(&dirEntry); + + pFile->SetPathName(basePath, pFile->fFileName); + pFile->SetParent(pParent); + AddFileToList(pFile); + + if (!fpImg->UpdateScanProgress(NULL)) { + LOGI(" HFS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + if (dirEntry.flags & HFS_ISDIR) { + strcpy(pathBuf + nameOffset, dirEntry.name); + dierr = RecursiveDirAdd(pFile, pathBuf, depth+1); + if (dierr != kDIErrNone) + goto bail; + } + } + +bail: + delete[] pathBuf; + return dierr; +} + +/* + * Initialize an A2FileHFS structure from the stuff in an hfsdirent. + */ +void A2FileHFS::InitEntry(const hfsdirent* dirEntry) +{ + //printf("--- File '%s' flags=0x%08x fdflags=0x%08x type='%s'\n", + // dirEntry.name, dirEntry.flags, dirEntry.fdflags, + // dirEntry.u.file.type); + + fIsVolumeDir = false; + memcpy(fFileName, dirEntry->name, A2FileHFS::kMaxFileName+1); + fFileName[A2FileHFS::kMaxFileName] = '\0'; // make sure + + if (dirEntry->flags & HFS_ISLOCKED) + fAccess = DiskFS::kFileAccessLocked; + else + fAccess = DiskFS::kFileAccessUnlocked; + if (dirEntry->fdflags & HFS_FNDR_ISINVISIBLE) + fAccess |= A2FileProDOS::kAccessInvisible; + + if (dirEntry->flags & HFS_ISDIR) { + fIsDir = true; + fType = fCreator = 0; + fDataLength = 0; + fRsrcLength = -1; + } else { + uint8_t* pType; + + fIsDir = false; + + pType = (uint8_t*) dirEntry->u.file.type; + fType = + pType[0] << 24 | pType[1] << 16 | pType[2] << 8 | pType[3]; + pType = (uint8_t*) dirEntry->u.file.creator; + fCreator = + pType[0] << 24 | pType[1] << 16 | pType[2] << 8 | pType[3]; + fDataLength = dirEntry->u.file.dsize; + fRsrcLength = dirEntry->u.file.rsize; + + /* + * Resource fork must be at least 512 bytes for Finder, so if + * it has zero length then the file must not have one. + */ + if (fRsrcLength == 0) + fRsrcLength = -1; + } + + /* + * Create/modified dates (we ignore the "last backup" date). The + * hfslib functions convert to time_t for us. + */ + fCreateWhen = dirEntry->crdate; + fModWhen = dirEntry->mddate; +} + +/* + * Return "true" if "name" is valid for use as an HFS volume name. + */ +/*static*/ bool DiskFSHFS::IsValidVolumeName(const char* name) +{ + if (name == NULL) + return false; + + int len = strlen(name); + if (len < 1 || len > kMaxVolumeName) + return false; + + while (*name != '\0') { + if (*name == A2FileHFS::kFssep) + return false; + name++; + } + + return true; +} + +/* + * Return "true" if "name" is valid for use as an HFS file name. + */ +/*static*/ bool DiskFSHFS::IsValidFileName(const char* name) +{ + if (name == NULL) + return false; + + int len = strlen(name); + if (len < 1 || len > A2FileHFS::kMaxFileName) + return false; + + while (*name != '\0') { + if (*name == A2FileHFS::kFssep) + return false; + name++; + } + + return true; +} + +/* + * Format the current volume with HFS. + */ +DIError DiskFSHFS::Format(DiskImg* pDiskImg, const char* volName) +{ + assert(strlen(volName) > 0 && strlen(volName) <= kMaxVolumeName); + + if (!IsValidVolumeName(volName)) + return kDIErrInvalidArg; + + /* set fpImg so calls that rely on it will work; we un-set it later */ + assert(fpImg == NULL); + SetDiskImg(pDiskImg); + + /* need this for callback function */ + fTotalBlocks = fpImg->GetNumBlocks(); + + // need HFS_OPT_2048 for CD-ROM? + if (hfs_callback_format(LibHFSCB, this, 0, volName) != 0) { + LOGI("hfs_callback_format failed (%s)", hfs_error); + return kDIErrGeneric; + } + + // no need to flush; HFS volume is closed + + SetDiskImg(NULL); // shouldn't really be set by us + return kDIErrNone; +} + +/* + * Normalize an HFS path. Invokes DoNormalizePath and handles the buffer + * management (if the normalized path doesn't fit in "*pNormalizedBufLen" + * bytes, we set "*pNormalizedBufLen to the required length). + * + * This is invoked from the generalized "add" function in CiderPress, which + * doesn't want to understand the ins and outs of pathnames. + */ +DIError DiskFSHFS::NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) +{ + DIError dierr = kDIErrNone; + char* normalizedPath = NULL; + int len; + + assert(pNormalizedBufLen != NULL); + assert(normalizedBuf != NULL || *pNormalizedBufLen == 0); + + dierr = DoNormalizePath(path, fssep, &normalizedPath); + if (dierr != kDIErrNone) + goto bail; + + assert(normalizedPath != NULL); + len = strlen(normalizedPath); + if (normalizedBuf == NULL || *pNormalizedBufLen <= len) { + /* too short */ + dierr = kDIErrDataOverrun; + } else { + /* fits */ + strcpy(normalizedBuf, normalizedPath); + } + + *pNormalizedBufLen = len+1; // alloc room for the '\0' + +bail: + delete[] normalizedPath; + return dierr; +} + +/* + * Normalize an HFS path. This requires separating each path component + * out, making it HFS-compliant, and then putting it back in. + * The fssep could be anything, so we need to change it to kFssep. + * + * The caller must delete[] "*pNormalizedPath". + */ +DIError DiskFSHFS::DoNormalizePath(const char* path, char fssep, + char** pNormalizedPath) +{ + DIError dierr = kDIErrNone; + char* workBuf = NULL; + char* partBuf = NULL; + char* outputBuf = NULL; + char* start; + char* end; + char* outPtr; + + assert(path != NULL); + workBuf = new char[strlen(path)+1]; + partBuf = new char[strlen(path)+1 +1]; // need +1 for prepending letter + outputBuf = new char[strlen(path) * 2]; + if (workBuf == NULL || partBuf == NULL || outputBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + strcpy(workBuf, path); + outputBuf[0] = '\0'; + + outPtr = outputBuf; + start = workBuf; + while (*start != '\0') { + //char* origStart = start; // need for debug msg + int partIdx; + + if (fssep == '\0') { + end = NULL; + } else { + end = strchr(start, fssep); + if (end != NULL) + *end = '\0'; + } + partIdx = 0; + + /* + * Copy, converting colons to underscores. We should strip out any + * illegal characters here, but there's not much in HFS that's + * considered illegal. + */ + while (*start != '\0') { + if (*start == A2FileHFS::kFssep) + partBuf[partIdx++] = '_'; + else + partBuf[partIdx++] = *start; + start++; + } + + /* + * Truncate at 31 chars, preserving anything that looks like a + * filename extension. "partIdx" represents the length of the + * string at this point. "partBuf" holds the string, which we + * want to null-terminate before proceeding. + * + * Try to keep the filename extension, if any. + */ + partBuf[partIdx] = '\0'; + if (partIdx > A2FileHFS::kMaxFileName) { + const char* pDot = strrchr(partBuf, '.'); + //int DEBUGDOTLEN = pDot - partBuf; + if (pDot != NULL && partIdx - (pDot-partBuf) <= kMaxExtensionLen) { + int dotLen = partIdx - (pDot-partBuf); + memmove(partBuf + (A2FileProDOS::kMaxFileName - dotLen), + pDot, dotLen); // don't use memcpy, move might overlap + } + partIdx = A2FileProDOS::kMaxFileName; + } + partBuf[partIdx] = '\0'; + + //LOGI(" HFS Converted component '%s' to '%s'", + // origStart, partBuf); + + if (outPtr != outputBuf) + *outPtr++ = A2FileHFS::kFssep; + strcpy(outPtr, partBuf); + outPtr += partIdx; + + /* + * Continue with next segment. + */ + if (end == NULL) + break; + start = end+1; + } + + *outPtr = '\0'; + + LOGI(" HFS Converted path '%s' to '%s' (fssep='%c')", + path, outputBuf, fssep); + assert(*outputBuf != '\0'); + + *pNormalizedPath = outputBuf; + outputBuf = NULL; + +bail: + delete[] workBuf; + delete[] partBuf; + delete[] outputBuf; + return dierr; +} + +/* + * Compare two Macintosh filename strings. + * + * This requires some effort because the Macintosh Roman character set + * doesn't sort the same way that ASCII does. HFS is case-insensitive but + * case-preserving, so we need to deal with that too. The hfs_charorder + * table takes care of it. + * + * Returns <0, ==0, or >0 depending on whether sstr1 is lexically less than, + * equal to, or greater than sstr2. + */ +/*static*/ int DiskFSHFS::CompareMacFileNames(const char* sstr1, const char* sstr2) +{ + const uint8_t* str1 = (const uint8_t*) sstr1; + const uint8_t* str2 = (const uint8_t*) sstr2; + int diff; + + while (*str1 && *str2) { + diff = hfs_charorder[*str1] - hfs_charorder[*str2]; + + if (diff != 0) + return diff; + + str1++; + str2++; + } + + return *str1 - *str2; +} + +/* + * Keep tweaking the filename until it no longer matches an existing file. + * The first time this is called we don't know if the name is unique or not, + * so we need to start by checking that. + * + * We have our choice between the DiskFS GetFileByName(), which traverses + * a linear list, and hfs_stat(), which uses more efficient data structures + * but may require disk reads. We use the DiskFS interface, on the assumption + * that someday we'll switch the linear list to a tree structure. + */ +DIError DiskFSHFS::MakeFileNameUnique(const char* pathName, char** pUniqueName) +{ + A2File* pFile; + const int kMaxExtra = 3; + const int kMaxDigits = 999; + char* uniqueName; + char* fileName; // points inside uniqueName + + assert(pathName != NULL); + assert(pathName[0] == A2FileHFS::kFssep); + + /* see if it exists */ + pFile = GetFileByName(pathName+1); + if (pFile == NULL) { + *pUniqueName = NULL; + return kDIErrNone; + } + + /* make a copy we can chew on */ + uniqueName = new char[strlen(pathName) + kMaxExtra +1]; + strcpy(uniqueName, pathName); + + fileName = strrchr(uniqueName, A2FileHFS::kFssep); + assert(fileName != NULL); + fileName++; + + int nameLen = strlen(fileName); + int dotOffset=0, dotLen=0; + char dotBuf[kMaxExtensionLen+1]; + + /* ensure the result will be null-terminated */ + memset(fileName + nameLen, 0, kMaxExtra+1); + + /* + * If this has what looks like a filename extension, grab it. We want + * to preserve ".gif", ".c", etc., since the filetypes don't necessarily + * do everything we need. + */ + const char* cp = strrchr(fileName, '.'); + if (cp != NULL) { + int tmpOffset = cp - fileName; + if (tmpOffset > 0 && nameLen - tmpOffset <= kMaxExtensionLen) { + LOGI(" HFS (keeping extension '%s')", cp); + assert(strlen(cp) <= kMaxExtensionLen); + strcpy(dotBuf, cp); + dotOffset = tmpOffset; + dotLen = nameLen - dotOffset; + } + } + + int digits = 0; + int digitLen; + int copyOffset; + char digitBuf[kMaxExtra+1]; + do { + if (digits == kMaxDigits) + return kDIErrFileExists; + digits++; + + /* not the most efficient way to do this, but it'll do */ + sprintf(digitBuf, "%d", digits); + digitLen = strlen(digitBuf); + if (nameLen + digitLen > A2FileHFS::kMaxFileName) + copyOffset = A2FileHFS::kMaxFileName - dotLen - digitLen; + else + copyOffset = nameLen - dotLen; + memcpy(fileName + copyOffset, digitBuf, digitLen); + if (dotLen != 0) + memcpy(fileName + copyOffset + digitLen, dotBuf, dotLen); + } while (GetFileByName(uniqueName+1) != NULL); + + LOGI(" HFS converted to unique name: %s", uniqueName); + + *pUniqueName = uniqueName; + return kDIErrNone; +} + +/* + * Create a new file or directory. Automatically creates the base path + * if necessary. + * + * NOTE: much of this was cloned out of the ProDOS code. We probably want + * a stronger set of utility functions in the parent class now that we have + * more than one hierarchical file system. + */ +DIError DiskFSHFS::CreateFile(const CreateParms* pParms, A2File** ppNewFile) +{ + DIError dierr = kDIErrNone; + char typeStr[5], creatorStr[5]; + char* normalizedPath = NULL; + char* basePath = NULL; + char* fileName = NULL; + char* fullPath = NULL; + A2FileHFS* pSubdir = NULL; + A2FileHFS* pNewFile = NULL; + hfsfile* pHfsFile = NULL; + const bool createUnique = (GetParameter(kParm_CreateUnique) != 0); + + assert(fHfsVol != NULL); + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + assert(pParms != NULL); + assert(pParms->pathName != NULL); + assert(pParms->storageType == A2FileProDOS::kStorageSeedling || + pParms->storageType == A2FileProDOS::kStorageExtended || + pParms->storageType == A2FileProDOS::kStorageDirectory); + // kStorageVolumeDirHeader not allowed -- that's created by Format + LOGI(" HFS ---v--- CreateFile '%s'", pParms->pathName); + + /* + * Normalize the pathname so that all components are HFS-safe + * and separated by ':'. + * + * This must not "sanitize" the path. We need to be working with the + * original characters, not the sanitized-for-display versions. + */ + assert(pParms->pathName != NULL); + dierr = DoNormalizePath(pParms->pathName, pParms->fssep, + &normalizedPath); + if (dierr != kDIErrNone) + goto bail; + assert(normalizedPath != NULL); + + /* + * The normalized path lacks a leading ':', and might need to + * have some digits added to make the name unique. + */ + fullPath = new char[strlen(normalizedPath)+2]; + fullPath[0] = A2FileHFS::kFssep; + strcpy(fullPath+1, normalizedPath); + delete[] normalizedPath; + normalizedPath = NULL; + + /* + * Make the name unique within the current directory. This requires + * appending digits until the name doesn't match any others. + */ + if (createUnique && + pParms->storageType != A2FileProDOS::kStorageDirectory) + { + char* uniquePath; + + dierr = MakeFileNameUnique(fullPath, &uniquePath); + if (dierr != kDIErrNone) + goto bail; + if (uniquePath != NULL) { + delete[] fullPath; + fullPath = uniquePath; + } + } else { + /* can't make unique; check to see if it already exists */ + hfsdirent dirEnt; + if (hfs_stat(fHfsVol, fullPath, &dirEnt) == 0) { + if (pParms->storageType == A2FileProDOS::kStorageDirectory) + dierr = kDIErrDirectoryExists; + else + dierr = kDIErrFileExists; + goto bail; + } + } + + /* + * Split the base path and filename apart. + */ + char* cp; + cp = strrchr(fullPath, A2FileHFS::kFssep); + assert(cp != NULL); + if (cp == fullPath) { + assert(basePath == NULL); + fileName = new char[strlen(fullPath) +1]; + strcpy(fileName, fullPath); + } else { + int dirNameLen = cp - fullPath; + + fileName = new char[strlen(cp+1) +1]; + strcpy(fileName, cp+1); + basePath = new char[dirNameLen+1]; + strncpy(basePath, fullPath, dirNameLen); + basePath[dirNameLen] = '\0'; + } + + LOGI("SPLIT: '%s' '%s'", basePath, fileName); + + assert(fileName != NULL); + + /* + * Open the base path. If it doesn't exist, create it recursively. + */ + if (basePath != NULL) { + LOGI(" HFS Creating '%s' in '%s'", fileName, basePath); + /* + * Open the named subdir, creating it if it doesn't exist. We need + * to check basePath+1 because we're comparing against what's in our + * linear file list, and that doesn't include the leading ':'. + */ + pSubdir = (A2FileHFS*)GetFileByName(basePath+1, CompareMacFileNames); + if (pSubdir == NULL) { + LOGI(" HFS Creating subdir '%s'", basePath); + A2File* pNewSub; + CreateParms newDirParms; + newDirParms.pathName = basePath; + newDirParms.fssep = A2FileHFS::kFssep; + newDirParms.storageType = A2FileProDOS::kStorageDirectory; + newDirParms.fileType = 0; + newDirParms.auxType = 0; + newDirParms.access = 0; + newDirParms.createWhen = newDirParms.modWhen = time(NULL); + dierr = this->CreateFile(&newDirParms, &pNewSub); + if (dierr != kDIErrNone) + goto bail; + assert(pNewSub != NULL); + + pSubdir = (A2FileHFS*) pNewSub; + } + + /* + * And now the annoying part. We need to reconstruct basePath out + * of the filenames actually present, rather than relying on the + * argument passed in. That's because HFS is case-insensitive but + * case-preserving. It's not crucial for our inner workings, but the + * linear file list in the DiskFS should have accurate strings. + * (It'll work just fine, but the display might show the wrong values + * for parent directories until they reload the disk.) + * + * On the bright side, we know exactly how long the string needs + * to be, so we can just stomp on it in place. Assuming, of course, + * that the filename created matches up with what the filename + * normalizer came up with, which we can guarantee since (a) everybody + * uses the same normalizer and (b) the "uniqueify" stuff doesn't + * kick in for subdirs because we wouldn't be creating a new subdir + * if it didn't already exist. + * + * This is essentially the same as RegeneratePathName(), but that's + * meant for a situation where the filename already exists. + */ + A2FileHFS* pBaseDir = pSubdir; + int basePathLen = strlen(basePath); + while (!pBaseDir->IsVolumeDirectory()) { + const char* fixedName = pBaseDir->GetFileName(); + int fixedLen = strlen(fixedName); + if (fixedLen > basePathLen) { + assert(false); + break; + } + assert(basePathLen == fixedLen || + *(basePath + (basePathLen-fixedLen-1)) == kDIFssep); + memcpy(basePath + (basePathLen-fixedLen), fixedName, fixedLen); + basePathLen -= fixedLen+1; + + pBaseDir = (A2FileHFS*) pBaseDir->GetParent(); + assert(pBaseDir != NULL); + } + // check the math; we should be left with the leading ':' + if (pSubdir->IsVolumeDirectory()) + assert(basePathLen == 1); + else + assert(basePathLen == 0); + } else { + /* open the volume directory */ + LOGI(" HFS Creating '%s' in volume dir", fileName); + /* volume dir must be first in the list */ + pSubdir = (A2FileHFS*) GetNextFile(NULL); + assert(pSubdir != NULL); + assert(pSubdir->IsVolumeDirectory()); + } + if (pSubdir == NULL) { + LOGI(" HFS Unable to open subdir '%s'", basePath); + dierr = kDIErrFileNotFound; + goto bail; + } + + /* + * Figure out file type. + */ + A2FileHFS::ConvertTypeToHFS(pParms->fileType, pParms->auxType, + typeStr, creatorStr); + + /* + * Create the file or directory. Populate "dirEnt" with the details. + */ + hfsdirent dirEnt; + if (pParms->storageType == A2FileProDOS::kStorageDirectory) { + /* create the directory */ + if (hfs_mkdir(fHfsVol, fullPath) != 0) { + LOGI(" HFS mkdir '%s' failed: %s", fullPath, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + if (hfs_stat(fHfsVol, fullPath, &dirEnt) != 0) { + LOGI(" HFS stat on new dir failed: %s", hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + /* create date *might* be useful, but probably not worth adjusting */ + } else { + /* create, and open, the file */ + pHfsFile = hfs_create(fHfsVol, fullPath, typeStr, creatorStr); + if (pHfsFile == NULL) { + LOGI(" HFS create failed: %s", hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + if (hfs_fstat(pHfsFile, &dirEnt) != 0) { + LOGI(" HFS fstat on new file failed: %s", hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + + /* set the attributes according to pParms, and update the file */ + dirEnt.crdate = pParms->createWhen; + dirEnt.mddate = pParms->modWhen; + if (pParms->access & A2FileProDOS::kAccessInvisible) + dirEnt.fdflags |= HFS_FNDR_ISINVISIBLE; + else + dirEnt.fdflags &= ~HFS_FNDR_ISINVISIBLE; + if ((pParms->access & ~A2FileProDOS::kAccessInvisible) == kFileAccessLocked) + dirEnt.flags |= HFS_ISLOCKED; + else + dirEnt.flags &= ~HFS_ISLOCKED; + + (void) hfs_fsetattr(pHfsFile, &dirEnt); + (void) hfs_close(pHfsFile); + pHfsFile = NULL; + } + + /* + * Success! + * + * Create a new entry and set the structure fields. + */ + pNewFile = new A2FileHFS(this); + pNewFile->InitEntry(&dirEnt); + pNewFile->SetPathName(basePath == NULL ? "" : basePath, pNewFile->fFileName); + pNewFile->SetParent(pSubdir); + + /* + * Because we're hierarchical, and we guarantee that the contents of + * subdirectories are grouped together, we must insert the file into an + * appropriate place in the list rather than just throwing it onto the + * end. + * + * The proper location for the new file in the linear list is in sorted + * order with the files in the current directory. We have to be careful + * here because libhfs is going to use Macintosh Roman sort ordering, + * which may be different from ASCII ordering. Worst case: we end up + * putting it in the wrong place and it jumps around when the disk image + * is reopened. + * + * All files in a subdir appear in the list after that subdir, but there + * might be intervening entries from deeper directories. So we have to + * chase through some or all of the file list to find the right place. + * Not great, but we don't have enough files or do adds often enough to + * make this worth optimizing. + */ + A2File* pLastSubdirFile; + A2File* pPrevFile; + A2File* pNextFile; + + pPrevFile = pLastSubdirFile = pSubdir; + pNextFile = GetNextFile(pPrevFile); + while (pNextFile != NULL) { + if (pNextFile->GetParent() == pNewFile->GetParent()) { + /* in same subdir, compare names */ + if (CompareMacFileNames(pNextFile->GetPathName(), + pNewFile->GetPathName()) > 0) + { + /* passed it; insert new after previous file */ + pLastSubdirFile = pPrevFile; + LOGI(" HFS Found '%s' > cur(%s)", pNextFile->GetPathName(), + pNewFile->GetPathName()); + break; + } + + /* still too early; save in case it's last one in dir */ + pLastSubdirFile = pNextFile; + } + pPrevFile = pNextFile; + pNextFile = GetNextFile(pNextFile); + } + + /* insert us after last file we saw that was part of the same subdir */ + LOGI(" HFS inserting '%s' after '%s'", pNewFile->GetPathName(), + pLastSubdirFile->GetPathName()); + InsertFileInList(pNewFile, pLastSubdirFile); + //LOGI("LIST NOW:"); + //DumpFileList(); + + *ppNewFile = pNewFile; + pNewFile = NULL; + +bail: + delete pNewFile; + delete[] normalizedPath; + delete[] basePath; + delete[] fileName; + delete[] fullPath; + hfs_flush(fHfsVol); + LOGI(" HFS ---^--- CreateFile '%s' DONE", pParms->pathName); + return dierr; +} + +/* + * Delete the named file. + * + * We need to use a different call for file vs. directory. + */ +DIError DiskFSHFS::DeleteFile(A2File* pGenericFile) +{ + DIError dierr = kDIErrNone; + char* pathName = NULL; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + if (pGenericFile->IsFileOpen()) + return kDIErrFileOpen; + + A2FileHFS* pFile = (A2FileHFS*) pGenericFile; + pathName = pFile->GetLibHFSPathName(); + LOGI(" Deleting '%s'", pathName); + + if (pFile->IsDirectory()) { + if (hfs_rmdir(fHfsVol, pathName) != 0) { + LOGI(" HFS rmdir failed '%s': '%s'", pathName, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + } else { + if (hfs_delete(fHfsVol, pathName) != 0) { + LOGI(" HFS delete failed '%s': '%s'", pathName, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + } + + /* + * Remove the A2File* from the list. + */ + DeleteFileFromList(pFile); + +bail: + hfs_flush(fHfsVol); + delete[] pathName; + return dierr; +} + +/* + * Rename a file. + * + * Pass in a pointer to the file and a string with the new filename (just + * the filename, not a pathname -- this function doesn't move files + * between directories). The new name must already be normalized. + * + * Renaming the magic volume directory "file" is not allowed. + * + * We don't try to keep AppleWorks aux type flags consistent (they're used + * to determine which characters are lower case on ProDOS disks). They'll + * get fixed up when we copy them to a ProDOS disk, which is the only way + * 8-bit AppleWorks can get at them. + */ +DIError DiskFSHFS::RenameFile(A2File* pGenericFile, const char* newName) +{ + DIError dierr = kDIErrNone; + A2FileHFS* pFile = (A2FileHFS*) pGenericFile; + char* colonOldName = NULL; + char* colonNewName = NULL; + + if (pFile == NULL || newName == NULL) + return kDIErrInvalidArg; + if (!IsValidFileName(newName)) + return kDIErrInvalidArg; + if (pFile->IsVolumeDirectory()) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + char* lastColon; + + colonOldName = pFile->GetLibHFSPathName(); // adds ':' to start of string + lastColon = strrchr(colonOldName, A2FileHFS::kFssep); + assert(lastColon != NULL); + if (lastColon == colonOldName) { + /* in root dir */ + colonNewName = new char[1 + strlen(newName) +1]; + colonNewName[0] = A2FileHFS::kFssep; + strcpy(colonNewName+1, newName); + } else { + /* prepend subdir */ + int len = lastColon - colonOldName +1; // e.g. ":path1:path2:" + colonNewName = new char[len + strlen(newName) +1]; + strncpy(colonNewName, colonOldName, len); + strcpy(colonNewName+len, newName); + } + + LOGI(" HFS renaming '%s' to '%s'", colonOldName, colonNewName); + + if (hfs_rename(fHfsVol, colonOldName, colonNewName) != 0) { + LOGI(" HFS rename('%s','%s') failed: %s", + colonOldName, colonNewName, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + + /* + * Success! Update the file name. + */ + strcpy(pFile->fFileName, newName); + + /* + * Now the fun part. If we simply renamed a file, we can just update the + * one entry. If we renamed a directory, life gets interesting because + * we store the full pathname in every A2FileHFS entry. (It's an + * efficiency win most of the time, but it's really annoying here.) + * + * HFS makes this especially unpleasant because it keeps the files + * arranged in sorted order. If we change a file's name, we may have to + * move it to a new position in the linear file list. If we don't, the + * list no longer reflects the order in which the files actually appear + * on the disk, and they'll shift around when we reload. + * + * There are two approaches: re-sort the list (awkward, since it's stored + * in a linked list -- we'd probably want to sort tags in a parallel + * structure), or find the affected block of files, find the new start + * position, and shift the entire range in one shot. + * + * This doesn't seem like something that anybody but me will ever care + * about, so I'm going to skip it for now. + */ + A2File* pCur; + if (pFile->IsDirectory()) { + /* do all files that come after us */ + pCur = pFile; + while (pCur != NULL) { + RegeneratePathName((A2FileHFS*) pCur); + pCur = GetNextFile(pCur); + } + } else { + RegeneratePathName(pFile); + } + +bail: + delete[] colonOldName; + delete[] colonNewName; + hfs_flush(fHfsVol); + return dierr; +} + +/* + * Regenerate fPathName for the specified file. + * + * Has no effect on the magic volume dir entry. + * + * This could be implemented more efficiently, but it's only used when + * renaming files, so there's not much point. + * + * [This was lifted straight out of the ProDOS sources. It should probably + * be moved into generic DiskFS.] + */ +DIError DiskFSHFS::RegeneratePathName(A2FileHFS* pFile) +{ + A2FileHFS* pParent; + char* buf = NULL; + int len; + + /* nothing to do here */ + if (pFile->IsVolumeDirectory()) + return kDIErrNone; + + /* compute the length of the path name */ + len = strlen(pFile->GetFileName()); + pParent = (A2FileHFS*) pFile->GetParent(); + while (!pParent->IsVolumeDirectory()) { + len++; // leave space for the ':' + len += strlen(pParent->GetFileName()); + + pParent = (A2FileHFS*) pParent->GetParent(); + } + + buf = new char[len+1]; + if (buf == NULL) + return kDIErrMalloc; + + /* generate the new path name */ + int partLen; + partLen = strlen(pFile->GetFileName()); + strcpy(buf + len - partLen, pFile->GetFileName()); + len -= partLen; + + pParent = (A2FileHFS*) pFile->GetParent(); + while (!pParent->IsVolumeDirectory()) { + assert(len > 0); + buf[--len] = A2FileHFS::kFssep; + + partLen = strlen(pParent->GetFileName()); + strncpy(buf + len - partLen, pParent->GetFileName(), partLen); + len -= partLen; + assert(len >= 0); + + pParent = (A2FileHFS*) pParent->GetParent(); + } + + LOGI("Replacing '%s' with '%s'", pFile->GetPathName(), buf); + pFile->SetPathName("", buf); + delete[] buf; + + return kDIErrNone; +} + +/* + * Change the HFS volume name. + * + * This uses the same libhfs interface that we use for renaming files. The + * Mac convention is to *not* start the volume name with a colon. In fact, + * the libhfs convention is to *end* the volume names with a colon. + */ +DIError DiskFSHFS::RenameVolume(const char* newName) +{ + DIError dierr = kDIErrNone; + A2FileHFS* pFile; + char* oldNameColon = NULL; + char* newNameColon = NULL; + + if (!IsValidVolumeName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + /* get file list entry for volume name */ + pFile = (A2FileHFS*) GetNextFile(NULL); + assert(strcmp(pFile->GetFileName(), fVolumeName) == 0); + + oldNameColon = new char[strlen(fVolumeName)+2]; + strcpy(oldNameColon, fVolumeName); + strcat(oldNameColon, ":"); + newNameColon = new char[strlen(newName)+2]; + strcpy(newNameColon, newName); + strcat(newNameColon, ":"); + + if (hfs_rename(fHfsVol, oldNameColon, newNameColon) != 0) { + LOGI(" HFS rename '%s' -> '%s' failed: %s", + oldNameColon, newNameColon, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + + /* update stuff */ + strcpy(fVolumeName, newName); + SetVolumeID(); + strcpy(pFile->fFileName, newName); + pFile->SetPathName("", newName); + +bail: + delete[] oldNameColon; + delete[] newNameColon; + hfs_flush(fHfsVol); + return dierr; +} + +/* + * Set file attributes. + */ +DIError DiskFSHFS::SetFileInfo(A2File* pGenericFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) +{ + DIError dierr = kDIErrNone; + A2FileHFS* pFile = (A2FileHFS*) pGenericFile; + hfsdirent dirEnt; + char* colonPath; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (pFile == NULL) + return kDIErrInvalidArg; + if (pFile->IsDirectory() || pFile->IsVolumeDirectory()) + return kDIErrNone; // impossible; just ignore it + + colonPath = pFile->GetLibHFSPathName(); + + if (hfs_stat(fHfsVol, colonPath, &dirEnt) != 0) { + LOGI(" HFS unable to stat '%s': %s", colonPath, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + + A2FileHFS::ConvertTypeToHFS(fileType, auxType, + dirEnt.u.file.type, dirEnt.u.file.creator); + + if (accessFlags & A2FileProDOS::kAccessInvisible) + dirEnt.fdflags |= HFS_FNDR_ISINVISIBLE; + else + dirEnt.fdflags &= ~HFS_FNDR_ISINVISIBLE; + + if ((accessFlags & ~A2FileProDOS::kAccessInvisible) == kFileAccessLocked) + dirEnt.flags |= HFS_ISLOCKED; + else + dirEnt.flags &= ~HFS_ISLOCKED; + + LOGD(" HFS setting '%s' to fdflags=0x%04x flags=0x%04x", + colonPath, dirEnt.fdflags, dirEnt.flags); + LOGD(" type=0x%08x creator=0x%08x", fileType, auxType); + + if (hfs_setattr(fHfsVol, colonPath, &dirEnt) != 0) { + LOGW(" HFS setattr '%s' failed: %s", colonPath, hfs_error); + dierr = kDIErrGeneric; + goto bail; + } + + /* update our local copy */ + pFile->fType = fileType; + pFile->fCreator = auxType; + pFile->fAccess = accessFlags; // should actually base them on HFS vals + +bail: + delete[] colonPath; + hfs_flush(fHfsVol); + return dierr; +} + +#endif // !EXCISE_GPL_CODE + + +/* + * =========================================================================== + * A2FileHFS + * =========================================================================== + */ + +/* + * Dump the contents of the A2File structure. + */ +void A2FileHFS::Dump(void) const +{ + LOGI("A2FileHFS '%s'", fFileName); +} + +/* convert hex to decimal */ +inline int FromHex(char hexVal) +{ + if (hexVal >= '0' && hexVal <= '9') + return hexVal - '0'; + else if (hexVal >= 'a' && hexVal <= 'f') + return hexVal -'a' + 10; + else if (hexVal >= 'A' && hexVal <= 'F') + return hexVal - 'A' + 10; + else + return -1; +} + +/* + * If this has a ProDOS filetype, convert it. + * + * This stuff is defined in Technical Note PT515, "Apple File Exchange Q&As". + * In theory we should convert type=BINA and type=TEXT regardless of the + * creator, but since those just go to generic text/binary types I don't + * think we need to handle it here (and I'm more comfortable leaving them + * with their Macintosh creators). + * + * In some respects, converting to ProDOS types is a bad idea, because we + * don't have a 1:1 mapping. If we copy a pdos/p\0\0\0 file we will store it + * as pdos/BINA instead. In practice, for the Apple II world they are + * equivalent, and CiderPress really doesn't need the "raw" file type. If + * it becomes annoying, we can add a DiskFSParameter to control it. + */ +uint32_t A2FileHFS::GetFileType(void) const +{ + if (fCreator != kPdosType) + return fType; + + if ((fType & 0xffff) == 0x2020) { + // 'XY ', where XY are hex digits for ProDOS file type + int digit1, digit2; + + digit1 = FromHex((char) (fType >> 24)); + digit2 = FromHex((char) (fType >> 16)); + if (digit1 < 0 || digit2 < 0) { + LOGI(" Unexpected: pdos + %08x", fType); + return 0x00; + } + return digit1 << 4 | digit2; + } + + uint8_t flag = (uint8_t)(fType >> 24); + if (flag == 0x70) { // 'p' + /* type and aux embedded within */ + return (fType >> 16) & 0xff; + } else { + /* type stored as a string */ + if (fType == 0x42494e41) // 'BINA' + return 0x00; // NON + else if (fType == 0x54455854) // 'TEXT' + return 0x04; + else if (fType == 0x50535953) // 'PSYS' + return 0xff; + else if (fType == 0x50533136) // 'PS16' + return 0xb3; + else + return 0x00; + } +} + +/* + * If this has a ProDOS aux type, convert it. + */ +uint32_t A2FileHFS::GetAuxType(void) const +{ + if (fCreator != kPdosType) + return fCreator; + + uint8_t flag = (uint8_t)(fType >> 24); + if (flag == 0x70) { // 'p' + /* type and aux embedded within */ + return fType & 0xffff; + } else { + return 0x0000; + } +} + +/* + * Set the full pathname to a combination of the base path and the + * current file's name. + * + * If we're in the volume directory, pass in "" for the base path (not NULL). + */ +void A2FileHFS::SetPathName(const char* basePath, const char* fileName) +{ + assert(basePath != NULL && fileName != NULL); + if (fPathName != NULL) + delete[] fPathName; + + // strip leading ':' (but treat ":" specially for volume dir entry) + if (basePath[0] == ':' && basePath[1] != '\0') + basePath++; + + int baseLen = strlen(basePath); + fPathName = new char[baseLen + 1 + strlen(fileName)+1]; + strcpy(fPathName, basePath); + if (baseLen != 0 && + !(baseLen == 1 && basePath[0] == ':')) + { + *(fPathName + baseLen) = kFssep; + baseLen++; + } + strcpy(fPathName + baseLen, fileName); +} + + +#ifndef EXCISE_GPL_CODE + +/* + * Return a copy of the pathname that libhfs will like. + * + * The caller must delete[] the return value. + */ +char* A2FileHFS::GetLibHFSPathName(void) const +{ + char* nameBuf; + + nameBuf = new char[strlen(fPathName)+2]; + nameBuf[0] = kFssep; + strcpy(nameBuf+1, fPathName); + + return nameBuf; +} + +/* + * Convert numeric file/aux type to HFS strings. "pType" and "pCreator" must + * be able to hold 5 bytes each (4-byte type + nul). + * + * Follows the PT515 recommendations, mostly. The "PSYS" and "PS16" + * conversions discard the file's aux type and therefore are unsuitable, + * and the conversion of SRC throws away its identity. + */ +/*static*/ void A2FileHFS::ConvertTypeToHFS(uint32_t fileType, uint32_t auxType, + char* pType, char* pCreator) +{ + if (fileType == 0x00 && auxType == 0x0000) { + strcpy(pCreator, "pdos"); + strcpy(pType, "BINA"); + } else if (fileType == 0x04 && auxType == 0x0000) { + strcpy(pCreator, "pdos"); + strcpy(pType, "TEXT"); + } else if (fileType >= 0 && fileType <= 0xff && + auxType >= 0 && auxType <= 0xffff) + { + pType[0] = 'p'; + pType[1] = (uint8_t) fileType; + pType[2] = (uint8_t) (auxType >> 8); + pType[3] = (uint8_t) auxType; + pType[4] = '\0'; + pCreator[0] = 'p'; + pCreator[1] = 'd'; + pCreator[2] = 'o'; + pCreator[3] = 's'; + pCreator[4] = '\0'; + } else { + pType[0] = (uint8_t)(fileType >> 24); + pType[1] = (uint8_t)(fileType >> 16); + pType[2] = (uint8_t)(fileType >> 8); + pType[3] = (uint8_t) fileType; + pType[4] = '\0'; + pCreator[0] = (uint8_t)(auxType >> 24); + pCreator[1] = (uint8_t)(auxType >> 16); + pCreator[2] = (uint8_t)(auxType >> 8); + pCreator[3] = (uint8_t) auxType; + pCreator[4] = '\0'; + } +} + + +/* + * Open a file through libhfs. + * + * libhfs wants filenames to begin with ':' unless they start with the + * name of the volume. This is the opposite of the convention followed + * by the rest of CiderPress (and most of the civilized world), so instead + * of storing the pathname that way we just tack it on here. + */ +DIError A2FileHFS::Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork /*=false*/) +{ + DIError dierr = kDIErrNone; + A2FDHFS* pOpenFile = NULL; + hfsfile* pHfsFile; + char* nameBuf = NULL; + + if (fpOpenFile != NULL) + return kDIErrAlreadyOpen; + //if (rsrcFork && fRsrcLength < 0) + // return kDIErrForkNotFound; + + nameBuf = GetLibHFSPathName(); + + DiskFSHFS* pDiskFS = (DiskFSHFS*) GetDiskFS(); + pHfsFile = hfs_open(pDiskFS->GetHfsVol(), nameBuf); + if (pHfsFile == NULL) { + LOGI(" HFS hfs_open(%s) failed: %s", nameBuf, hfs_error); + dierr = kDIErrGeneric; // better value might be in errno + goto bail; + } + hfs_setfork(pHfsFile, rsrcFork ? 1 : 0); + + pOpenFile = new A2FDHFS(this, pHfsFile); + + fpOpenFile = pOpenFile; + *ppOpenFile = pOpenFile; + +bail: + delete[] nameBuf; + return dierr; +} + + +/* + * =========================================================================== + * A2FDHFS + * =========================================================================== + */ + +/* + * Read a chunk of data from the fake file. + */ +DIError A2FDHFS::Read(void* buf, size_t len, size_t* pActual) +{ + long result; + + LOGD(" HFS reading %lu bytes from '%s' (offset=%ld)", + (unsigned long) len, fpFile->GetPathName(), + hfs_seek(fHfsFile, 0, HFS_SEEK_CUR)); + + //A2FileHFS* pFile = (A2FileHFS*) fpFile; + + result = hfs_read(fHfsFile, buf, len); + if (result < 0) + return kDIErrReadFailed; + + if (pActual != NULL) { + *pActual = (size_t) result; + } else if (result != (long) len) { + // short read, can't report it, return error + return kDIErrDataUnderrun; + } + + /* + * To do this right we need to break the hfs_read() into smaller + * pieces. However, it only really affects us for files that are + * getting reformatted, because that's the only time we grab the + * entire thing in one big piece. + */ + long offset = hfs_seek(fHfsFile, 0, HFS_SEEK_CUR); + if (!UpdateProgress(offset)) { + return kDIErrCancelled; + } + + return kDIErrNone; +} + +/* + * Write data at the current offset. + * + * (In the current implementation, the entire file is always written in + * one piece. This function does work correctly with multiple smaller + * pieces though, because it lets libhfs do all the work.) + */ +DIError A2FDHFS::Write(const void* buf, size_t len, size_t* pActual) +{ + long result; + + LOGD(" HFS writing %lu bytes to '%s' (offset=%ld)", + (unsigned long) len, fpFile->GetPathName(), + hfs_seek(fHfsFile, 0, HFS_SEEK_CUR)); + + fModified = true; // assume something gets changed + + //A2FileHFS* pFile = (A2FileHFS*) fpFile; + + result = hfs_write(fHfsFile, buf, len); + if (result < 0) + return kDIErrWriteFailed; + + if (pActual != NULL) { + *pActual = (size_t) result; + } else if (result != (long) len) { + // short write, can't report it, return error + return kDIErrDataUnderrun; + } + + /* to make this work right, we need to break hfs_write into pieces */ + long offset = hfs_seek(fHfsFile, 0, HFS_SEEK_CUR); + if (!UpdateProgress(offset)) { + return kDIErrCancelled; + } + + /* + * We don't hfs_flush here, because we don't expect the application to + * hold the file open, and we flush in Close(). + */ + + return kDIErrNone; +} + +/* + * Seek to a new offset. + */ +DIError A2FDHFS::Seek(di_off_t offset, DIWhence whence) +{ + int hfsWhence; + unsigned long result; + + switch (whence) { + case kSeekSet: hfsWhence = HFS_SEEK_SET; break; + case kSeekEnd: hfsWhence = HFS_SEEK_END; break; + case kSeekCur: hfsWhence = HFS_SEEK_CUR; break; + default: + assert(false); + return kDIErrInvalidArg; + } + + result = hfs_seek(fHfsFile, (long) offset, hfsWhence); + if (result == (unsigned long) -1) { + DebugBreak(); + return kDIErrGeneric; + } + return kDIErrNone; +} + +/* + * Return current offset. + */ +di_off_t A2FDHFS::Tell(void) +{ + di_off_t offset; + + /* get current position without moving pointer */ + offset = hfs_seek(fHfsFile, 0, HFS_SEEK_CUR); + return offset; +} + +/* + * Release file state, and tell our parent to destroy us. + */ +DIError A2FDHFS::Close(void) +{ + hfsdirent dirEnt; + + /* + * If the file was written to, update our info. + */ + if (fModified) { + if (hfs_fstat(fHfsFile, &dirEnt) == 0) { + A2FileHFS* pFile = (A2FileHFS*) fpFile; + pFile->fDataLength = dirEnt.u.file.dsize; + pFile->fRsrcLength = dirEnt.u.file.rsize; + if (pFile->fRsrcLength == 0) + pFile->fRsrcLength = -1; + LOGI(" HFS close set dataLen=%ld rsrcLen=%ld", + (long) pFile->fDataLength, (long) pFile->fRsrcLength); + } else { + LOGI(" HFS Close fstat failed: %s", hfs_error); + // close it anyway + } + } + + hfs_close(fHfsFile); + fHfsFile = NULL; + + /* flush changes */ + if (fModified) { + DiskFSHFS* pDiskFS = (DiskFSHFS*) fpFile->GetDiskFS(); + + if (hfs_flush(pDiskFS->GetHfsVol()) != 0) { + LOGI("HEY: Close flush failed!"); + DebugBreak(); + } + } + + fpFile->CloseDescr(this); + return kDIErrNone; +} + +/* + * Return the #of sectors/blocks in the file. Not supported, but since HFS + * doesn't support "sparse" files we can fake it. + */ +long A2FDHFS::GetSectorCount(void) const +{ + A2FileHFS* pFile = (A2FileHFS*) fpFile; + return (long) ((pFile->fDataLength+255) / 256 + + (pFile->fRsrcLength+255) / 256); +} + +long A2FDHFS::GetBlockCount(void) const +{ + A2FileHFS* pFile = (A2FileHFS*) fpFile; + return (long) ((pFile->fDataLength+511) / 512 + + (pFile->fRsrcLength+511) / 512); +} + +/* + * Return the Nth track/sector in this file. Not supported. + */ +DIError A2FDHFS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const +{ + return kDIErrNotSupported; +} + +/* + * Return the Nth 512-byte block in this file. Not supported. + */ +DIError A2FDHFS::GetStorage(long blockIdx, long* pBlock) const +{ + return kDIErrNotSupported; +} + + + + +#else // EXCISE_GPL_CODE ----------------------------------------------------- + +/* + * 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 DiskFSHFS::Initialize(InitMode initMode) +{ + DIError dierr = kDIErrNone; + + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) + goto bail; + DumpVolHeader(); + + CreateFakeFile(); + + SetVolumeUsageMap(); + +bail: + return dierr; +} + +/* + * Fill a buffer with some interesting stuff, and add it to the file list. + */ +void DiskFSHFS::CreateFakeFile(void) +{ + A2FileHFS* pFile; + char buf[768]; // currently running about 475 + static const char* kFormatMsg = +"The Macintosh HFS filesystem is not supported. CiderPress knows how to\r" +"recognize HFS volumes so that it can identify partitions on CFFA-formatted\r" +"CompactFlash cards and Apple II CD-ROMs, but the current version does not\r" +"know how to view or extract files.\r" +"\r" +"Some information about this HFS volume:\r" +"\r" +" Volume name : '%s'\r" +" Storage capacity : %ld blocks (%.2fMB)\r" +" Number of files : %ld\r" +" Number of folders : %ld\r" +" Last modified : %s\r" +"\r" +; + char dateBuf[32]; + long capacity; + const char* timeStr; + + capacity = (fAllocationBlockSize / kBlkSize) * fNumAllocationBlocks; + + /* get the mod time, format it, and remove the trailing '\n' */ + time_t when = + (time_t) (fModifiedDateTime - kDateTimeOffset - fLocalTimeOffset); + timeStr = ctime(&when); + if (timeStr == NULL) { + LOGI("Invalid date %ld (orig=%ld)", when, fModifiedDateTime); + strcpy(dateBuf, ""); + } else + strncpy(dateBuf, timeStr, sizeof(dateBuf)); + int len = strlen(dateBuf); + if (len > 0) + dateBuf[len-1] = '\0'; + + memset(buf, 0, sizeof(buf)); + snprintf(buf, NELEM(buf), kFormatMsg, + fVolumeName, + capacity, + (double) capacity / 2048.0, + fNumFiles, + fNumDirectories, + dateBuf); + + pFile = new A2FileHFS(this); + pFile->fIsDir = false; + pFile->fIsVolumeDir = false; + pFile->fType = 0; + pFile->fCreator = 0; + pFile->SetFakeFile(buf, strlen(buf)); + strcpy(pFile->fFileName, "(not supported)"); + pFile->SetPathName("", pFile->fFileName); + pFile->fDataLength = 0; + pFile->fRsrcLength = -1; + pFile->fCreateWhen = 0; + pFile->fModWhen = 0; + + pFile->SetFakeFile(buf, strlen(buf)); + + AddFileToList(pFile); +} + +/* + * We could do this, but there's not much point. + */ +DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const +{ + return kDIErrNotSupported; +} + +/* + * Not a whole lot to do. + */ +DIError A2FileHFS::Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork /*=false*/) +{ + A2FDHFS* pOpenFile = NULL; + + if (fpOpenFile != NULL) + return kDIErrAlreadyOpen; + if (rsrcFork && fRsrcLength < 0) + return kDIErrForkNotFound; + assert(readOnly == true); + + pOpenFile = new A2FDHFS(this, NULL); + + fpOpenFile = pOpenFile; + *ppOpenFile = pOpenFile; + + return kDIErrNone; +} + + +/* + * =========================================================================== + * A2FDHFS + * =========================================================================== + */ + +/* + * Read a chunk of data from the fake file. + */ +DIError A2FDHFS::Read(void* buf, size_t len, size_t* pActual) +{ + LOGD(" HFS reading %d bytes from '%s' (offset=%ld)", + len, fpFile->GetPathName(), (long) fOffset); + + A2FileHFS* pFile = (A2FileHFS*) fpFile; + + /* don't allow them to read past the end of the file */ + if (fOffset + (long)len > pFile->fDataLength) { + if (pActual == NULL) + return kDIErrDataUnderrun; + len = (size_t) (pFile->fDataLength - fOffset); + } + if (pActual != NULL) + *pActual = len; + + memcpy(buf, pFile->GetFakeFileBuf(), len); + + fOffset += len; + + return kDIErrNone; +} + +/* + * Write data at the current offset. + */ +DIError A2FDHFS::Write(const void* buf, size_t len, size_t* pActual) +{ + return kDIErrNotSupported; +} + +/* + * Seek to a new offset. + */ +DIError A2FDHFS::Seek(di_off_t offset, DIWhence whence) +{ + di_off_t fileLen = ((A2FileHFS*) fpFile)->fDataLength; + + 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 A2FDHFS::Tell(void) +{ + return fOffset; +} + +/* + * Release file state, and tell our parent to destroy us. + */ +DIError A2FDHFS::Close(void) +{ + fpFile->CloseDescr(this); + return kDIErrNone; +} + +/* + * Return the #of sectors/blocks in the file. + */ +long A2FDHFS::GetSectorCount(void) const +{ + A2FileHFS* pFile = (A2FileHFS*) fpFile; + return (long) ((pFile->fDataLength+255) / 256); +} + +long A2FDHFS::GetBlockCount(void) const +{ + A2FileHFS* pFile = (A2FileHFS*) fpFile; + return (long) ((pFile->fDataLength+511) / 512); +} + +/* + * Return the Nth track/sector in this file. + */ +DIError A2FDHFS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const +{ + return kDIErrNotSupported; +} + +/* + * Return the Nth 512-byte block in this file. + */ +DIError A2FDHFS::GetStorage(long blockIdx, long* pBlock) const +{ + return kDIErrNotSupported; +} + +#endif // EXCISE_GPL_CODE --------------------------------------------------- diff --git a/diskimg/ImageWrapper.cpp b/diskimg/ImageWrapper.cpp new file mode 100644 index 0000000..efcdade --- /dev/null +++ b/diskimg/ImageWrapper.cpp @@ -0,0 +1,2476 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Code for handling disk image "wrappers", things like DiskCopy, 2MG, and + * SHK that surround a disk image. + * + * Returning with kDIErrBadChecksum from Test or Prep is taken as a sign + * that, while we have correctly identified the wrapper format, the contents + * of the file are corrupt, and the user needs to be told. + * + * Some formats, such as 2MG, include a DOS volume number. This is useful + * because DOS actually embeds the volume number in sector headers; the value + * stored in the VTOC is ignored by certain things (notably some games with + * trivial copy-protection). This value needs to be preserved. It's + * unclear how useful this will actually be; mostly we just want to preserve + * it when translating from one format to another. + * + * If a library (such as NufxLib) needs to read an actual file, it can + * (usually) pry the name out of the GFD. + * + * In general, it should be possible to write to any "wrapped" file that we + * can read from. For things like NuFX and DDD, this means we need to be + * able to re-compress the image file when we're done with it. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" +#include "TwoImg.h" + + +/* + * =========================================================================== + * 2MG (a/k/a 2IMG) + * =========================================================================== + */ + +/* + * Test to see if this is a 2MG file. + * + * The easiest way to do that is to open up the header and see if + * it looks valid. + */ +/*static*/ DIError Wrapper2MG::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + TwoImgHeader header; + + LOGI("Testing for 2MG"); + + // HEY: should test for wrappedLength > 2GB; if so, skip + + pGFD->Rewind(); + if (header.ReadHeader(pGFD, (long) wrappedLength) != 0) + return kDIErrGeneric; + + LOGI("Looks like valid 2MG"); + return kDIErrNone; +} + +/* + * Get the header (again) and use it to locate the data. + */ +DIError Wrapper2MG::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + TwoImgHeader header; + long offset; + + LOGI("Prepping for 2MG"); + pGFD->Rewind(); + if (header.ReadHeader(pGFD, (long) wrappedLength) != 0) + return kDIErrGeneric; + + offset = header.fDataOffset; + + if (header.fFlags & TwoImgHeader::kDOSVolumeSet) + *pDiskVolNum = header.GetDOSVolumeNum(); + + *pLength = header.fDataLen; + *pPhysical = DiskImg::kPhysicalFormatSectors; + if (header.fImageFormat == TwoImgHeader::kImageFormatDOS) + *pOrder = DiskImg::kSectorOrderDOS; + else if (header.fImageFormat == TwoImgHeader::kImageFormatProDOS) + *pOrder = DiskImg::kSectorOrderProDOS; + else if (header.fImageFormat == TwoImgHeader::kImageFormatNibble) { + *pOrder = DiskImg::kSectorOrderPhysical; + if (*pLength == kTrackCount525 * kTrackLenNib525) { + LOGI(" Prepping for 6656-byte 2MG-NIB"); + *pPhysical = DiskImg::kPhysicalFormatNib525_6656; + } else if (*pLength == kTrackCount525 * kTrackLenNb2525) { + LOGI(" Prepping for 6384-byte 2MG-NB2"); + *pPhysical = DiskImg::kPhysicalFormatNib525_6384; + } else { + LOGI(" NIB 2MG with length=%ld rejected", (long) *pLength); + return kDIErrOddLength; + } + } + + *ppNewGFD = new GFDGFD; + return ((GFDGFD*)*ppNewGFD)->Open(pGFD, offset, readOnly); +} + +/* + * Initialize fields for a new file. + */ +DIError Wrapper2MG::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + TwoImgHeader header; + int cc; + + switch (physical) { + case DiskImg::kPhysicalFormatNib525_6656: + if (length != kTrackLenNib525 * kTrackCount525) { + LOGI("Invalid 2MG nibble length %ld", (long) length); + return kDIErrInvalidArg; + } + header.InitHeader(TwoImgHeader::kImageFormatNibble, (long) length, + 8 * kTrackCount525); // 8 blocks per track + break; + case DiskImg::kPhysicalFormatSectors: + if ((length % 512) != 0) { + LOGI("Invalid 2MG length %ld", (long) length); + return kDIErrInvalidArg; + } + if (order == DiskImg::kSectorOrderProDOS) + cc = header.InitHeader(TwoImgHeader::kImageFormatProDOS, + (long) length, (long) length / 512); + else if (order == DiskImg::kSectorOrderDOS) + cc = header.InitHeader(TwoImgHeader::kImageFormatDOS, + (long) length, (long) length / 512); + else { + LOGI("Invalid 2MG sector order %d", order); + return kDIErrInvalidArg; + } + if (cc != 0) { + LOGI("TwoImg InitHeader failed (len=%ld)", (long) length); + return kDIErrInvalidArg; + } + break; + default: + LOGI("Invalid 2MG physical %d", physical); + return kDIErrInvalidArg; + } + + if (dosVolumeNum != DiskImg::kVolumeNumNotSet) + header.SetDOSVolumeNum(dosVolumeNum); + + cc = header.WriteHeader(pWrapperGFD); + if (cc != 0) { + LOGI("ERROR: 2MG header write failed (cc=%d)", cc); + return kDIErrGeneric; + } + + long footerLen = header.fCmtLen + header.fCreatorLen; + if (footerLen > 0) { + // This is currently impossible, which is good because the Seek call + // will fail if pWrapperGFD is a buffer. + assert(false); + pWrapperGFD->Seek(header.fDataOffset + length, kSeekSet); + header.WriteFooter(pWrapperGFD); + } + + long offset = header.fDataOffset; + + + *pWrappedLength = length + offset + footerLen; + *pDataFD = new GFDGFD; + return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, offset, false); +} + +/* + * We only use GFDGFD, so there's nothing to do here. + * + * If we want to support changing the comment field in an open image, we'd + * need to handle making the file longer or shorter here. Right now we + * just ignore everything that comes before or after the start of the data. + * Since there's no checksum, none of the header fields change, so we + * don't even deal with that. + */ +DIError Wrapper2MG::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + return kDIErrNone; +} + + +/* + * =========================================================================== + * SHK (ShrinkIt NuFX), also .SDK and .BXY + * =========================================================================== + */ + +/* + * NOTE: this doesn't override the global error message callback because + * we expect it to be set by the application. + */ + +/* + * Display error messages... or not. + */ +/*static*/ NuResult WrapperNuFX::ErrMsgHandler(NuArchive* /*pArchive*/, + void* vErrorMessage) +{ + const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage; + + if (pErrorMessage->isDebug) { + Global::PrintDebugMsg(pErrorMessage->file, pErrorMessage->line, + "[D] %s\n", pErrorMessage->message); + } else { + Global::PrintDebugMsg(pErrorMessage->file, pErrorMessage->line, + "%s\n", pErrorMessage->message); + } + + return kNuOK; +} + +/* + * Open a NuFX archive, and verify that it holds exactly one disk archive. + * + * On success, the NuArchive pointer and thread idx are set, and 0 is + * returned. Returns -1 on failure. + */ +/*static*/ DIError WrapperNuFX::OpenNuFX(const char* pathName, NuArchive** ppArchive, + NuThreadIdx* pThreadIdx, long* pLength, bool readOnly) +{ + NuError nerr = kNuErrNone; + NuArchive* pArchive = NULL; + NuRecordIdx recordIdx; + NuAttr attr; + const NuRecord* pRecord; + const NuThread* pThread = NULL; + int idx; + + LOGI("Opening file '%s' to test for NuFX", pathName); + + /* + * Open the archive. + */ + if (readOnly) { + nerr = NuOpenRO(pathName, &pArchive); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to open archive (err=%d)", nerr); + goto bail; + } + } else { + char* tmpPath; + + tmpPath = GenTempPath(pathName); + if (tmpPath == NULL) { + nerr = kNuErrInternal; + goto bail; + } + + nerr = NuOpenRW(pathName, tmpPath, 0, &pArchive); + if (nerr != kNuErrNone) { + LOGI(" NuFX OpenRW failed (nerr=%d)", nerr); + nerr = kNuErrGeneric; + delete[] tmpPath; + goto bail; + } + delete[] tmpPath; + } + + NuSetErrorMessageHandler(pArchive, ErrMsgHandler); + + nerr = NuGetAttr(pArchive, kNuAttrNumRecords, &attr); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to get record count (err=%d)", nerr); + goto bail; + } + if (attr != 1) { + LOGI(" NuFX archive has %d entries, not disk-only", attr); + nerr = kNuErrGeneric; + if (attr > 1) + goto file_archive; + else + goto bail; // shouldn't get zero-count archives, but... + } + + /* get the first record */ + nerr = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to get first recordIdx (err=%d)", nerr); + goto bail; + } + nerr = NuGetRecord(pArchive, recordIdx, &pRecord); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to get first record (err=%d)", nerr); + goto bail; + } + + /* find a disk image thread */ + for (idx = 0; idx < (int)NuRecordGetNumThreads(pRecord); idx++) { + pThread = NuGetThread(pRecord, idx); + + if (NuGetThreadID(pThread) == kNuThreadIDDiskImage) + break; + } + if (idx == (int)NuRecordGetNumThreads(pRecord)) { + LOGI(" NuFX no disk image found in first record"); + nerr = kNuErrGeneric; + goto file_archive; + } + assert(pThread != NULL); + *pThreadIdx = pThread->threadIdx; + + /* + * Don't allow zero-length disks. + */ + *pLength = pThread->actualThreadEOF; + if (!*pLength) { + LOGI(" NuFX length of disk image is bad (%ld)", *pLength); + nerr = kNuErrGeneric; + goto bail; + } + + /* + * Success! + */ + assert(nerr == kNuErrNone); + *ppArchive = pArchive; + pArchive = NULL; + +bail: + if (pArchive != NULL) + NuClose(pArchive); + if (nerr == kNuErrNone) + return kDIErrNone; + else if (nerr == kNuErrBadMHCRC || nerr == kNuErrBadRHCRC) + return kDIErrBadChecksum; + else + return kDIErrGeneric; + +file_archive: + if (pArchive != NULL) + NuClose(pArchive); + return kDIErrFileArchive; +} + +/* + * Load a disk image into memory. + * + * Allocates a buffer with the specified length and loads the desired + * thread into it. + * + * In an LZW-I compressed thread, the third byte of the compressed thread + * data is the disk volume number that P8 ShrinkIt would use when formatting + * the disk. In an LZW-II compressed thread, it's the first byte of the + * compressed data. Uncompressed disk images simply don't have the disk + * volume number in them. Until NufxLib provides a simple way to access + * this bit of loveliness, we're going to pretend it's not there. + * + * Returns 0 on success, -1 on error. + */ +DIError WrapperNuFX::GetNuFXDiskImage(NuArchive* pArchive, NuThreadIdx threadIdx, + long length, char** ppData) +{ + NuError err; + NuDataSink* pDataSink = NULL; + uint8_t* buf = NULL; + + assert(length > 0); + buf = new uint8_t[length]; + if (buf == NULL) + return kDIErrMalloc; + + /* + * Create a buffer and expand the disk image into it. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, length, + &pDataSink); + if (err != kNuErrNone) { + LOGI(" NuFX: unable to create data sink (err=%d)", err); + goto bail; + } + + err = NuExtractThread(pArchive, threadIdx, pDataSink); + if (err != kNuErrNone) { + LOGI(" NuFX: unable to extract thread (err=%d)", err); + goto bail; + } + + //err = kNuErrBadThreadCRC; goto bail; // debug test only + + *ppData = (char*)buf; + +bail: + NuFreeDataSink(pDataSink); + if (err != kNuErrNone) { + LOGI(" NuFX GetNuFXDiskImage returning after nuerr=%d", err); + delete[] buf; + } + if (err == kNuErrNone) + return kDIErrNone; + else if (err == kNuErrBadDataCRC || err == kNuErrBadThreadCRC) + return kDIErrBadChecksum; + else if (err == kNuErrBadData) + return kDIErrBadCompressedData; + else if (err == kNuErrBadFormat) + return kDIErrUnsupportedCompression; + else + return kDIErrGeneric; +} + +/* + * Test to see if this is a single-record NuFX archive with a disk archive + * in it. + */ +/*static*/ DIError WrapperNuFX::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + DIError dierr; + NuArchive* pArchive = NULL; + NuThreadIdx threadIdx; + long length; + const char* imagePath; + + imagePath = pGFD->GetPathName(); + if (imagePath == NULL) { + LOGI("Can't test NuFX on non-file"); + return kDIErrNotSupported; + } + LOGI("Testing for NuFX"); + dierr = OpenNuFX(imagePath, &pArchive, &threadIdx, &length, true); + if (dierr != kDIErrNone) + return dierr; + + /* success; throw away state in case they don't like us anyway */ + assert(pArchive != NULL); + NuClose(pArchive); + + return kDIErrNone; +} + +/* + * Open the archive, extract the disk image into a memory buffer. + */ +DIError WrapperNuFX::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + DIError dierr = kDIErrNone; + NuThreadIdx threadIdx; + GFDBuffer* pNewGFD = NULL; + char* buf = NULL; + long length = -1; + const char* imagePath; + + imagePath = pGFD->GetPathName(); + if (imagePath == NULL) { + assert(false); // should've been caught in Test + return kDIErrNotSupported; + } + pGFD->Close(); // don't hold the file open + dierr = OpenNuFX(imagePath, &fpArchive, &threadIdx, &length, readOnly); + if (dierr != kDIErrNone) + goto bail; + + dierr = GetNuFXDiskImage(fpArchive, threadIdx, length, &buf); + if (dierr != kDIErrNone) + goto bail; + + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, readOnly); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + /* + * Success! + */ + assert(dierr == kDIErrNone); + *ppNewGFD = pNewGFD; + *pLength = length; + *pPhysical = DiskImg::kPhysicalFormatSectors; + *pOrder = DiskImg::kSectorOrderProDOS; + + LOGI(" NuFX is ready, threadIdx=%d", threadIdx); + fThreadIdx = threadIdx; + +bail: + if (dierr != kDIErrNone) { + NuClose(fpArchive); + fpArchive = NULL; + delete pNewGFD; + delete buf; + } + return dierr; +} + +/* + * Given a filename, create a suitable temp pathname. + * + * This is really the wrong place to be doing this -- the application + * should get to deal with this -- but it's not the end of the world + * if we handle it here. Add to wish list: fix NufxLib so that the + * temp file can be a memory buffer. + * + * Returns a string allocated with new[]. + */ +/*static*/ char* WrapperNuFX::GenTempPath(const char* path) +{ + static const char* kTmpTemplate = "DItmp_XXXXXX"; + char* tmpPath; + + assert(path != NULL); + assert(strlen(path) > 0); + + tmpPath = new char[strlen(path) + 32]; + if (tmpPath == NULL) + return NULL; + + strcpy(tmpPath, path); + + /* back up to the first thing that looks like it's an fssep */ + char* cp; + cp = tmpPath + strlen(tmpPath); + while (--cp >= tmpPath) { + if (*cp == '/' || *cp == '\\' || *cp == ':') + break; + } + + /* we either fell off the back end or found an fssep; advance */ + cp++; + + strcpy(cp, kTmpTemplate); + + LOGI(" NuFX GenTempPath '%s' -> '%s'", path, tmpPath); + + return tmpPath; +} + +/* + * Initialize fields for a new file. + * + * "pWrapperGFD" will be fairly useless after this, because we're + * recreating the underlying file. (If it doesn't have an underlying + * file, then we're hosed.) + */ +DIError WrapperNuFX::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + assert(physical == DiskImg::kPhysicalFormatSectors); + assert(order == DiskImg::kSectorOrderProDOS); + + DIError dierr = kDIErrNone; + NuArchive* pArchive; + const char* imagePath; + char* tmpPath = NULL; + uint8_t* buf = NULL; + NuError nerr; + + /* + * Create the NuFX archive, stomping on the existing file. (This + * makes pWrapperGFD invalid, but such is life with NufxLib.) + */ + imagePath = pWrapperGFD->GetPathName(); + if (imagePath == NULL) { + assert(false); // must not have an outer wrapper + dierr = kDIErrNotSupported; + goto bail; + } + pWrapperGFD->Close(); // don't hold the file open + tmpPath = GenTempPath(imagePath); + if (tmpPath == NULL) { + dierr = kDIErrInternal; + goto bail; + } + + nerr = NuOpenRW(imagePath, tmpPath, kNuOpenCreat, &pArchive); + if (nerr != kNuErrNone) { + LOGI(" NuFX OpenRW failed (nerr=%d)", nerr); + dierr = kDIErrGeneric; + goto bail; + } + + /* + * Create a blank chunk of memory for the image. + */ + assert(length > 0); + buf = new uint8_t[(int) length]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + GFDBuffer* pNewGFD; + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, false); + if (dierr != kDIErrNone) { + delete pNewGFD; + goto bail; + } + *pDataFD = pNewGFD; + buf = NULL; // now owned by pNewGFD; + + /* + * Success! Set misc stuff. + */ + fThreadIdx = 0; // don't have one to overwrite + fpArchive = pArchive; + +bail: + delete[] tmpPath; + delete[] buf; + return dierr; +} + +/* + * Close the NuFX archive. + */ +DIError WrapperNuFX::CloseNuFX(void) +{ + NuError nerr; + + /* throw away any un-flushed changes so that "close" can't fail */ + (void) NuAbort(fpArchive); + + nerr = NuClose(fpArchive); + if (nerr != kNuErrNone) { + LOGI("WARNING: NuClose failed"); + return kDIErrGeneric; + } + return kDIErrNone; +} + +/* + * Write the data using the default compression method. + * + * Doesn't touch "pWrapperGFD" or "pWrappedLen". Could probably update + * "pWrappedLen", but that's really only useful if we have a gzip Outer + * that wants to know how much data we have. Because we don't write to + * pWrapperGFD, we can't have a gzip wrapper, so there's no point in + * updating it. + */ +DIError WrapperNuFX::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + NuError nerr = kNuErrNone; + NuFileDetails fileDetails; + NuRecordIdx recordIdx; + NuThreadIdx threadIdx; + NuDataSource* pDataSource = NULL; + + if (fThreadIdx != 0) { + /* + * Mark the old record for deletion. + */ + nerr = NuGetRecordIdxByPosition(fpArchive, 0, &recordIdx); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to get first recordIdx (err=%d)", nerr); + goto bail; + } + nerr = NuDeleteRecord(fpArchive, recordIdx); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to delete first record (err=%d)", nerr); + goto bail; + } + } + + assert((dataLen % 512) == 0); + + nerr = NuSetValue(fpArchive, kNuValueDataCompression, + fCompressType + kNuCompressNone); + if (nerr != kNuErrNone) { + LOGI("WARNING: unable to set compression to format %d", + fCompressType); + nerr = kNuErrNone; + } else { + LOGI(" NuFX set compression to %d/%d", fCompressType, + fCompressType + kNuCompressNone); + } + + /* + * Fill out the fileDetails record appropriately. + */ + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.threadID = kNuThreadIDDiskImage; + if (fStorageName != NULL) + fileDetails.storageNameMOR = fStorageName; // TODO + else + fileDetails.storageNameMOR = "NEW.DISK"; + fileDetails.fileSysID = kNuFileSysUnknown; + fileDetails.fileSysInfo = kDefaultStorageFssep; + fileDetails.storageType = 512; + fileDetails.extraType = (long) (dataLen / 512); + fileDetails.access = kNuAccessUnlocked; + + time_t now; + now = time(NULL); + UNIXTimeToDateTime(&now, &fileDetails.archiveWhen); + UNIXTimeToDateTime(&now, &fileDetails.modWhen); + UNIXTimeToDateTime(&now, &fileDetails.createWhen); + + /* + * Create the new record. + */ + nerr = NuAddRecord(fpArchive, &fileDetails, &recordIdx); + if (nerr != kNuErrNone) { + LOGI(" NuFX AddRecord failed (nerr=%d)", nerr); + goto bail; + } + + /* + * Create a data source for the thread. + * + * We need to get the memory buffer from pDataGFD, which we do in + * a somewhat unwholesome manner. However, there's no other way to + * feed the data into NufxLib. + */ + nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0, + (const uint8_t*) ((GFDBuffer*) pDataGFD)->GetBuffer(), + 0, (long) dataLen, NULL, &pDataSource); + if (nerr != kNuErrNone) { + LOGI(" NuFX unable to create NufxLib data source (nerr=%d)", nerr); + goto bail; + } + + /* + * Add the thread. + */ + nerr = NuAddThread(fpArchive, recordIdx, kNuThreadIDDiskImage, + pDataSource, &threadIdx); + if (nerr != kNuErrNone) { + LOGI(" NuFX AddThread failed (nerr=%d)", nerr); + goto bail; + } + pDataSource = NULL; // now owned by NufxLib + LOGI(" NuFX added thread %d in record %d, flushing changes", + threadIdx, recordIdx); + + /* + * Flush changes (does the actual compression). + */ + uint32_t status; + nerr = NuFlush(fpArchive, &status); + if (nerr != kNuErrNone) { + LOGI(" NuFX flush failed (nerr=%d, status=%u)", nerr, status); + goto bail; + } + + /* update the threadID */ + fThreadIdx = threadIdx; + +bail: + NuFreeDataSource(pDataSource); + if (nerr != kNuErrNone) + return kDIErrGeneric; + return kDIErrNone; +} + +/* + * Common NuFX utility function. This ought to be in NufxLib. + */ +void WrapperNuFX::UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime) +{ + struct tm* ptm; + + assert(pWhen != NULL); + assert(pDateTime != NULL); + + ptm = localtime(pWhen); + pDateTime->second = ptm->tm_sec; + pDateTime->minute = ptm->tm_min; + pDateTime->hour = ptm->tm_hour; + pDateTime->day = ptm->tm_mday -1; + pDateTime->month = ptm->tm_mon; + pDateTime->year = ptm->tm_year; + pDateTime->extra = 0; + pDateTime->weekDay = ptm->tm_wday +1; +} + + +/* + * =========================================================================== + * DDD (DDD 2.1, DDD Pro) + * =========================================================================== + */ + +/* + * There really isn't a way to test if the file is a DDD archive, except + * to try to unpack it. One thing we can do fairly quickly is look for + * runs of repeated bytes, which will be impossible in a DDD file because + * we compress runs of repeated bytes with RLE. + */ +/*static*/ DIError WrapperDDD::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + DIError dierr; + GenericFD* pNewGFD = NULL; + LOGI("Testing for DDD"); + + pGFD->Rewind(); + + dierr = CheckForRuns(pGFD); + if (dierr != kDIErrNone) + return dierr; + + dierr = Unpack(pGFD, &pNewGFD, NULL); + delete pNewGFD; + return dierr; +} + +/* + * Load a bunch of data and check it for repeated byte sequences that + * would be removed by RLE. Runs of 4 bytes or longer should have been + * stripped out. DDD adds a couple of zeroes onto the end, so to avoid + * special cases we assume that a run of 5 is okay, and only flunk the + * data when it gets to 6. + * + * One big exception: the "favorites" table isn't run-length encoded, + * and if the track is nothing but zeroes the entire thing will be + * filled with 0xff. So we allow runs of 0xff bytes. + * + * PROBLEM: some sequences, such as repeated d5aa, can turn into what looks + * like a run of bytes in the output. We can't assume that arbitrary + * sequences of bytes won't be repeated. It does appear that we can assume + * that 00 bytes won't be repeated, so we can still scan for a series of + * zeroes and reject the image if found (which should clear us for all + * uncompressed formats and any compressed format with a padded file header). + * + * The goal is to detect uncompressed data sources. The test for DDD + * should come after other compressed data formats. + * + * For speed we crank the data in 8K at a time and don't correctly handle + * the boundaries. We do, however, need to avoid scanning the last 256 + * bytes of the file, because DOS DDD just fills it with junk, and it's + * possible that junk might have runs in it. + */ +/*static*/ DIError WrapperDDD::CheckForRuns(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + int kRunThreshold = 5; + uint8_t buf[8192]; + size_t bufCount; + int runLen; + di_off_t fileLen; + int i; + + dierr = pGFD->Seek(0, kSeekEnd); + if (dierr != kDIErrNone) + goto bail; + fileLen = pGFD->Tell(); + pGFD->Rewind(); + + fileLen -= 256; // could be extra data from DOS DDD + + while (fileLen) { + bufCount = (size_t) fileLen; + if (bufCount > sizeof(buf)) + bufCount = sizeof(buf); + fileLen -= bufCount; + + dierr = pGFD->Read(buf, bufCount); + if (dierr != kDIErrNone) + goto bail; + //LOGI(" DDD READ %d bytes", bufCount); + if (dierr != kDIErrNone) { + LOGI(" DDD CheckForRuns read failed (err=%d)", dierr); + return dierr; + } + + runLen = 0; + for (i = 1; i < (int) bufCount; i++) { + if (buf[i] == 0 && buf[i] == buf[i-1]) { + runLen++; + if (runLen == kRunThreshold && buf[i] != 0xff) { + LOGI(" DDD found run of >= %d of 0x%02x, bailing", + runLen+1, buf[i]); + return kDIErrGeneric; + } + } else { + runLen = 0; + } + } + } + + LOGI(" DDD CheckForRuns scan complete, no long runs found"); + +bail: + return dierr; +} + +/* + * Prepping is much the same as testing, but we fill in a few more details. + */ +DIError WrapperDDD::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + DIError dierr; + LOGI("Prepping for DDD"); + + assert(*ppNewGFD == NULL); + + dierr = Unpack(pGFD, ppNewGFD, pDiskVolNum); + if (dierr != kDIErrNone) + return dierr; + + *pLength = kNumTracks * kTrackLen; + *pPhysical = DiskImg::kPhysicalFormatSectors; + *pOrder = DiskImg::kSectorOrderDOS; + + return dierr; +} + +/* + * Unpack a compressed disk image from "pGFD" to a new memory buffer + * created in "*ppNewGFD". + */ +/*static*/ DIError WrapperDDD::Unpack(GenericFD* pGFD, GenericFD** ppNewGFD, + short* pDiskVolNum) +{ + DIError dierr; + GFDBuffer* pNewGFD = NULL; + uint8_t* buf = NULL; + short diskVolNum; + + pGFD->Rewind(); + + buf = new uint8_t[kNumTracks * kTrackLen]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + pNewGFD = new GFDBuffer; + if (pNewGFD == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = pNewGFD->Open(buf, kNumTracks * kTrackLen, true, false, false); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + dierr = UnpackDisk(pGFD, pNewGFD, &diskVolNum); + if (dierr != kDIErrNone) + goto bail; + + if (pDiskVolNum != NULL) + *pDiskVolNum = diskVolNum; + *ppNewGFD = pNewGFD; + pNewGFD = NULL; // now owned by caller + +bail: + delete[] buf; + delete pNewGFD; + return dierr; +} + +/* + * Initialize stuff for a new file. There's no file header or other + * goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone. + */ +DIError WrapperDDD::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + assert(length == kNumTracks * kTrackLen); + assert(physical == DiskImg::kPhysicalFormatSectors); + assert(order == DiskImg::kSectorOrderDOS); + + DIError dierr; + uint8_t* buf = NULL; + + /* + * Create a blank chunk of memory for the image. + */ + buf = new uint8_t[(int) length]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + GFDBuffer* pNewGFD; + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, false); + if (dierr != kDIErrNone) { + delete pNewGFD; + goto bail; + } + *pDataFD = pNewGFD; + buf = NULL; // now owned by pNewGFD; + + // can't set *pWrappedLength yet + + if (dosVolumeNum != DiskImg::kVolumeNumNotSet) + fDiskVolumeNum = dosVolumeNum; + else + fDiskVolumeNum = kDefaultNibbleVolumeNum; + +bail: + delete[] buf; + return dierr; +} + +/* + * Compress the disk image. + */ +DIError WrapperDDD::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + DIError dierr; + + assert(dataLen == kNumTracks * kTrackLen); + + pDataGFD->Rewind(); + + dierr = PackDisk(pDataGFD, pWrapperGFD, fDiskVolumeNum); + if (dierr != kDIErrNone) + return dierr; + + *pWrappedLen = pWrapperGFD->Tell(); + LOGI(" DDD compressed from %d to %ld", + kNumTracks * kTrackLen, (long) *pWrappedLen); + + return kDIErrNone; +} + + +/* + * =========================================================================== + * DiskCopy (primarily a Mac format) + * =========================================================================== + */ + +/* + * DiskCopy 4.2 header, from FTN $e0/0005. + * + * All values are BIG-endian. + */ +const int kDC42NameLen = 64; +const int kDC42ChecksumOffset = 72; // where the checksum lives +const int kDC42DataOffset = 84; // header is always this long +const int kDC42PrivateMagic = 0x100; +const int kDC42FakeTagLen = 19200; // add a "fake" tag to match Mac + +typedef struct DiskImgLib::DC42Header { + char diskName[kDC42NameLen+1]; // from pascal string + uint32_t dataSize; // in bytes + uint32_t tagSize; + uint32_t dataChecksum; + uint32_t tagChecksum; + uint8_t diskFormat; // should be 1 for 800K + uint8_t formatByte; // should be $24, sometimes $22 + uint16_t privateWord; // must be 0x0100 + // userData begins at +84 + // tagData follows user data +} DC42Header; + +/* + * Dump the contents of a DC42Header. + */ +/*static*/ void WrapperDiskCopy42::DumpHeader(const DC42Header* pHeader) +{ + LOGI("--- header contents:"); + LOGI("\tdiskName = '%s'", pHeader->diskName); + LOGI("\tdataSize = %d (%dK)", pHeader->dataSize, + pHeader->dataSize / 1024); + LOGI("\ttagSize = %d", pHeader->tagSize); + LOGI("\tdataChecksum = 0x%08x", pHeader->dataChecksum); + LOGI("\ttagChecksum = 0x%08x", pHeader->tagChecksum); + LOGI("\tdiskFormat = %d", pHeader->diskFormat); + LOGI("\tformatByte = 0x%02x", pHeader->formatByte); + LOGI("\tprivateWord = 0x%04x", pHeader->privateWord); +} + +/* + * Init a DC42 header for an 800K ProDOS disk. + */ +void WrapperDiskCopy42::InitHeader(DC42Header* pHeader) +{ + memset(pHeader, 0, sizeof(*pHeader)); + if (fStorageName == NULL || strlen(fStorageName) == 0) + strcpy(pHeader->diskName, "-not a Macintosh disk"); + else + strcpy(pHeader->diskName, fStorageName); + pHeader->dataSize = 819200; + pHeader->tagSize = kDC42FakeTagLen; // emulate Mac behavior + pHeader->dataChecksum = 0xffffffff; // fixed during Flush + pHeader->tagChecksum = 0x00000000; // 19200 zeroes + pHeader->diskFormat = 1; + pHeader->formatByte = 0x24; + pHeader->privateWord = kDC42PrivateMagic; +} + +/* + * Read the header from a DC42 file and verify it. + * + * Returns 0 on success, -1 on error or invalid header. + */ +/*static*/ int WrapperDiskCopy42::ReadHeader(GenericFD* pGFD, DC42Header* pHeader) +{ + uint8_t hdrBuf[kDC42DataOffset]; + + if (pGFD->Read(hdrBuf, kDC42DataOffset) != kDIErrNone) + return -1; + + // test the Pascal length byte + if (hdrBuf[0] >= kDC42NameLen) + return -1; + + memcpy(pHeader->diskName, &hdrBuf[1], hdrBuf[0]); + pHeader->diskName[hdrBuf[0]] = '\0'; + + pHeader->dataSize = GetLongBE(&hdrBuf[64]); + pHeader->tagSize = GetLongBE(&hdrBuf[68]); + pHeader->dataChecksum = GetLongBE(&hdrBuf[72]); + pHeader->tagChecksum = GetLongBE(&hdrBuf[76]); + pHeader->diskFormat = hdrBuf[80]; + pHeader->formatByte = hdrBuf[81]; + pHeader->privateWord = GetShortBE(&hdrBuf[82]); + + if (pHeader->dataSize != 800 * 1024 || + pHeader->diskFormat != 1 || + (pHeader->formatByte != 0x22 && pHeader->formatByte != 0x24) || + pHeader->privateWord != kDC42PrivateMagic) + { + return -1; + } + + return 0; +} + +/* + * Write the header for a DC42 file. + */ +DIError WrapperDiskCopy42::WriteHeader(GenericFD* pGFD, const DC42Header* pHeader) +{ + uint8_t hdrBuf[kDC42DataOffset]; + + pGFD->Rewind(); + + memset(hdrBuf, 0, sizeof(hdrBuf)); + /* + * Disks created on a Mac include the null byte in the count; not sure + * if this applies to volume labels or just the "not a Macintosh disk" + * magic string. To be safe, we only increment it if it starts with '-'. + * (Need access to a Macintosh to test this.) + */ + hdrBuf[0] = (uint8_t) (strlen(pHeader->diskName) & 0xff); + if (pHeader->diskName[0] == '-' && hdrBuf[0] < (kDC42NameLen-1)) + hdrBuf[0]++; + memcpy(&hdrBuf[1], pHeader->diskName, hdrBuf[0]); + + PutLongBE(&hdrBuf[64], pHeader->dataSize); + PutLongBE(&hdrBuf[68], pHeader->tagSize); + PutLongBE(&hdrBuf[72], pHeader->dataChecksum); + PutLongBE(&hdrBuf[76], pHeader->tagChecksum); + hdrBuf[80] = pHeader->diskFormat; + hdrBuf[81] = pHeader->formatByte; + PutShortBE(&hdrBuf[82], pHeader->privateWord); + + return pGFD->Write(hdrBuf, kDC42DataOffset); +} + +/* + * Check to see if this is a DiskCopy 4.2 image. + * + * The format doesn't really have a magic number, but if we're stringent + * about our interpretation of some of the header fields (e.g. we only + * recognize 800K disks) we should be okay. + */ +/*static*/ DIError WrapperDiskCopy42::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + DC42Header header; + + LOGI("Testing for DiskCopy"); + + if (wrappedLength < 800 * 1024 + kDC42DataOffset) + return kDIErrGeneric; + + pGFD->Rewind(); + if (ReadHeader(pGFD, &header) != 0) + return kDIErrGeneric; + + DumpHeader(&header); + + return kDIErrNone; +} + +/* + * Compute the funky DiskCopy checksum. + * + * Position "pGFD" at the start of data. + */ +/*static*/ DIError WrapperDiskCopy42::ComputeChecksum(GenericFD* pGFD, uint32_t* pChecksum) +{ + DIError dierr = kDIErrNone; + uint8_t buf[512]; + long dataRem = 800 * 1024 /*pHeader->dataSize*/; + uint32_t checksum; + + assert(dataRem % sizeof(buf) == 0); + assert((sizeof(buf) & 0x01) == 0); // we take it two bytes at a time + + checksum = 0; + while (dataRem) { + int i; + + dierr = pGFD->Read(buf, sizeof(buf)); + if (dierr != kDIErrNone) { + LOGI(" DC42 read failed, dataRem=%ld (err=%d)", dataRem, dierr); + return dierr; + } + + for (i = 0; i < (int) sizeof(buf); i += 2) { + uint16_t val = GetShortBE(buf+i); + + checksum += val; + if (checksum & 0x01) + checksum = checksum >> 1 | 0x80000000; + else + checksum = checksum >> 1; + } + + dataRem -= sizeof(buf); + } + + *pChecksum = checksum; + + return dierr; +} + +/* + * Prepare a DiskCopy image for use. + */ +DIError WrapperDiskCopy42::Prep(GenericFD* pGFD, di_off_t wrappedLength, + bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + DIError dierr; + DC42Header header; + + LOGI("Prepping for DiskCopy 4.2"); + pGFD->Rewind(); + if (ReadHeader(pGFD, &header) != 0) + return kDIErrGeneric; + + /* + * Verify checksum. File should already be seeked to appropriate place. + */ + uint32_t checksum; + dierr = ComputeChecksum(pGFD, &checksum); + if (dierr != kDIErrNone) + return dierr; + + if (checksum != header.dataChecksum) { + LOGW(" DC42 checksum mismatch (got 0x%08x, expected 0x%08x)", + checksum, header.dataChecksum); + fBadChecksum = true; + //return kDIErrBadChecksum; + } else { + LOGD(" DC42 checksum matches!"); + } + + + /* looks good! */ + *pLength = header.dataSize; + *pPhysical = DiskImg::kPhysicalFormatSectors; + *pOrder = DiskImg::kSectorOrderProDOS; + + *ppNewGFD = new GFDGFD; + return ((GFDGFD*)*ppNewGFD)->Open(pGFD, kDC42DataOffset, readOnly); + +} + +/* + * Initialize fields for a new file. + */ +DIError WrapperDiskCopy42::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + DIError dierr; + DC42Header header; + + assert(length == 800 * 1024); + assert(physical == DiskImg::kPhysicalFormatSectors); + //assert(order == DiskImg::kSectorOrderProDOS); + + InitHeader(&header); // set all but checksum + + dierr = WriteHeader(pWrapperGFD, &header); + if (dierr != kDIErrNone) { + LOGI("ERROR: 2MG header write failed (err=%d)", dierr); + return dierr; + } + + *pWrappedLength = length + kDC42DataOffset; + *pDataFD = new GFDGFD; + return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, kDC42DataOffset, false); +} + +/* + * We only use GFDGFD, so there's no data to write. However, we do need + * to update the checksum, and append our "fake" tag section. + */ +DIError WrapperDiskCopy42::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + DIError dierr; + uint32_t checksum; + + /* compute the data checksum */ + dierr = pWrapperGFD->Seek(kDC42DataOffset, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + dierr = ComputeChecksum(pWrapperGFD, &checksum); + if (dierr != kDIErrNone) { + LOGI(" DC42 failed while computing checksum (err=%d)", dierr); + goto bail; + } + + /* write it into the wrapper */ + dierr = pWrapperGFD->Seek(kDC42ChecksumOffset, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + dierr = WriteLongBE(pWrapperGFD, checksum); + if (dierr != kDIErrNone) + goto bail; + + /* add the tag bytes */ + dierr = pWrapperGFD->Seek(kDC42DataOffset + 800*1024, kSeekSet); + char* tmpBuf; + tmpBuf = new char[kDC42FakeTagLen]; + if (tmpBuf == NULL) + return kDIErrMalloc; + memset(tmpBuf, 0, kDC42FakeTagLen); + dierr = pWrapperGFD->Write(tmpBuf, kDC42FakeTagLen, NULL); + delete[] tmpBuf; + if (dierr != kDIErrNone) + goto bail; + +bail: + return dierr; +} + + +/* + * =========================================================================== + * Sim2eHDV (Sim2e virtual hard-drive images) + * =========================================================================== + */ + +/* +// mkhdv.c +// +// Create a Hard Disk Volume File (.HDV) for simIIe +static int mkhdv(FILE *op, uint blocks) +{ + byte sector[512]; + byte data[15]; + uint i; + + memset(data, 0, sizeof(data)); + memcpy(data, "SIMSYSTEM_HDV", 13); + data[13] = (blocks & 0xff); + data[14] = (blocks & 0xff00) >> 8; + fwrite(data, 1, sizeof(data), op); + + memset(sector, 0, sizeof(sector)); + for (i = 0; i < blocks; i++) + fwrite(sector, 1, sizeof(sector), op); + return 0; +} +*/ + +const int kSim2eHeaderLen = 15; +static const char* kSim2eID = "SIMSYSTEM_HDV"; + +/* + * Test for a virtual hard-drive image. This is either a "raw" unadorned + * image, or one with a 15-byte "SimIIe" header on it. + */ +DIError WrapperSim2eHDV::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + char buf[kSim2eHeaderLen]; + + LOGI("Testing for Sim2e HDV"); + + if (wrappedLength < 512 || + ((wrappedLength - kSim2eHeaderLen) % 4096) != 0) + { + return kDIErrGeneric; + } + + pGFD->Rewind(); + + if (pGFD->Read(buf, sizeof(buf)) != kDIErrNone) + return kDIErrGeneric; + + if (strncmp(buf, kSim2eID, strlen(kSim2eID)) == 0) + return kDIErrNone; + else + return kDIErrGeneric; +} + +/* + * These are always ProDOS volumes. + */ +DIError WrapperSim2eHDV::Prep(GenericFD* pGFD, di_off_t wrappedLength, + bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + *pLength = wrappedLength - kSim2eHeaderLen; + *pPhysical = DiskImg::kPhysicalFormatSectors; + *pOrder = DiskImg::kSectorOrderProDOS; + + *ppNewGFD = new GFDGFD; + return ((GFDGFD*)*ppNewGFD)->Open(pGFD, kSim2eHeaderLen, readOnly); +} + +/* + * Initialize fields for a new file. + */ +DIError WrapperSim2eHDV::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + uint8_t header[kSim2eHeaderLen]; + long blocks = (long) (length / 512); + + assert(physical == DiskImg::kPhysicalFormatSectors); + assert(order == DiskImg::kSectorOrderProDOS); + + if (blocks < 4 || blocks > 65536) { + LOGI(" Sim2e invalid blocks %ld", blocks); + return kDIErrInvalidArg; + } + if (blocks == 65536) // 32MB volumes are actually 31.9 + blocks = 65535; + + memcpy(header, kSim2eID, strlen(kSim2eID)); + header[13] = (uint8_t) blocks; + header[14] = (uint8_t) ((blocks & 0xff00) >> 8); + DIError dierr = pWrapperGFD->Write(header, kSim2eHeaderLen); + if (dierr != kDIErrNone) { + LOGI(" Sim2eHDV header write failed (err=%d)", dierr); + return dierr; + } + + *pWrappedLength = length + kSim2eHeaderLen; + + *pDataFD = new GFDGFD; + return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, kSim2eHeaderLen, false); +} + +/* + * We only use GFDGFD, so there's nothing to do here. + */ +DIError WrapperSim2eHDV::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + return kDIErrNone; +} + + +/* + * =========================================================================== + * TrackStar .app images + * =========================================================================== + */ + +/* + * File format: + * $0000 track 0 data + * $1a00 track 1 data + * $3400 track 2 data + * ... + * $3f600 track 39 data + * + * Each track consists of: + * $0000 Text description of disk contents (same on every track), in low + * ASCII, padded out with spaces ($20) + * $002e Start of zeroed-out header field + * $0080 $00 (indicates end of data when reading from end??) + * $0081 Raw nibble data (hi bit set), written backwards + * $19fe Start offset of track data + * + * Take the start offset, add 128, and walk backward until you find a + * value with the high bit clear. If the start offset is zero, start + * scanning from $19fd backward. (This approach courtesty Gerald Ryckman.) + * + * My take: the "offset" actually indicates the length of data, and the + * $00 is there to simplify somebody's algorithm. If the offset is zero + * it means the track couldn't be analyzed successfully, so a raw dump has + * been provided. Tracks 35-39 on most Apple II disks have zero length, + * but occasionally one analyzes "successfully" with some horribly truncated + * length. + * + * I'm going to assert that byte $81 be zero and that nothing else has the + * high bit clear until you hit the end of valid data. + * + * Because the nibbles are stored in reverse order, it's easiest to unpack + * the tracks to local buffers, then re-pack them when saving the file. + */ + +/* + * Test to see if this is a TrackStar 5.25" disk image. + * + * While the image format supports variable-length nibble tracks, it uses + * fixed-length fields to store them. Each track is stored in 6656 bytes, + * but has a 129-byte header and a 2-byte footer (max of 6525). + * + * Images may be 40-track (5.25") or 80-track (5.25" disk with half-track + * stepping). The latter is useful in some circumstances for handling + * copy-protected disks. We don't have a half-track interface, so we just + * ignore the odd-numbered tracks. + * + * There is currently no way for the API to set the number of tracks. + */ +/*static*/ DIError WrapperTrackStar::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + DIError dierr = kDIErrNone; + LOGI("Testing for TrackStar"); + int numTracks; + + /* check the length */ + if (wrappedLength == 6656*40) + numTracks = 40; + else if (wrappedLength == 6656*80) + numTracks = 80; + else + return kDIErrGeneric; + + LOGI(" Checking for %d-track image", numTracks); + + /* verify each track */ + uint8_t trackBuf[kFileTrackStorageLen]; + pGFD->Rewind(); + for (int trk = 0; trk < numTracks; trk++) { + dierr = pGFD->Read(trackBuf, sizeof(trackBuf)); + if (dierr != kDIErrNone) + goto bail; + dierr = VerifyTrack(trk, trackBuf); + if (dierr != kDIErrNone) + goto bail; + } + LOGI(" TrackStar tracks verified"); + +bail: + return dierr; +} + +/* + * Check the format. + */ +/*static*/ DIError WrapperTrackStar::VerifyTrack(int track, const uint8_t* trackBuf) +{ + unsigned int dataLen; + + if (trackBuf[0x80] != 0) { + LOGI(" TrackStar track=%d found nonzero at 129", track); + return kDIErrGeneric; + } + + dataLen = GetShortLE(trackBuf + 0x19fe); + if (dataLen > kMaxTrackLen) { + LOGI(" TrackStar track=%d len=%d exceeds max (%d)", + track, dataLen, kMaxTrackLen); + return kDIErrGeneric; + } + if (dataLen == 0) + dataLen = kMaxTrackLen; + + unsigned int i; + for (i = 0; i < dataLen; i++) { + if ((trackBuf[0x81 + i] & 0x80) == 0) { + LOGI(" TrackStar track=%d found invalid data 0x%02x at %d", + track, trackBuf[0x81+i], i); + return kDIErrGeneric; + } + } + + if (track == 0) { + LOGI(" TrackStar msg='%s'", trackBuf); + } + + return kDIErrNone; +} + +/* + * Fill in some details. + */ +DIError WrapperTrackStar::Prep(GenericFD* pGFD, di_off_t wrappedLength, + bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + LOGI("Prepping for TrackStar"); + DIError dierr = kDIErrNone; + + if (wrappedLength == kFileTrackStorageLen * 40) + fImageTracks = 40; + else if (wrappedLength == kFileTrackStorageLen * 80) + fImageTracks = 80; + else + return kDIErrInternal; + + dierr = Unpack(pGFD, ppNewGFD); + if (dierr != kDIErrNone) + return dierr; + + *pLength = kTrackStarNumTracks * kTrackAllocSize; + *pPhysical = DiskImg::kPhysicalFormatNib525_Var; + *pOrder = DiskImg::kSectorOrderPhysical; + + return dierr; +} + +/* + * Unpack reverse-order nibbles from "pGFD" to a new memory buffer + * created in "*ppNewGFD". + */ +DIError WrapperTrackStar::Unpack(GenericFD* pGFD, GenericFD** ppNewGFD) +{ + DIError dierr; + GFDBuffer* pNewGFD = NULL; + uint8_t* buf = NULL; + + pGFD->Rewind(); + + buf = new uint8_t[kTrackStarNumTracks * kTrackAllocSize]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + pNewGFD = new GFDBuffer; + if (pNewGFD == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = pNewGFD->Open(buf, kTrackStarNumTracks * kTrackAllocSize, + true, false, false); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + dierr = UnpackDisk(pGFD, pNewGFD); + if (dierr != kDIErrNone) + goto bail; + + *ppNewGFD = pNewGFD; + pNewGFD = NULL; // now owned by caller + +bail: + delete[] buf; + delete pNewGFD; + return dierr; +} + +/* + * Unpack a TrackStar image. This is mainly just copying bytes around. The + * nibble code is perfectly happy with odd-sized tracks. However, we want + * to be able to find a particular track without having to do a lookup. So, + * we just block out 40 sets of 6656-byte tracks. + * + * The resultant image will always have 40 tracks. On an 80-track image + * we skip the odd ones. + * + * The bytes are stored in reverse order, so we need to unpack them to a + * separate buffer. + * + * This fills out "fNibbleTrackInfo". + */ +DIError WrapperTrackStar::UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD) +{ + DIError dierr = kDIErrNone; + uint8_t inBuf[kFileTrackStorageLen]; + uint8_t outBuf[kTrackAllocSize]; + int i, trk; + + assert(kTrackStarNumTracks <= kMaxNibbleTracks525); + + pGFD->Rewind(); + pNewGFD->Rewind(); + + /* we don't currently support half-tracks */ + fNibbleTrackInfo.numTracks = kTrackStarNumTracks; + for (trk = 0; trk < kTrackStarNumTracks; trk++) { + unsigned int dataLen; + + fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize; + + /* these were verified earlier, so assume data is okay */ + dierr = pGFD->Read(inBuf, sizeof(inBuf)); + if (dierr != kDIErrNone) + goto bail; + + dataLen = GetShortLE(inBuf + 0x19fe); + if (dataLen == 0) + dataLen = kMaxTrackLen; + assert(dataLen <= kMaxTrackLen); + assert(dataLen <= sizeof(outBuf)); + + fNibbleTrackInfo.length[trk] = dataLen; + + memset(outBuf, 0x11, sizeof(outBuf)); + for (i = 0; i < (int) dataLen; i++) + outBuf[i] = inBuf[128+dataLen-i]; + + pNewGFD->Write(outBuf, sizeof(outBuf)); + + if (fImageTracks == 2*kTrackStarNumTracks) { + /* skip the odd-numbered tracks */ + dierr = pGFD->Read(inBuf, sizeof(inBuf)); + if (dierr != kDIErrNone) + goto bail; + } + } + +bail: + return dierr; +} + + +/* + * Initialize stuff for a new file. There's no file header or other + * goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone. + */ +DIError WrapperTrackStar::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + assert(length == kTrackLenTrackStar525 * kTrackCount525 || + length == kTrackLenTrackStar525 * kTrackStarNumTracks); + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(order == DiskImg::kSectorOrderPhysical); + + DIError dierr; + uint8_t* buf = NULL; + int numTracks = (int) (length / kTrackLenTrackStar525); + int i; + + /* + * Set up the track offset and length table. We use the maximum + * data length (kTrackLenTrackStar525) for each. The nibble write + * routine will alter the length field as appropriate. + */ + fNibbleTrackInfo.numTracks = numTracks; + assert(fNibbleTrackInfo.numTracks <= kMaxNibbleTracks525); + for (i = 0; i < numTracks; i++) { + fNibbleTrackInfo.offset[i] = kTrackLenTrackStar525 * i; + fNibbleTrackInfo.length[i] = kTrackLenTrackStar525; + } + + /* + * Create a blank chunk of memory for the image. + */ + buf = new uint8_t[(int) length]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + GFDBuffer* pNewGFD; + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, false); + if (dierr != kDIErrNone) { + delete pNewGFD; + goto bail; + } + *pDataFD = pNewGFD; + buf = NULL; // now owned by pNewGFD; + + // can't set *pWrappedLength yet + +bail: + delete[] buf; + return dierr; +} + +/* + * Write the stored data into TrackStar format. + * + * The source data is in "pDataGFD" in a layout described by fNibbleTrackInfo. + * We need to create the new file in "pWrapperGFD". + */ +DIError WrapperTrackStar::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + DIError dierr = kDIErrNone; + + assert(dataLen == kTrackLenTrackStar525 * kTrackCount525 || + dataLen == kTrackLenTrackStar525 * kTrackStarNumTracks); + assert(kTrackLenTrackStar525 <= kMaxTrackLen); + + pDataGFD->Rewind(); + + uint8_t writeBuf[kFileTrackStorageLen]; + uint8_t dataBuf[kTrackLenTrackStar525]; + int track, trackLen; + + for (track = 0; track < kTrackStarNumTracks; track++) { + if (track < fNibbleTrackInfo.numTracks) { + dierr = pDataGFD->Read(dataBuf, kTrackLenTrackStar525); + if (dierr != kDIErrNone) + goto bail; + trackLen = fNibbleTrackInfo.length[track]; + assert(fNibbleTrackInfo.offset[track] == kTrackLenTrackStar525 * track); + } else { + LOGI(" TrackStar faking track %d", track); + memset(dataBuf, 0xff, sizeof(dataBuf)); + trackLen = kMaxTrackLen; + } + + memset(writeBuf, 0x80, sizeof(writeBuf)); // not strictly necessary + memset(writeBuf, 0x20, kCommentFieldLen); + memset(writeBuf+kCommentFieldLen, 0x00, 0x81-kCommentFieldLen); + + const char* comment; + if (fStorageName != NULL && *fStorageName != '\0') + comment = fStorageName; + else + comment = "(created by CiderPress)"; + if (strlen(comment) > kCommentFieldLen) + memcpy(writeBuf, comment, kCommentFieldLen); + else + memcpy(writeBuf, comment, strlen(comment)); + + int i; + for (i = 0; i < trackLen; i++) { + // If we write a value here with the high bit clear, we will + // reject the file when we try to open it. So, we force the + // high bit on here, on the assumption that the nibble data + // we've been handled is otherwise good. + //writeBuf[0x81+i] = dataBuf[trackLen - i -1]; + writeBuf[0x81+i] = dataBuf[trackLen - i -1] | 0x80; + } + + if (trackLen == kMaxTrackLen) + PutShortLE(writeBuf + 0x19fe, 0); + else + PutShortLE(writeBuf + 0x19fe, (uint16_t) trackLen); + + dierr = pWrapperGFD->Write(writeBuf, sizeof(writeBuf)); + if (dierr != kDIErrNone) + goto bail; + } + + *pWrappedLen = pWrapperGFD->Tell(); + assert(*pWrappedLen == kFileTrackStorageLen * kTrackStarNumTracks); + +bail: + return dierr; +} + +void WrapperTrackStar::SetNibbleTrackLength(int track, int length) +{ + assert(track >= 0); + assert(length > 0 && length <= kMaxTrackLen); + assert(track < fNibbleTrackInfo.numTracks); + + LOGI(" TrackStar: set length of track %d to %d", track, length); + fNibbleTrackInfo.length[track] = length; +} + + +/* + * =========================================================================== + * FDI (Formatted Disk Image) format + * =========================================================================== + */ + +/* + * The format is described in detail in documents on the "disk2fdi" web site. + * + * FDI is currently unique in that it can (and often does) store nibble + * images of 3.5" disks. Rather than add an understanding of nibblized + * 3.5" disks to DiskImg, I've chosen to present it as a simple 800K + * ProDOS disk image. The only flaw in the scheme is that we have to + * keep track of the bad blocks in a parallel data structure. + */ + +/*static*/ const char* WrapperFDI::kFDIMagic = "Formatted Disk Image file\r\n"; + +/* + * Test to see if this is an FDI disk image. + */ +/*static*/ DIError WrapperFDI::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + DIError dierr = kDIErrNone; + uint8_t headerBuf[kMinHeaderLen]; + FDIHeader hdr; + + LOGI("Testing for FDI"); + + pGFD->Rewind(); + dierr = pGFD->Read(headerBuf, sizeof(headerBuf)); + if (dierr != kDIErrNone) + goto bail; + + UnpackHeader(headerBuf, &hdr); + if (strcmp(hdr.signature, kFDIMagic) != 0) { + LOGI("FDI: FDI signature not found"); + return kDIErrGeneric; + } + if (hdr.version < kMinVersion) { + LOGI("FDI: bad version 0x%.04x", hdr.version); + return kDIErrGeneric; + } + +bail: + return dierr; +} + +/* + * Unpack a 512-byte buffer with the FDI header into its components. + */ +/*static*/ void WrapperFDI::UnpackHeader(const uint8_t* headerBuf, FDIHeader* pHdr) +{ + memcpy(pHdr->signature, &headerBuf[0], kSignatureLen); + pHdr->signature[kSignatureLen] = '\0'; + memcpy(pHdr->creator, &headerBuf[27], kCreatorLen); + pHdr->creator[kCreatorLen] = '\0'; + memcpy(pHdr->comment, &headerBuf[59], kCommentLen); + pHdr->comment[kCommentLen] = '\0'; + + pHdr->version = GetShortBE(&headerBuf[140]); + pHdr->lastTrack = GetShortBE(&headerBuf[142]); + pHdr->lastHead = headerBuf[144]; + pHdr->type = headerBuf[145]; + pHdr->rotSpeed = headerBuf[146]; + pHdr->flags = headerBuf[147]; + pHdr->tpi = headerBuf[148]; + pHdr->headWidth = headerBuf[149]; + pHdr->reserved = GetShortBE(&headerBuf[150]); +} + +/* + * Dump the contents of an FDI header. + */ +/*static*/ void WrapperFDI::DumpHeader(const FDIHeader* pHdr) +{ + static const char* kTypes[] = { + "8\"", "5.25\"", "3.5\"", "3\"" + }; + static const char* kTPI[] = { + "48", "67", "96", "100", "135", "192" + }; + + LOGI(" FDI header contents:"); + LOGI(" signature: '%s'", pHdr->signature); + LOGI(" creator : '%s'", pHdr->creator); + LOGI(" comment : '%s'", pHdr->comment); + LOGI(" version : %d.%d", pHdr->version >> 8, pHdr->version & 0xff); + LOGI(" lastTrack: %d", pHdr->lastTrack); + LOGI(" lastHead : %d", pHdr->lastHead); + LOGI(" type : %d (%s)", pHdr->type, + (/*pHdr->type >= 0 &&*/ pHdr->type < NELEM(kTypes)) ? + kTypes[pHdr->type] : "???"); + LOGI(" rotSpeed : %drpm", pHdr->rotSpeed + 128); + LOGI(" flags : 0x%02x", pHdr->flags); + LOGI(" tpi : %d (%s)", pHdr->tpi, + (/*pHdr->tpi >= 0 &&*/ pHdr->tpi < NELEM(kTPI)) ? + kTPI[pHdr->tpi] : "???"); + LOGI(" headWidth: %d (%s)", pHdr->headWidth, + (/*pHdr->headWidth >= 0 &&*/ pHdr->headWidth < NELEM(kTPI)) ? + kTPI[pHdr->headWidth] : "???"); + LOGI(" reserved : %d", pHdr->reserved); +} + +/* + * Unpack the disk to heap storage. + */ +DIError WrapperFDI::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly, + di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + LOGI("Prepping for FDI"); + DIError dierr = kDIErrNone; + FDIHeader hdr; + + pGFD->Rewind(); + dierr = pGFD->Read(fHeaderBuf, sizeof(fHeaderBuf)); + if (dierr != kDIErrNone) + goto bail; + + UnpackHeader(fHeaderBuf, &hdr); + if (strcmp(hdr.signature, kFDIMagic) != 0) + return kDIErrGeneric; + DumpHeader(&hdr); + + /* + * There are two formats that we're interested in, 3.5" and 5.25". They + * are handled differently within CiderPress, so we split here. + * + * Sometimes disk2fdi finds extra tracks. No Apple II hardware ever + * went past 40 on 5.25" disks, but we'll humor the software and allow + * images with up to 50. Ditto for 3.5" disks, which should always + * have 80 double-sided tracks. + */ + if (hdr.type == kDiskType525) { + LOGI("FDI: decoding 5.25\" disk"); + if (hdr.lastHead != 0 || hdr.lastTrack >= kMaxNibbleTracks525 + 10) { + LOGI("FDI: bad params head=%d ltrack=%d", + hdr.lastHead, hdr.lastTrack); + dierr = kDIErrUnsupportedImageFeature; + goto bail; + } + if (hdr.lastTrack >= kMaxNibbleTracks525) { + LOGI("FDI: reducing hdr.lastTrack from %d to %d", + hdr.lastTrack, kMaxNibbleTracks525-1); + hdr.lastTrack = kMaxNibbleTracks525-1; + } + + /* + * Unpack to a series of variable-length nibble tracks. The data + * goes into ppNewGFD, and a table of track info goes into + * fNibbleTrackInfo. + */ + dierr = Unpack525(pGFD, ppNewGFD, hdr.lastTrack+1, hdr.lastHead+1); + if (dierr != kDIErrNone) + return dierr; + + *pLength = kMaxNibbleTracks525 * kTrackAllocSize; + *pPhysical = DiskImg::kPhysicalFormatNib525_Var; + *pOrder = DiskImg::kSectorOrderPhysical; + } else if (hdr.type == kDiskType35) { + LOGI("FDI: decoding 3.5\" disk"); + if (hdr.lastHead != 1 || hdr.lastTrack >= kMaxNibbleTracks35 + 10) { + LOGI("FDI: bad params head=%d ltrack=%d", + hdr.lastHead, hdr.lastTrack); + dierr = kDIErrUnsupportedImageFeature; + goto bail; + } + if (hdr.lastTrack >= kMaxNibbleTracks35) { + LOGI("FDI: reducing hdr.lastTrack from %d to %d", + hdr.lastTrack, kMaxNibbleTracks35-1); + hdr.lastTrack = kMaxNibbleTracks35-1; + } + + /* + * Unpack to 800K of 512-byte ProDOS-order blocks, with a + * "bad block" map. + */ + dierr = Unpack35(pGFD, ppNewGFD, hdr.lastTrack+1, hdr.lastHead+1, + ppBadBlockMap); + if (dierr != kDIErrNone) + return dierr; + + *pLength = 800 * 1024; + *pPhysical = DiskImg::kPhysicalFormatSectors; + *pOrder = DiskImg::kSectorOrderProDOS; + } else { + LOGI("FDI: unsupported disk type"); + dierr = kDIErrUnsupportedImageFeature; + goto bail; + } + +bail: + return dierr; +} + +/* + * Unpack pulse timing values to nibbles. + */ +DIError WrapperFDI::Unpack525(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls, + int numHeads) +{ + DIError dierr = kDIErrNone; + GFDBuffer* pNewGFD = NULL; + uint8_t* buf = NULL; + int numTracks; + + numTracks = numCyls * numHeads; + if (numTracks < kMaxNibbleTracks525) + numTracks = kMaxNibbleTracks525; + + pGFD->Rewind(); + + buf = new uint8_t[numTracks * kTrackAllocSize]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + pNewGFD = new GFDBuffer; + if (pNewGFD == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = pNewGFD->Open(buf, numTracks * kTrackAllocSize, + true, false, false); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + dierr = UnpackDisk525(pGFD, pNewGFD, numCyls, numHeads); + if (dierr != kDIErrNone) + goto bail; + + *ppNewGFD = pNewGFD; + pNewGFD = NULL; // now owned by caller + +bail: + delete[] buf; + delete pNewGFD; + return dierr; +} + +/* + * Unpack pulse timing values to fully-decoded blocks. + */ +DIError WrapperFDI::Unpack35(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls, + int numHeads, LinearBitmap** ppBadBlockMap) +{ + DIError dierr = kDIErrNone; + GFDBuffer* pNewGFD = NULL; + uint8_t* buf = NULL; + + pGFD->Rewind(); + + buf = new uint8_t[800 * 1024]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + pNewGFD = new GFDBuffer; + if (pNewGFD == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = pNewGFD->Open(buf, 800 * 1024, true, false, false); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + *ppBadBlockMap = new LinearBitmap(1600); + if (*ppBadBlockMap == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + dierr = UnpackDisk35(pGFD, pNewGFD, numCyls, numHeads, *ppBadBlockMap); + if (dierr != kDIErrNone) + goto bail; + + *ppNewGFD = pNewGFD; + pNewGFD = NULL; // now owned by caller + +bail: + delete[] buf; + delete pNewGFD; + return dierr; +} + +/* + * Initialize stuff for a new file. There's no file header or other + * goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone. + */ +DIError WrapperFDI::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + DIError dierr = kDIErrGeneric; // not yet +#if 0 + uint8_t* buf = NULL; + int numTracks = (int) (length / kTrackLenTrackStar525); + int i; + + assert(length == kTrackLenTrackStar525 * kTrackCount525 || + length == kTrackLenTrackStar525 * kTrackStarNumTracks); + assert(physical == DiskImg::kPhysicalFormatNib525_Var); + assert(order == DiskImg::kSectorOrderPhysical); + + /* + * Set up the track offset and length table. We use the maximum + * data length (kTrackLenTrackStar525) for each. The nibble write + * routine will alter the length field as appropriate. + */ + fNibbleTrackInfo.numTracks = numTracks; + assert(fNibbleTrackInfo.numTracks <= kMaxNibbleTracks); + for (i = 0; i < numTracks; i++) { + fNibbleTrackInfo.offset[i] = kTrackLenTrackStar525 * i; + fNibbleTrackInfo.length[i] = kTrackLenTrackStar525; + } + + /* + * Create a blank chunk of memory for the image. + */ + buf = new uint8_t[(int) length]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + GFDBuffer* pNewGFD; + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, false); + if (dierr != kDIErrNone) { + delete pNewGFD; + goto bail; + } + *pDataFD = pNewGFD; + buf = NULL; // now owned by pNewGFD; + + // can't set *pWrappedLength yet + +bail: + delete[] buf; +#endif + return dierr; +} + +/* + * Write the stored data into FDI format. + * + * The source data is in "pDataGFD" in a layout described by fNibbleTrackInfo. + * We need to create the new file in "pWrapperGFD". + */ +DIError WrapperFDI::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + DIError dierr = kDIErrGeneric; // not yet + +#if 0 + assert(dataLen == kTrackLenTrackStar525 * kTrackCount525 || + dataLen == kTrackLenTrackStar525 * kTrackStarNumTracks); + assert(kTrackLenTrackStar525 <= kMaxTrackLen); + + pDataGFD->Rewind(); + + uint8_t writeBuf[kFileTrackStorageLen]; + uint8_t dataBuf[kTrackLenTrackStar525]; + int track, trackLen; + + for (track = 0; track < kTrackStarNumTracks; track++) { + if (track < fNibbleTrackInfo.numTracks) { + dierr = pDataGFD->Read(dataBuf, kTrackLenTrackStar525); + if (dierr != kDIErrNone) + goto bail; + trackLen = fNibbleTrackInfo.length[track]; + assert(fNibbleTrackInfo.offset[track] == kTrackLenTrackStar525 * track); + } else { + LOGI(" TrackStar faking track %d", track); + memset(dataBuf, 0xff, sizeof(dataBuf)); + trackLen = kMaxTrackLen; + } + + memset(writeBuf, 0x80, sizeof(writeBuf)); // not strictly necessary + memset(writeBuf, 0x20, kCommentFieldLen); + memset(writeBuf+kCommentFieldLen, 0x00, 0x81-kCommentFieldLen); + + const char* comment; + if (fStorageName != NULL && *fStorageName != '\0') + comment = fStorageName; + else + comment = "(created by CiderPress)"; + if (strlen(comment) > kCommentFieldLen) + memcpy(writeBuf, comment, kCommentFieldLen); + else + memcpy(writeBuf, comment, strlen(comment)); + + int i; + for (i = 0; i < trackLen; i++) + writeBuf[0x81+i] = dataBuf[trackLen - i -1]; + + if (trackLen == kMaxTrackLen) + PutShortLE(writeBuf + 0x19fe, 0); + else + PutShortLE(writeBuf + 0x19fe, (uint16_t) trackLen); + + dierr = pWrapperGFD->Write(writeBuf, sizeof(writeBuf)); + if (dierr != kDIErrNone) + goto bail; + } + + *pWrappedLen = pWrapperGFD->Tell(); + assert(*pWrappedLen == kFileTrackStorageLen * kTrackStarNumTracks); + +bail: +#endif + return dierr; +} + +void WrapperFDI::SetNibbleTrackLength(int track, int length) +{ + assert(false); // not yet +#if 0 + assert(track >= 0); + assert(length > 0 && length <= kMaxTrackLen); + assert(track < fNibbleTrackInfo.numTracks); + + LOGI(" FDI: set length of track %d to %d", track, length); + fNibbleTrackInfo.length[track] = length; +#endif +} + + +/* + * =========================================================================== + * Unadorned nibble format + * =========================================================================== + */ + +/* + * See if this is unadorned nibble format. + */ +/*static*/ DIError WrapperUnadornedNibble::Test(GenericFD* pGFD, + di_off_t wrappedLength) +{ + LOGI("Testing for unadorned nibble"); + + /* test length */ + if (wrappedLength != kTrackCount525 * kTrackLenNib525 && + wrappedLength != kTrackCount525 * kTrackLenNb2525) + { + return kDIErrGeneric; + } + + /* quick scan for invalid data */ + const int kScanSize = 512; + uint8_t buf[kScanSize]; + + pGFD->Rewind(); + if (pGFD->Read(buf, sizeof(buf)) != kDIErrNone) + return kDIErrGeneric; + + /* + * Make sure this is a nibble image and not just a ProDOS volume that + * happened to get the right number of blocks. The primary test is + * for < 0x80 since there's no way that can be valid, even on a track + * full of junk. + */ + for (int i = 0; i < kScanSize; i++) { + if (buf[i] < 0x80) { + LOGD(" Disqualifying len=%ld from nibble, byte=0x%02x", + (long) wrappedLength, buf[i]); + return kDIErrGeneric; + } else if (buf[i] < 0x96) { + LOGD(" Warning: funky byte 0x%02x in file", buf[i]); + } + } + + return kDIErrNone; +} + +/* + * Prepare unadorned nibble for use. Not much to do here. + */ +DIError WrapperUnadornedNibble::Prep(GenericFD* pGFD, di_off_t wrappedLength, + bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + LOGI("Prep for unadorned nibble"); + + if (wrappedLength == kTrackCount525 * kTrackLenNib525) { + LOGI(" Prepping for 6656-byte NIB"); + *pPhysical = DiskImg::kPhysicalFormatNib525_6656; + } else if (wrappedLength == kTrackCount525 * kTrackLenNb2525) { + LOGI(" Prepping for 6384-byte NB2"); + *pPhysical = DiskImg::kPhysicalFormatNib525_6384; + } else { + LOGI(" Unexpected wrappedLength %ld for unadorned nibble", + (long) wrappedLength); + assert(false); + } + + *pLength = wrappedLength; + *pOrder = DiskImg::kSectorOrderPhysical; + + *ppNewGFD = new GFDGFD; + return ((GFDGFD*)*ppNewGFD)->Open(pGFD, 0, readOnly); +} + +/* + * Initialize fields for a new file. + */ +DIError WrapperUnadornedNibble::Create(di_off_t length, + DiskImg::PhysicalFormat physical, DiskImg::SectorOrder order, + short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + LOGI("Create unadorned nibble"); + + *pWrappedLength = length; + *pDataFD = new GFDGFD; + return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, 0, false); +} + +/* + * We only use GFDGFD, so there's nothing to do here. + */ +DIError WrapperUnadornedNibble::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + return kDIErrNone; +} + + +/* + * =========================================================================== + * Unadorned sectors + * =========================================================================== + */ + +/* + * See if this is unadorned sector format. The only way we can really tell + * is by looking at the file size. + * + * The only requirement is that it be a multiple of 512 bytes. This holds + * for all ProDOS volumes and all floppy disk images. We also need to test + * for 13-sector ".d13" images. + * + * It also holds for 35-track 6656-byte unadorned nibble images, so we need + * to test for them first. + */ +/*static*/ DIError WrapperUnadornedSector::Test(GenericFD* pGFD, di_off_t wrappedLength) +{ + LOGI("Testing for unadorned sector (wrappedLength=%ld/%u)", + (long) (wrappedLength >> 32), (uint32_t) wrappedLength); + if (wrappedLength >= 1536 && (wrappedLength % 512) == 0) + return kDIErrNone; + if (wrappedLength == kD13Length) // 13-sector image? + return kDIErrNone; + + return kDIErrGeneric; +} + +/* + * Prepare unadorned sector for use. Not much to do here. + */ +DIError WrapperUnadornedSector::Prep(GenericFD* pGFD, di_off_t wrappedLength, + bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical, + DiskImg::SectorOrder* pOrder, short* pDiskVolNum, + LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD) +{ + LOGI("Prepping for unadorned sector"); + assert(wrappedLength > 0); + *pLength = wrappedLength; + *pPhysical = DiskImg::kPhysicalFormatSectors; + //*pOrder = undetermined + + *ppNewGFD = new GFDGFD; + return ((GFDGFD*)*ppNewGFD)->Open(pGFD, 0, readOnly); +} + +/* + * Initialize fields for a new file. + */ +DIError WrapperUnadornedSector::Create(di_off_t length, DiskImg::PhysicalFormat physical, + DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD, + di_off_t* pWrappedLength, GenericFD** pDataFD) +{ + LOGI("Create unadorned sector"); + + *pWrappedLength = length; + *pDataFD = new GFDGFD; + return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, 0, false); +} + +/* + * We only use GFDGFD, so there's nothing to do here. + */ +DIError WrapperUnadornedSector::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD, + di_off_t dataLen, di_off_t* pWrappedLen) +{ + return kDIErrNone; +} diff --git a/diskimg/MacPart.cpp b/diskimg/MacPart.cpp new file mode 100644 index 0000000..e94f37a --- /dev/null +++ b/diskimg/MacPart.cpp @@ -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; +} diff --git a/diskimg/Makefile b/diskimg/Makefile new file mode 100644 index 0000000..6da2346 --- /dev/null +++ b/diskimg/Makefile @@ -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. diff --git a/diskimg/MicroDrive.cpp b/diskimg/MicroDrive.cpp new file mode 100644 index 0000000..0201da9 --- /dev/null +++ b/diskimg/MicroDrive.cpp @@ -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; +} diff --git a/diskimg/Nibble.cpp b/diskimg/Nibble.cpp new file mode 100644 index 0000000..5b53509 --- /dev/null +++ b/diskimg/Nibble.cpp @@ -0,0 +1,1021 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * DiskImg nibblized read/write functions. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + +/* define this for verbose output */ +//#define NIB_VERBOSE_DEBUG + + +/* + * =========================================================================== + * Nibble encoding and decoding + * =========================================================================== + */ + +/*static*/ uint8_t DiskImg::kDiskBytes53[32] = { + 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba, + 0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb, + 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef, + 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff +}; +/*static*/ uint8_t DiskImg::kDiskBytes62[64] = { + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; +/*static*/ uint8_t DiskImg::kInvDiskBytes53[256]; // all values are 0-31 +/*static*/ uint8_t DiskImg::kInvDiskBytes62[256]; // all values are 0-63 + +/* + * Compute tables to convert disk bytes back to values. + * + * Should be called once, at DLL initialization time. + */ +/*static*/ void DiskImg::CalcNibbleInvTables(void) +{ + unsigned int i; + + memset(kInvDiskBytes53, kInvInvalidValue, sizeof(kInvDiskBytes53)); + for (i = 0; i < sizeof(kDiskBytes53); i++) { + assert(kDiskBytes53[i] >= 0x96); + kInvDiskBytes53[kDiskBytes53[i]] = i; + } + + memset(kInvDiskBytes62, kInvInvalidValue, sizeof(kInvDiskBytes62)); + for (i = 0; i < sizeof(kDiskBytes62); i++) { + assert(kDiskBytes62[i] >= 0x96); + kInvDiskBytes62[kDiskBytes62[i]] = i; + } +} + +/* + * Find the start of the data field of a sector in nibblized data. + * + * Returns the index start on success or -1 on failure. + */ +int DiskImg::FindNibbleSectorStart(const CircularBufferAccess& buffer, int track, + int sector, const NibbleDescr* pNibbleDescr, int* pVol) +{ + const int kMaxDataReach = 48; // fairly arbitrary + //DIError dierr; + long trackLen = buffer.GetSize(); + + assert(sector >= 0 && sector < 16); + + int i; + + for (i = 0; i < trackLen; i++) { + bool foundAddr = false; + + if (pNibbleDescr->special == kNibbleSpecialSkipFirstAddrByte) { + if (/*buffer[i] == pNibbleDescr->addrProlog[0] &&*/ + buffer[i+1] == pNibbleDescr->addrProlog[1] && + buffer[i+2] == pNibbleDescr->addrProlog[2]) + { + foundAddr = true; + } + } else { + if (buffer[i] == pNibbleDescr->addrProlog[0] && + buffer[i+1] == pNibbleDescr->addrProlog[1] && + buffer[i+2] == pNibbleDescr->addrProlog[2]) + { + foundAddr = true; + } + } + + if (foundAddr) { + //i += 3; + + /* found the address header, decode the address */ + short hdrVol, hdrTrack, hdrSector, hdrChksum; + DecodeAddr(buffer, i+3, &hdrVol, &hdrTrack, &hdrSector, + &hdrChksum); + + if (pNibbleDescr->addrVerifyTrack && track != hdrTrack) { + LOGI(" Track mismatch (T=%d) got T=%d,S=%d", + track, hdrTrack, hdrSector); + continue; + } + + if (pNibbleDescr->addrVerifyChecksum) { + if ((pNibbleDescr->addrChecksumSeed ^ + hdrVol ^ hdrTrack ^ hdrSector ^ hdrChksum) != 0) + { + LOGW(" Addr checksum mismatch (want T=%d,S=%d, got " + "T=%d,S=%d)", + track, sector, hdrTrack, hdrSector); + continue; + } + } + + i += 3; + + int j; + for (j = 0; j < pNibbleDescr->addrEpilogVerifyCount; j++) { + if (buffer[i+8+j] != pNibbleDescr->addrEpilog[j]) { + //LOGI(" Bad epilog byte %d (%02x vs %02x)", + // j, buffer[i+8+j], pNibbleDescr->addrEpilog[j]); + break; + } + } + if (j != pNibbleDescr->addrEpilogVerifyCount) + continue; + +#ifdef NIB_VERBOSE_DEBUG + LOGI(" Good header, T=%d,S=%d (looking for T=%d,S=%d)", + hdrTrack, hdrSector, track, sector); +#endif + + if (pNibbleDescr->special == kNibbleSpecialMuse) { + /* e.g. original Castle Wolfenstein */ + if (track > 2) { + if ((hdrSector & 0x01) != 0) + continue; + hdrSector /= 2; + } + } + + if (sector != hdrSector) + continue; + + /* + * Scan forward and look for data prolog. We want to limit + * the reach of our search so we don't blunder into the data + * field of the next sector. + */ + for (j = 0; j < kMaxDataReach; j++) { + if (buffer[i + j] == pNibbleDescr->dataProlog[0] && + buffer[i + j +1] == pNibbleDescr->dataProlog[1] && + buffer[i + j +2] == pNibbleDescr->dataProlog[2]) + { + *pVol = hdrVol; + return buffer.Normalize(i + j + 3); + } + } + } + } + +#ifdef NIB_VERBOSE_DEBUG + LOGI(" Couldn't find T=%d,S=%d", track, sector); +#endif + return -1; +} + +/* + * Decode the values in the address field. + */ +void DiskImg::DecodeAddr(const CircularBufferAccess& buffer, int offset, + short* pVol, short* pTrack, short* pSector, short* pChksum) +{ + //unsigned int vol, track, sector, chksum; + + *pVol = ConvFrom44(buffer[offset], buffer[offset+1]); + *pTrack = ConvFrom44(buffer[offset+2], buffer[offset+3]); + *pSector = ConvFrom44(buffer[offset+4], buffer[offset+5]); + *pChksum = ConvFrom44(buffer[offset+6], buffer[offset+7]); +} + +/* + * Decode the sector pointed to by "pData" and described by "pNibbleDescr". + * This invokes the appropriate function (e.g. 5&3 or 6&2) to decode the + * data into a 256-byte sector. + */ +DIError DiskImg::DecodeNibbleData(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) +{ + switch (pNibbleDescr->encoding) { + case kNibbleEnc62: + return DecodeNibble62(buffer, idx, sctBuf, pNibbleDescr); + case kNibbleEnc53: + return DecodeNibble53(buffer, idx, sctBuf, pNibbleDescr); + default: + assert(false); + return kDIErrInternal; + } +} + +/* + * Encode the sector pointed to by "pData" and described by "pNibbleDescr". + * This invokes the appropriate function (e.g. 5&3 or 6&2) to encode the + * data from a 256-byte sector. + */ +void DiskImg::EncodeNibbleData(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const +{ + switch (pNibbleDescr->encoding) { + case kNibbleEnc62: + EncodeNibble62(buffer, idx, sctBuf, pNibbleDescr); + break; + case kNibbleEnc53: + EncodeNibble53(buffer, idx, sctBuf, pNibbleDescr); + break; + default: + assert(false); + break; + } +} + +/* + * Decode 6&2 encoding. + */ +DIError DiskImg::DecodeNibble62(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) +{ + uint8_t twos[kChunkSize62 * 3]; // 258 + int chksum = pNibbleDescr->dataChecksumSeed; + uint8_t decodedVal; + int i; + + /* + * Pull the 342 bytes out, convert them from disk bytes to 6-bit + * values, and arrange them into a DOS-like pair of buffers. + */ + for (i = 0; i < kChunkSize62; i++) { + decodedVal = kInvDiskBytes62[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes62)); + + chksum ^= decodedVal; + twos[i] = + ((chksum & 0x01) << 1) | ((chksum & 0x02) >> 1); + twos[i + kChunkSize62] = + ((chksum & 0x04) >> 1) | ((chksum & 0x08) >> 3); + twos[i + kChunkSize62*2] = + ((chksum & 0x10) >> 3) | ((chksum & 0x20) >> 5); + } + + for (i = 0; i < 256; i++) { + decodedVal = kInvDiskBytes62[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes62)); + + chksum ^= decodedVal; + sctBuf[i] = (chksum << 2) | twos[i]; + } + + /* + * Grab the 343rd byte (the checksum byte) and see if we did this + * right. + */ + //printf("Dec checksum value is 0x%02x\n", chksum); + decodedVal = kInvDiskBytes62[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes62)); + chksum ^= decodedVal; + + if (pNibbleDescr->dataVerifyChecksum && chksum != 0) { + LOGI(" NIB bad data checksum"); + return kDIErrBadChecksum; + } + return kDIErrNone; +} + +/* + * Encode 6&2 encoding. + */ +void DiskImg::EncodeNibble62(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const +{ + uint8_t top[256]; + uint8_t twos[kChunkSize62]; + int twoPosn, twoShift; + int i; + + memset(twos, 0, sizeof(twos)); + + twoShift = 0; + for (i = 0, twoPosn = kChunkSize62-1; i < 256; i++) { + unsigned int val = sctBuf[i]; + top[i] = val >> 2; + twos[twoPosn] |= ((val & 0x01) << 1 | (val & 0x02) >> 1) << twoShift; + + if (twoPosn == 0) { + twoPosn = kChunkSize62; + twoShift += 2; + } + twoPosn--; + } + + int chksum = pNibbleDescr->dataChecksumSeed; + for (i = kChunkSize62-1; i >= 0; i--) { + assert(twos[i] < sizeof(kDiskBytes62)); + buffer[idx++] = kDiskBytes62[twos[i] ^ chksum]; + chksum = twos[i]; + } + + for (i = 0; i < 256; i++) { + assert(top[i] < sizeof(kDiskBytes62)); + buffer[idx++] = kDiskBytes62[top[i] ^ chksum]; + chksum = top[i]; + } + + //printf("Enc checksum value is 0x%02x\n", chksum); + buffer[idx++] = kDiskBytes62[chksum]; +} + +/* + * Decode 5&3 encoding. + */ +DIError DiskImg::DecodeNibble53(const CircularBufferAccess& buffer, int idx, + uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) +{ + uint8_t base[256]; + uint8_t threes[kThreeSize]; + int chksum = pNibbleDescr->dataChecksumSeed; + uint8_t decodedVal; + int i; + + /* + * Pull the 410 bytes out, convert them from disk bytes to 5-bit + * values, and arrange them into a DOS-like pair of buffers. + */ + for (i = kThreeSize-1; i >= 0; i--) { + decodedVal = kInvDiskBytes53[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes53)); + + chksum ^= decodedVal; + threes[i] = chksum; + } + + for (i = 0; i < 256; i++) { + decodedVal = kInvDiskBytes53[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes53)); + + chksum ^= decodedVal; + base[i] = (chksum << 3); + } + + /* + * Grab the 411th byte (the checksum byte) and see if we did this + * right. + */ + //printf("Dec checksum value is 0x%02x\n", chksum); + decodedVal = kInvDiskBytes53[buffer[idx++]]; + if (decodedVal == kInvInvalidValue) + return kDIErrInvalidDiskByte; + assert(decodedVal < sizeof(kDiskBytes53)); + chksum ^= decodedVal; + + if (pNibbleDescr->dataVerifyChecksum && chksum != 0) { + LOGI(" NIB bad data checksum (0x%02x)", chksum); + return kDIErrBadChecksum; + } + + /* + * Convert this pile of stuff into 256 data bytes. + */ + uint8_t* bufPtr; + + bufPtr = sctBuf; + for (i = kChunkSize53-1; i >= 0; i--) { + int three1, three2, three3, three4, three5; + + three1 = threes[i]; + three2 = threes[kChunkSize53 + i]; + three3 = threes[kChunkSize53*2 + i]; + three4 = (three1 & 0x02) << 1 | (three2 & 0x02) | (three3 & 0x02) >> 1; + three5 = (three1 & 0x01) << 2 | (three2 & 0x01) << 1 | (three3 & 0x01); + + *bufPtr++ = base[i] | ((three1 >> 2) & 0x07); + *bufPtr++ = base[kChunkSize53 + i] | ((three2 >> 2) & 0x07); + *bufPtr++ = base[kChunkSize53*2 + i] | ((three3 >> 2) & 0x07); + *bufPtr++ = base[kChunkSize53*3 + i] | (three4 & 0x07); + *bufPtr++ = base[kChunkSize53*4 + i] | (three5 & 0x07); + } + assert(bufPtr == sctBuf + 255); + + /* + * Convert the very last byte, which is handled specially. + */ + *bufPtr = base[255] | (threes[kThreeSize-1] & 0x07); + + return kDIErrNone; +} + +/* + * Encode 5&3 encoding. + */ +void DiskImg::EncodeNibble53(const CircularBufferAccess& buffer, int idx, + const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const +{ + uint8_t top[kChunkSize53 * 5 +1]; // (255 / 0xff) +1 + uint8_t threes[kChunkSize53 * 3 +1]; // (153 / 0x99) +1 + int i, chunk; + + /* + * Split the bytes into sections. + */ + chunk = kChunkSize53-1; + for (i = 0; i < (int) sizeof(top)-1; i += 5) { + int three1, three2, three3, three4, three5; + + three1 = *sctBuf++; + three2 = *sctBuf++; + three3 = *sctBuf++; + three4 = *sctBuf++; + three5 = *sctBuf++; + + top[chunk] = three1 >> 3; + top[chunk + kChunkSize53*1] = three2 >> 3; + top[chunk + kChunkSize53*2] = three3 >> 3; + top[chunk + kChunkSize53*3] = three4 >> 3; + top[chunk + kChunkSize53*4] = three5 >> 3; + + threes[chunk] = + (three1 & 0x07) << 2 | (three4 & 0x04) >> 1 | (three5 & 0x04) >> 2; + threes[chunk + kChunkSize53*1] = + (three2 & 0x07) << 2 | (three4 & 0x02) | (three5 & 0x02) >> 1; + threes[chunk + kChunkSize53*2] = + (three3 & 0x07) << 2 | (three4 & 0x01) << 1 | (three5 & 0x01); + + chunk--; + } + assert(chunk == -1); + + /* + * Handle the last byte. + */ + int val; + val = *sctBuf++; + top[255] = val >> 3; + threes[kThreeSize-1] = val & 0x07; + + /* + * Write the bytes. + */ + int chksum = pNibbleDescr->dataChecksumSeed; + for (i = sizeof(threes)-1; i >= 0; i--) { + assert(threes[i] < sizeof(kDiskBytes53)); + buffer[idx++] = kDiskBytes53[threes[i] ^ chksum]; + chksum = threes[i]; + } + + for (i = 0; i < 256; i++) { + assert(top[i] < sizeof(kDiskBytes53)); + buffer[idx++] = kDiskBytes53[top[i] ^ chksum]; + chksum = top[i]; + } + + //printf("Enc checksum value is 0x%02x\n", chksum); + buffer[idx++] = kDiskBytes53[chksum]; +} + + +/* + * =========================================================================== + * Higher-level functions + * =========================================================================== + */ + +/* + * Dump some bytes as hex values into a string. + * + * "buf" must be able to hold (num * 3) characters. + */ +static void DumpBytes(const uint8_t* bytes, unsigned int num, char* buf) +{ + sprintf(buf, "%02x", bytes[0]); + buf += 2; + + for (int i = 1; i < (int) num; i++) { + sprintf(buf, " %02x", bytes[i]); + buf += 3; + } + + *buf = '\0'; +} + +static inline const char* VerifyStr(bool val) +{ + return val ? "verify" : "ignore"; +} + +/* + * Dump the contents of a NibbleDescr struct. + */ +void DiskImg::DumpNibbleDescr(const NibbleDescr* pNibDescr) const +{ + char outBuf1[48]; + char outBuf2[48]; + const char* encodingStr; + + switch (pNibDescr->encoding) { + case kNibbleEnc62: encodingStr = "6&2"; break; + case kNibbleEnc53: encodingStr = "5&3"; break; + case kNibbleEnc44: encodingStr = "4&4"; break; + default: encodingStr = "???"; break; + } + + LOGI("NibbleDescr '%s':", pNibDescr->description); + LOGI(" Nibble encoding is %s", encodingStr); + DumpBytes(pNibDescr->addrProlog, sizeof(pNibDescr->addrProlog), outBuf1); + DumpBytes(pNibDescr->dataProlog, sizeof(pNibDescr->dataProlog), outBuf2); + LOGI(" Addr prolog: %s Data prolog: %s", outBuf1, outBuf2); + DumpBytes(pNibDescr->addrEpilog, sizeof(pNibDescr->addrEpilog), outBuf1); + DumpBytes(pNibDescr->dataEpilog, sizeof(pNibDescr->dataEpilog), outBuf2); + LOGI(" Addr epilog: %s (%d) Data epilog: %s (%d)", + outBuf1, pNibDescr->addrEpilogVerifyCount, + outBuf2, pNibDescr->dataEpilogVerifyCount); + LOGI(" Addr checksum: %s Data checksum: %s", + VerifyStr(pNibDescr->addrVerifyChecksum), + VerifyStr(pNibDescr->dataVerifyChecksum)); + LOGI(" Addr checksum seed: 0x%02x Data checksum seed: 0x%02x", + pNibDescr->addrChecksumSeed, pNibDescr->dataChecksumSeed); + LOGI(" Addr check track: %s", + VerifyStr(pNibDescr->addrVerifyTrack)); +} + + +/* + * Load a nibble track into our track buffer. + */ +DIError DiskImg::LoadNibbleTrack(long track, long* pTrackLen) +{ + DIError dierr = kDIErrNone; + long offset; + assert(track >= 0 && track < kMaxNibbleTracks525); + + *pTrackLen = GetNibbleTrackLength(track); + offset = GetNibbleTrackOffset(track); + assert(*pTrackLen > 0); + assert(offset >= 0); + + if (track == fNibbleTrackLoaded) { +#ifdef NIB_VERBOSE_DEBUG + LOGI(" DI track %d already loaded", track); +#endif + return kDIErrNone; + } else { + LOGI(" DI loading track %ld", track); + } + + /* invalidate in case we fail with partial read */ + fNibbleTrackLoaded = -1; + + /* alloc track buffer if needed */ + if (fNibbleTrackBuf == NULL) { + fNibbleTrackBuf = new uint8_t[kTrackAllocSize]; + if (fNibbleTrackBuf == NULL) + return kDIErrMalloc; + } + + /* + * Read the entire track into memory. + */ + dierr = CopyBytesOut(fNibbleTrackBuf, offset, *pTrackLen); + if (dierr != kDIErrNone) + return dierr; + + fNibbleTrackLoaded = track; + + return dierr; +} + +/* + * Save the track buffer back to disk. + */ +DIError DiskImg::SaveNibbleTrack(void) +{ + if (fNibbleTrackLoaded < 0) { + LOGI("ERROR: tried to save track without loading it first"); + return kDIErrInternal; + } + assert(fNibbleTrackBuf != NULL); + + DIError dierr = kDIErrNone; + long trackLen = GetNibbleTrackLength(fNibbleTrackLoaded); + long offset = GetNibbleTrackOffset(fNibbleTrackLoaded); + + /* write the track to fpDataGFD */ + dierr = CopyBytesIn(fNibbleTrackBuf, offset, trackLen); + return dierr; +} + + +/* + * Count up the number of readable sectors found on this track, and + * return it. If "pVol" is non-NULL, return the volume number from + * one of the readable sectors. + */ +int DiskImg::TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, + int* pVol) +{ + long trackLen; + int count = 0; + + assert(track >= 0 && track < kTrackCount525); + assert(pNibbleDescr != NULL); + + if (LoadNibbleTrack(track, &trackLen) != kDIErrNone) { + LOGI(" DI FindNibbleSectorStart: LoadNibbleTrack failed"); + return 0; + } + + CircularBufferAccess buffer(fNibbleTrackBuf, trackLen); + + int i, sectorIdx; + for (i = 0; i < pNibbleDescr->numSectors; i++) { + int vol; + sectorIdx = FindNibbleSectorStart(buffer, track, i, pNibbleDescr, &vol); + if (sectorIdx >= 0) { + if (pVol != NULL) + *pVol = vol; + + uint8_t sctBuf[256]; + if (DecodeNibbleData(buffer, sectorIdx, sctBuf, pNibbleDescr) == kDIErrNone) + count++; + } + } + + LOGI(" Tests on track=%d with '%s' returning count=%d", + track, pNibbleDescr->description, count); + + return count; +} + +/* + * Analyze the nibblized track data. + * + * On entry: + * fPhysical indicates the appropriate nibble format + * + * On exit: + * fpNibbleDescr points to the most-likely-to-succeed NibbleDescr + * fDOSVolumeNum holds a volume number from one of the tracks + * fNumTracks holds the number of tracks on the disk + */ +DIError DiskImg::AnalyzeNibbleData(void) +{ + assert(IsNibbleFormat(fPhysical)); + + if (fPhysical == kPhysicalFormatNib525_Var) { + /* TrackStar can have up to 40 */ + fNumTracks = fpImageWrapper->GetNibbleNumTracks(); + assert(fNumTracks > 0); + } else { + /* fixed-length formats (.nib, .nb2) are always 35 tracks */ + fNumTracks = kTrackCount525; + } + + /* + * Try to read sectors from tracks 1, 16, 17, and 26. If we can get + * at least 13 out of 16 (or 10 out of 13) on three out of four tracks, + * we have a winner. + */ + int i, good, goodTracks; + int protoVol = kVolumeNumNotSet; + + for (i = 0; i < fNumNibbleDescrEntries; i++) { + if (fpNibbleDescrTable[i].numSectors == 0) { + /* uninitialized "custom" entry */ + LOGI(" Skipping '%s'", fpNibbleDescrTable[i].description); + continue; + } + LOGI(" Trying '%s'", fpNibbleDescrTable[i].description); + goodTracks = 0; + + good = TestNibbleTrack(1, &fpNibbleDescrTable[i], NULL); + if (good > fpNibbleDescrTable[i].numSectors - 4) + goodTracks++; + good = TestNibbleTrack(16, &fpNibbleDescrTable[i], NULL); + if (good > fpNibbleDescrTable[i].numSectors - 4) + goodTracks++; + good = TestNibbleTrack(17, &fpNibbleDescrTable[i], &protoVol); + if (good > fpNibbleDescrTable[i].numSectors - 4) + goodTracks++; + good = TestNibbleTrack(26, &fpNibbleDescrTable[i], NULL); + if (good > fpNibbleDescrTable[i].numSectors - 4) + goodTracks++; + + if (goodTracks >= 3) { + LOGI(" Looks like '%s' (%d-sector), vol=%d", + fpNibbleDescrTable[i].description, + fpNibbleDescrTable[i].numSectors, protoVol); + fpNibbleDescr = &fpNibbleDescrTable[i]; + fDOSVolumeNum = protoVol; + break; + } + } + if (i == fNumNibbleDescrEntries) { + LOGI("AnalyzeNibbleData did not find matching NibbleDescr"); + return kDIErrBadNibbleSectors; + } + + return kDIErrNone; +} + +/* + * Read a sector from a nibble image. + * + * While fNumTracks is valid, fNumSectPerTrack is a little flaky, because + * in theory each track could be formatted differently. + */ +DIError DiskImg::ReadNibbleSector(long track, int sector, void* buf, + const NibbleDescr* pNibbleDescr) +{ + if (pNibbleDescr == NULL) { + /* disk has no recognizable sectors */ + LOGI(" DI ReadNibbleSector: pNibbleDescr is NULL, returning failure"); + return kDIErrBadNibbleSectors; + } + if (sector >= pNibbleDescr->numSectors) { + /* e.g. trying to read sector 14 on a 13-sector disk */ + LOGI(" DI ReadNibbleSector: bad sector number request"); + return kDIErrInvalidSector; + } + + assert(pNibbleDescr != NULL); + assert(IsNibbleFormat(fPhysical)); + assert(track >= 0 && track < GetNumTracks()); + assert(sector >= 0 && sector < pNibbleDescr->numSectors); + + DIError dierr = kDIErrNone; + long trackLen; + int sectorIdx, vol; + + dierr = LoadNibbleTrack(track, &trackLen); + if (dierr != kDIErrNone) { + LOGI(" DI ReadNibbleSector: LoadNibbleTrack %ld failed", track); + return dierr; + } + + CircularBufferAccess buffer(fNibbleTrackBuf, trackLen); + sectorIdx = FindNibbleSectorStart(buffer, track, sector, pNibbleDescr, + &vol); + if (sectorIdx < 0) + return kDIErrSectorUnreadable; + + dierr = DecodeNibbleData(buffer, sectorIdx, (uint8_t*) buf, + pNibbleDescr); + + return dierr; +} + +/* + * Write a sector to a nibble image. + */ +DIError DiskImg::WriteNibbleSector(long track, int sector, const void* buf, + const NibbleDescr* pNibbleDescr) +{ + assert(pNibbleDescr != NULL); + assert(IsNibbleFormat(fPhysical)); + assert(track >= 0 && track < GetNumTracks()); + assert(sector >= 0 && sector < pNibbleDescr->numSectors); + assert(!fReadOnly); + + DIError dierr = kDIErrNone; + long trackLen; + int sectorIdx, vol; + + dierr = LoadNibbleTrack(track, &trackLen); + if (dierr != kDIErrNone) { + LOGI(" DI ReadNibbleSector: LoadNibbleTrack %ld failed", track); + return dierr; + } + + CircularBufferAccess buffer(fNibbleTrackBuf, trackLen); + sectorIdx = FindNibbleSectorStart(buffer, track, sector, pNibbleDescr, + &vol); + if (sectorIdx < 0) + return kDIErrSectorUnreadable; + + EncodeNibbleData(buffer, sectorIdx, (uint8_t*) buf, pNibbleDescr); + + dierr = SaveNibbleTrack(); + if (dierr != kDIErrNone) { + LOGI(" DI ReadNibbleSector: SaveNibbleTrack %ld failed", track); + return dierr; + } + + return dierr; +} + +/* + * Get the contents of the nibble track. + * + * "buf" must be able to hold kTrackAllocSize bytes. + */ +DIError DiskImg::ReadNibbleTrack(long track, uint8_t* buf, long* pTrackLen) +{ + DIError dierr; + + dierr = LoadNibbleTrack(track, pTrackLen); + if (dierr != kDIErrNone) { + LOGI(" DI ReadNibbleTrack: LoadNibbleTrack %ld failed", track); + return dierr; + } + + memcpy(buf, fNibbleTrackBuf, *pTrackLen); + return kDIErrNone; +} + +/* + * Set the contents of a nibble track. + * + * NOTE: This currently does the wrong thing when converting from .nb2 to + * .nib. Fixed-length formats shouldn't be allowed to interact. Figure + * this out someday. For now, the higher-level code prevents it. + */ +DIError DiskImg::WriteNibbleTrack(long track, const uint8_t* buf, long trackLen) +{ + DIError dierr; + long oldTrackLen; + + /* load the track to set the "current track" stuff */ + dierr = LoadNibbleTrack(track, &oldTrackLen); + if (dierr != kDIErrNone) { + LOGI(" DI WriteNibbleTrack: LoadNibbleTrack %ld failed", track); + return dierr; + } + + if (trackLen > GetNibbleTrackAllocLength()) { + LOGI("ERROR: tried to write too-long track len (%ld vs %d)", + trackLen, GetNibbleTrackAllocLength()); + return kDIErrInvalidArg; + } + + if (trackLen < oldTrackLen) // pad out any extra space + memset(fNibbleTrackBuf, 0xff, oldTrackLen); + memcpy(fNibbleTrackBuf, buf, trackLen); + fpImageWrapper->SetNibbleTrackLength(track, trackLen); + + dierr = SaveNibbleTrack(); + if (dierr != kDIErrNone) { + LOGI(" DI ReadNibbleSector: SaveNibbleTrack %ld failed", track); + return dierr; + } + + return kDIErrNone; +} + +/* + * Create a blank nibble image, using fpNibbleDescr as the template. + * Sets "fLength". + * + * Tracks are written the same way regardless of actual track length (be + * it 6656, 6384, or variable-length). Anything longer than 6384 just has + * more padding at the end of the track. + * + * The format looks like this: + * Gap one (48 self-sync bytes) + * For each sector: + * Address field (14 bytes, e.g. d5aa96 vol track sect chksum deaaeb) + * Gap two (six self-sync bytes) + * Data field (6 header bytes, 1 checksum byte, and 342 or 410 data bytes) + * Gap three (27 self-sync bytes) + * + * 48 + (14 + 6 + (6 + 1 + 342) + 27) * 16 = 6384 + * 48 + (14 + 6 + (6 + 1 + 410) + 27) * 13 = 6080 + */ +DIError DiskImg::FormatNibbles(GenericFD* pGFD) const +{ + assert(fHasNibbles); + assert(GetNumTracks() > 0); + + DIError dierr = kDIErrNone; + uint8_t trackBuf[kTrackAllocSize]; + /* these should be the same except for var-len images */ + long trackAllocLen = GetNibbleTrackAllocLength(); + long trackLen = GetNibbleTrackFormatLength(); + int track; + + assert(trackLen > 0); + pGFD->Rewind(); + + /* + * If we don't have sector access, take a shortcut and just fill the + * entire image with 0xff. + */ + if (!fHasSectors) { + memset(trackBuf, 0xff, trackLen); + for (track = 0; track < GetNumTracks(); track++) { + /* write the track to the GFD */ + dierr = pGFD->Write(trackBuf, trackAllocLen); + if (dierr != kDIErrNone) + return dierr; + fpImageWrapper->SetNibbleTrackLength(track, trackAllocLen); + } + + return kDIErrNone; + } + + + assert(fHasSectors); + assert(fpNibbleDescr != NULL); + assert(fpNibbleDescr->numSectors == GetNumSectPerTrack()); + assert(fpNibbleDescr->encoding == kNibbleEnc53 || + fpNibbleDescr->encoding == kNibbleEnc62); + assert(fDOSVolumeNum != kVolumeNumNotSet); + + /* + * Create a prototype sector. The data for a sector full of zeroes + * is exactly the same; only the address header changes. + */ + uint8_t sampleSource[256]; + uint8_t sampleBuf[512]; // must hold 5&3 and 6&2 + CircularBufferAccess sample(sampleBuf, 512); + long dataLen; + + if (fpNibbleDescr->encoding == kNibbleEnc53) + dataLen = 410 +1; + else + dataLen = 342 +1; + + memset(sampleSource, 0, sizeof(sampleSource)); + EncodeNibbleData(sample, 0, sampleSource, fpNibbleDescr); + + /* + * For each track in the image, "format" the expected number of + * sectors, then write the data to the GFD. + */ + for (track = 0; track < GetNumTracks(); track++) { + //LOGI("Formatting track %d", track); + uint8_t* trackPtr = trackBuf; + + /* + * Fill with "self-sync" bytes. + */ + memset(trackBuf, 0xff, trackAllocLen); + + /* gap one */ + trackPtr += 48; + + for (int sector = 0; sector < fpNibbleDescr->numSectors; sector++) { + /* + * Write address field. + */ + uint16_t hdrTrack, hdrSector, hdrVol, hdrChksum; + hdrTrack = track; + hdrSector = sector; + hdrVol = fDOSVolumeNum; + *trackPtr++ = fpNibbleDescr->addrProlog[0]; + *trackPtr++ = fpNibbleDescr->addrProlog[1]; + *trackPtr++ = fpNibbleDescr->addrProlog[2]; + *trackPtr++ = Conv44(hdrVol, true); + *trackPtr++ = Conv44(hdrVol, false); + *trackPtr++ = Conv44(hdrTrack, true); + *trackPtr++ = Conv44(hdrTrack, false); + *trackPtr++ = Conv44(hdrSector, true); + *trackPtr++ = Conv44(hdrSector, false); + hdrChksum = fpNibbleDescr->addrChecksumSeed ^ + hdrVol ^ hdrTrack ^ hdrSector; + *trackPtr++ = Conv44(hdrChksum, true); + *trackPtr++ = Conv44(hdrChksum, false); + *trackPtr++ = fpNibbleDescr->addrEpilog[0]; + *trackPtr++ = fpNibbleDescr->addrEpilog[1]; + *trackPtr++ = fpNibbleDescr->addrEpilog[2]; + + /* gap two */ + trackPtr += 6; + + /* + * Write data field. + */ + *trackPtr++ = fpNibbleDescr->dataProlog[0]; + *trackPtr++ = fpNibbleDescr->dataProlog[1]; + *trackPtr++ = fpNibbleDescr->dataProlog[2]; + memcpy(trackPtr, sampleBuf, dataLen); + trackPtr += dataLen; + *trackPtr++ = fpNibbleDescr->dataEpilog[0]; + *trackPtr++ = fpNibbleDescr->dataEpilog[1]; + *trackPtr++ = fpNibbleDescr->dataEpilog[2]; + + /* gap three */ + trackPtr += 27; + } + + assert(trackPtr - trackBuf == 6384 || + trackPtr - trackBuf == 6080); + + /* + * Write the track to the GFD. + */ + dierr = pGFD->Write(trackBuf, trackAllocLen); + if (dierr != kDIErrNone) + break; + + /* on a variable-length image, reduce track len to match */ + fpImageWrapper->SetNibbleTrackLength(track, trackLen); + } + + return dierr; +} diff --git a/diskimg/Nibble35.cpp b/diskimg/Nibble35.cpp new file mode 100644 index 0000000..a0efeef --- /dev/null +++ b/diskimg/Nibble35.cpp @@ -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, §or); + 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); +} diff --git a/diskimg/OuterWrapper.cpp b/diskimg/OuterWrapper.cpp new file mode 100644 index 0000000..b5609a2 --- /dev/null +++ b/diskimg/OuterWrapper.cpp @@ -0,0 +1,1504 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Code for handling "outer wrappers" like ZIP and gzip. + * + * TODO: for safety, these should compress into a temp file and then rename + * the temp file over the original. The current implementation just + * truncates the open file descriptor or reopens the original file. Both + * risk data loss if the program or system crashes while the data is being + * written to disk. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" +#define DEF_MEM_LEVEL 8 // normally in zutil.h + + +/* + * =========================================================================== + * OuterGzip + * =========================================================================== + */ + +/* + * Test to see if this is a gzip file. + * + * This test is pretty weak, so we shouldn't even be looking at this + * unless the file ends in ".gz". A better test would scan the entire + * header. + * + * Would be nice to just gzopen the file, but unfortunately that tries + * to be "helpful" and reads the file whether it's in gz format or not. + * Some days I could do with a little less "help". + */ +/*static*/ DIError OuterGzip::Test(GenericFD* pGFD, di_off_t outerLength) +{ + const int kGzipMagic = 0x8b1f; // 0x1f 0x8b + uint16_t magic, magicBuf; + const char* imagePath; + + LOGI("Testing for gzip"); + + /* don't need this here, but we will later on */ + imagePath = pGFD->GetPathName(); + if (imagePath == NULL) { + LOGI("Can't test gzip on non-file"); + return kDIErrNotSupported; + } + + pGFD->Rewind(); + + if (pGFD->Read(&magicBuf, 2) != kDIErrNone) + return kDIErrGeneric; + magic = GetShortLE((uint8_t*) &magicBuf); + + if (magic == kGzipMagic) + return kDIErrNone; + else + return kDIErrGeneric; +} + +/* + * The gzip file format has a length embedded in the footer, but + * unfortunately there is no interface to access it. So, we have + * to keep reading until we run out of data, extending the buffer + * to accommodate the new data each time. (We could also just read + * the footer directly, but that requires that there are no garbage + * bytes at the end of the file, which is a real concern on some FTP sites.) + * + * Start out by trying sizes that we think will work (140K, 800K), + * then grow quickly. + * + * The largest possible ProDOS image is 32MB, but it's possible to + * have an HFS volume or a partitioned image larger than that. We currently + * cap the limit to avoid nasty behavior when encountering really + * large .gz files. This isn't great -- we ought to support extracting + * to a temp file, or allowing the caller to specify what the largest + * size they can handle is. + */ +DIError OuterGzip::ExtractGzipImage(gzFile gzfp, char** pBuf, di_off_t* pLength) +{ + DIError dierr = kDIErrNone; + const int kMinEmpty = 256 * 1024; + const int kStartSize = 141 * 1024; + const int kNextSize1 = 801 * 1024; + const int kNextSize2 = 1024 * 1024; + const int kMaxIncr = 4096 * 1024; + const int kAbsoluteMax = kMaxUncompressedSize; + char* buf = NULL; + char* newBuf = NULL; + long curSize, maxSize; + + assert(gzfp != NULL); + assert(pBuf != NULL); + assert(pLength != NULL); + + curSize = 0; + maxSize = kStartSize; + + buf = new char[maxSize]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + while (1) { + long len; + + /* + * Try to fill the buffer. + * + * It appears that zlib v1.1.4 was more tolerant of certain kinds + * of broken archives than v1.2.1. Both give you a pile of data + * on the first read, with no error reported, but the next read + * attempt returns with z_err=-3 (Z_DATA_ERROR) and z_eof set. I'm + * not sure exactly what the flaw is, but I'm guessing something + * got lopped off the end of the archives. gzip v1.3.3 won't touch + * them either. + * + * It would be easy enough to access them, if they were accessible. + * Unfortunately the implementation is buried. Instead, we do + * a quick test against known unadorned floppy disk sizes to see + * if we can salvage the contents. (Our read attempts are all + * slightly *over* the standard disk sizes, so if it comes back right + * on one we're *probably* okay.) + */ + len = gzread(gzfp, buf + curSize, maxSize - curSize); + if (len < 0) { + LOGI(" ExGZ Call to gzread failed, errno=%d", errno); + if (curSize == 140*1024 || curSize == 800*1024) { + LOGI("WARNING: accepting damaged gzip file"); + fWrapperDamaged = true; + break; // sleazy, but currently necessary + } + dierr = kDIErrReadFailed; + goto bail; + } else if (len == 0) { + /* EOF reached */ + break; + } else if (len < (maxSize - curSize)) { + /* we've probably reached the end, but we can't be sure, + so let's go around again */ + LOGI(" ExGZ gzread(%ld) returned %ld, letting it ride", + maxSize - curSize, len); + curSize += len; + } else { + /* update buffer, and grow it if it's not big enough */ + curSize += len; + LOGI(" max=%ld cur=%ld", maxSize, curSize); + if (maxSize - curSize < kMinEmpty) { + /* not enough room, grow it */ + + if (maxSize == kStartSize) + maxSize = kNextSize1; + else if (maxSize == kNextSize1) + maxSize = kNextSize2; + else { + if (maxSize < kMaxIncr) + maxSize = maxSize * 2; + else + maxSize += kMaxIncr; + } + + newBuf = new char[maxSize]; + if (newBuf == NULL) { + LOGI(" ExGZ failed buffer alloc (%ld)", + maxSize); + dierr = kDIErrMalloc; + goto bail; + } + + memcpy(newBuf, buf, curSize); + delete[] buf; + buf = newBuf; + newBuf = NULL; + + LOGI(" ExGZ grew buffer to %ld", maxSize); + } else { + /* don't need to grow buffer yet */ + LOGI(" ExGZ read %ld bytes, cur=%ld max=%ld", + len, curSize, maxSize); + } + } + assert(curSize < maxSize); + + if (curSize > kAbsoluteMax) { + LOGI(" ExGZ excessive size, probably not a disk image"); + dierr = kDIErrTooBig; // close enough + goto bail; + } + } + + if (curSize + (1024*1024) < maxSize) { + /* shrink it down so it fits */ + LOGI(" Down-sizing buffer from %ld to %ld", maxSize, curSize); + newBuf = new char[curSize]; + if (newBuf == NULL) + goto bail; + memcpy(newBuf, buf, curSize); + delete[] buf; + buf = newBuf; + newBuf = NULL; + } + + *pBuf = buf; + *pLength = curSize; + LOGI(" ExGZ final size = %ld", curSize); + + buf = NULL; + +bail: + delete[] buf; + delete[] newBuf; + return dierr; +} + +/* + * Open the archive, and extract the disk image into a memory buffer. + */ +DIError OuterGzip::Load(GenericFD* pOuterGFD, di_off_t outerLength, bool readOnly, + di_off_t* pWrapperLength, GenericFD** ppWrapperGFD) +{ + DIError dierr = kDIErrNone; + GFDBuffer* pNewGFD = NULL; + char* buf = NULL; + di_off_t length = -1; + const char* imagePath; + gzFile gzfp = NULL; + + imagePath = pOuterGFD->GetPathName(); + if (imagePath == NULL) { + assert(false); // should've been caught in Test + return kDIErrNotSupported; + } + + gzfp = gzopen(imagePath, "rb"); // use "readOnly" here + if (gzfp == NULL) { // DON'T retry RO -- should be done at higher level? + LOGI("gzopen failed, errno=%d", errno); + dierr = kDIErrGeneric; + goto bail; + } + + dierr = ExtractGzipImage(gzfp, &buf, &length); + if (dierr != kDIErrNone) + goto bail; + + /* + * Everything is going well. Now we substitute a memory-based GenericFD + * for the existing GenericFD. + */ + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, readOnly); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + /* + * Success! + */ + assert(dierr == kDIErrNone); + *ppWrapperGFD = pNewGFD; + pNewGFD = NULL; + + *pWrapperLength = length; + +bail: + if (dierr != kDIErrNone) { + delete pNewGFD; + } + if (gzfp != NULL) + gzclose(gzfp); + return dierr; +} + +/* + * Save the contents of "pWrapperGFD" to the file pointed to by + * "pOuterGFD". + * + * "pOuterGFD" isn't disturbed (same as Load). All we want is to get the + * filename and then do everything through gzio. + */ +DIError OuterGzip::Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD, + di_off_t wrapperLength) +{ + DIError dierr = kDIErrNone; + const char* imagePath; + gzFile gzfp = NULL; + + LOGI(" GZ save (wrapperLen=%ld)", (long) wrapperLength); + assert(wrapperLength > 0); + + /* + * Reopen the file. + */ + imagePath = pOuterGFD->GetPathName(); + if (imagePath == NULL) { + assert(false); // should've been caught long ago + return kDIErrNotSupported; + } + + gzfp = gzopen(imagePath, "wb"); + if (gzfp == NULL) { + LOGI("gzopen for write failed, errno=%d", errno); + dierr = kDIErrGeneric; + goto bail; + } + + char buf[16384]; + size_t actual; + long written, totalWritten; + + pWrapperGFD->Rewind(); + + totalWritten = 0; + while (wrapperLength > 0) { + dierr = pWrapperGFD->Read(buf, sizeof(buf), &actual); + if (dierr == kDIErrEOF) { + dierr = kDIErrNone; + break; + } + if (dierr != kDIErrNone) { + LOGI("Error reading source GFD during gzip save (err=%d)",dierr); + goto bail; + } + assert(actual > 0); + + written = gzwrite(gzfp, buf, actual); + if (written == 0) { + LOGE("Failed writing %lu bytes to gzio", (unsigned long) actual); + dierr = kDIErrGeneric; + goto bail; + } + + totalWritten += written; + wrapperLength -= actual; + } + assert(wrapperLength == 0); // not expecting any slop + + LOGD(" GZ wrote %ld bytes", totalWritten); + + /* + * Success! + */ + assert(dierr == kDIErrNone); + +bail: + if (gzfp != NULL) + gzclose(gzfp); + return dierr; +} + + +/* + * =========================================================================== + * OuterZip + * =========================================================================== + */ + +/* + * Test to see if this is a ZIP archive. + */ +/*static*/ DIError OuterZip::Test(GenericFD* pGFD, di_off_t outerLength) +{ + DIError dierr = kDIErrNone; + CentralDirEntry cde; + + LOGI("Testing for zip"); + dierr = ReadCentralDir(pGFD, outerLength, &cde); + if (dierr != kDIErrNone) + goto bail; + + /* + * Make sure it's a compression method we support. + */ + if (cde.fCompressionMethod != kCompressStored && + cde.fCompressionMethod != kCompressDeflated) + { + LOGI(" ZIP compression method %d not supported", + cde.fCompressionMethod); + dierr = kDIErrGeneric; + goto bail; + } + + /* + * Limit the size to something reasonable. + */ + if (cde.fUncompressedSize < 512 || + cde.fUncompressedSize > kMaxUncompressedSize) + { + LOGI(" ZIP uncompressed size %u is outside range", + cde.fUncompressedSize); + dierr = kDIErrGeneric; + goto bail; + } + + assert(dierr == kDIErrNone); + +bail: + return dierr; +} + +/* + * Open the archive, and extract the disk image into a memory buffer. + */ +DIError OuterZip::Load(GenericFD* pOuterGFD, di_off_t outerLength, bool readOnly, + di_off_t* pWrapperLength, GenericFD** ppWrapperGFD) +{ + DIError dierr = kDIErrNone; + GFDBuffer* pNewGFD = NULL; + CentralDirEntry cde; + uint8_t* buf = NULL; + di_off_t length = -1; + const char* pExt; + + dierr = ReadCentralDir(pOuterGFD, outerLength, &cde); + if (dierr != kDIErrNone) + goto bail; + + if (cde.fFileNameLength > 0) { + pExt = FindExtension((const char*) cde.fFileName, kZipFssep); + if (pExt != NULL) { + assert(*pExt == '.'); + SetExtension(pExt+1); + + LOGI("OuterZip using extension '%s'", GetExtension()); + } + + SetStoredFileName((const char*) cde.fFileName); + } + + dierr = ExtractZipEntry(pOuterGFD, &cde, &buf, &length); + if (dierr != kDIErrNone) + goto bail; + + /* + * Everything is going well. Now we substitute a memory-based GenericFD + * for the existing GenericFD. + */ + pNewGFD = new GFDBuffer; + dierr = pNewGFD->Open(buf, length, true, false, readOnly); + if (dierr != kDIErrNone) + goto bail; + buf = NULL; // now owned by pNewGFD; + + /* + * Success! + */ + assert(dierr == kDIErrNone); + *ppWrapperGFD = pNewGFD; + pNewGFD = NULL; + + *pWrapperLength = length; + +bail: + if (dierr != kDIErrNone) { + delete pNewGFD; + } + return dierr; +} + +/* + * Save the contents of "pWrapperGFD" to the file pointed to by + * "pOuterGFD". + */ +DIError OuterZip::Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD, + di_off_t wrapperLength) +{ + DIError dierr = kDIErrNone; + LocalFileHeader lfh; + CentralDirEntry cde; + EndOfCentralDir eocd; + di_off_t lfhOffset; + + LOGI(" ZIP save (wrapperLen=%ld)", (long) wrapperLength); + assert(wrapperLength > 0); + + dierr = pOuterGFD->Rewind(); + if (dierr != kDIErrNone) + goto bail; + dierr = pOuterGFD->Truncate(); + if (dierr != kDIErrNone) + goto bail; + + dierr = pWrapperGFD->Rewind(); + if (dierr != kDIErrNone) + goto bail; + + lfhOffset = pOuterGFD->Tell(); // always 0 with only one file + + /* + * Don't store an empty filename. Some applications, e.g. Info-ZIP's + * "unzip", get confused. Ideally the DiskImg image creation code + * will have set the actual filename, with an extension that matches + * the file contents. + */ + if (fStoredFileName == NULL || fStoredFileName[0] == '\0') + SetStoredFileName("disk"); + + /* + * Write the ZIP local file header. We don't have file lengths or + * CRCs yet, so we have to go back and fill those in later. + */ + lfh.fVersionToExtract = kDefaultVersion; +#if NO_ZIP_COMPRESS + lfh.fGPBitFlag = 0; + lfh.fCompressionMethod = 0; +#else + lfh.fGPBitFlag = 0x0002; // indicates maximum compression used + lfh.fCompressionMethod = 8; // when compressionMethod == deflate +#endif + GetMSDOSTime(&lfh.fLastModFileDate, &lfh.fLastModFileTime); + lfh.SetFileName(fStoredFileName); + dierr = lfh.Write(pOuterGFD); + if (dierr != kDIErrNone) + goto bail; + + /* + * Write the compressed data. + */ + uint32_t crc; + di_off_t compressedLen; + if (lfh.fCompressionMethod == kCompressDeflated) { + dierr = DeflateGFDToGFD(pOuterGFD, pWrapperGFD, wrapperLength, + &compressedLen, &crc); + if (dierr != kDIErrNone) + goto bail; + } else if (lfh.fCompressionMethod == kCompressStored) { + dierr = GenericFD::CopyFile(pOuterGFD, pWrapperGFD, wrapperLength, + &crc); + if (dierr != kDIErrNone) + goto bail; + compressedLen = wrapperLength; + } else { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + /* + * Go back and take care of the local file header stuff. + * + * It's not supposed to be necessary, but some utilities (WinZip, + * Info-ZIP) get bent out of shape if these aren't set and the data + * is compressed. They seem okay with it when the file isn't + * compressed. I don't understand this behavior, but writing the + * local file header is easy enough. + */ + lfh.fCRC32 = crc; + lfh.fCompressedSize = (uint32_t) compressedLen; + lfh.fUncompressedSize = (uint32_t) wrapperLength; + + di_off_t curPos; + curPos = pOuterGFD->Tell(); + dierr = pOuterGFD->Seek(lfhOffset, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + dierr = lfh.Write(pOuterGFD); + if (dierr != kDIErrNone) + goto bail; + dierr = pOuterGFD->Seek(curPos, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + di_off_t cdeStart, cdeFinish; + cdeStart = pOuterGFD->Tell(); + + /* + * Write the central dir entry. This is largely just a copy of the + * data in the local file header (and in fact some utilities will + * get rather bent out of shape if the two don't match exactly). + */ + cde.fVersionMadeBy = kDefaultVersion; + cde.fVersionToExtract = lfh.fVersionToExtract; + cde.fGPBitFlag = lfh.fGPBitFlag; + cde.fCompressionMethod = lfh.fCompressionMethod; + cde.fLastModFileDate = lfh.fLastModFileDate; + cde.fLastModFileTime = lfh.fLastModFileTime; + cde.fCRC32 = lfh.fCRC32; + cde.fCompressedSize = lfh.fCompressedSize; + cde.fUncompressedSize = lfh.fUncompressedSize; + assert(lfh.fExtraFieldLength == 0 && cde.fExtraFieldLength == 0); + cde.fExternalAttrs = 0x81b60020; // matches what WinZip does + cde.fLocalHeaderRelOffset = (uint32_t) lfhOffset; + cde.SetFileName(fStoredFileName); + dierr = cde.Write(pOuterGFD); + if (dierr != kDIErrNone) + goto bail; + + cdeFinish = pOuterGFD->Tell(); + + /* + * Write the end-of-central-dir stuff. + */ + eocd.fNumEntries = 1; + eocd.fTotalNumEntries = 1; + eocd.fCentralDirSize = (uint32_t) (cdeFinish - cdeStart); + eocd.fCentralDirOffset = (uint32_t) cdeStart; + assert(eocd.fCentralDirSize >= EndOfCentralDir::kEOCDLen); + dierr = eocd.Write(pOuterGFD); + if (dierr != kDIErrNone) + goto bail; + + /* + * Success! + */ + assert(dierr == kDIErrNone); + +bail: + return dierr; +} + +/* + * Track the name of the file stored in the ZIP archive. + */ +void OuterZip::SetStoredFileName(const char* name) +{ + delete[] fStoredFileName; + fStoredFileName = StrcpyNew(name); +} + +/* + * Find the central directory and read the contents. + * + * We currently only support archives with a single entry. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that the Info-ZIP guys + * do it though, so we're in pretty good company if this fails. + */ +/*static*/ DIError OuterZip::ReadCentralDir(GenericFD* pGFD, di_off_t outerLength, + CentralDirEntry* pDirEntry) +{ + DIError dierr = kDIErrNone; + EndOfCentralDir eocd; + uint8_t* buf = NULL; + di_off_t seekStart; + long readAmount; + int i; + + /* too small to be a ZIP archive? */ + if (outerLength < EndOfCentralDir::kEOCDLen + 4) + return kDIErrGeneric; + + buf = new uint8_t[kMaxEOCDSearch]; + if (buf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + if (outerLength > kMaxEOCDSearch) { + seekStart = outerLength - kMaxEOCDSearch; + readAmount = kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) outerLength; + } + dierr = pGFD->Seek(seekStart, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + /* read the last part of the file into the buffer */ + dierr = pGFD->Read(buf, readAmount); + if (dierr != kDIErrNone) + goto bail; + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + GetLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + LOGI("+++ Found EOCD at buf+%d", i); + break; + } + } + if (i < 0) { + LOGI("+++ EOCD not found, not ZIP"); + dierr = kDIErrGeneric; + goto bail; + } + + /* extract eocd values */ + dierr = eocd.ReadBuf(buf + i, readAmount - i); + if (dierr != kDIErrNone) + goto bail; + eocd.Dump(); + + if (eocd.fDiskNumber != 0 || eocd.fDiskWithCentralDir != 0 || + eocd.fNumEntries != 1 || eocd.fTotalNumEntries != 1) + { + LOGI(" Probable ZIP archive has more than one member"); + dierr = kDIErrFileArchive; + goto bail; + } + + /* + * So far so good. "fCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward fCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + dierr = pGFD->Seek(eocd.fCentralDirOffset, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + /* + * Read the central dir entry. + */ + dierr = pDirEntry->Read(pGFD); + if (dierr != kDIErrNone) + goto bail; + + pDirEntry->Dump(); + + { + uint8_t checkBuf[4]; + dierr = pGFD->Read(checkBuf, 4); + if (dierr != kDIErrNone) + goto bail; + if (GetLongLE(checkBuf) != EndOfCentralDir::kSignature) { + LOGI("CDE read check failed"); + assert(false); + dierr = kDIErrGeneric; + goto bail; + } + LOGI("+++ CDE read check passed"); + } + +bail: + delete[] buf; + return dierr; +} + +/* + * The central directory tells us where to find the local header. We + * have to skip over that to get to the start of the data. + */ +DIError OuterZip::ExtractZipEntry(GenericFD* pOuterGFD, CentralDirEntry* pCDE, + uint8_t** pBuf, di_off_t* pLength) +{ + DIError dierr = kDIErrNone; + LocalFileHeader lfh; + uint8_t* buf = NULL; + + /* seek to the start of the local header */ + dierr = pOuterGFD->Seek(pCDE->fLocalHeaderRelOffset, kSeekSet); + if (dierr != kDIErrNone) + goto bail; + + /* + * Read the local file header, mainly as a way to get past it. There + * are legitimate reasons why the size fields and filename might be + * empty, so we really don't want to depend on any data in the LFH. + * We just need to find where the data starts. + */ + dierr = lfh.Read(pOuterGFD); + if (dierr != kDIErrNone) + goto bail; + lfh.Dump(); + + /* we should now be pointing at the data */ + LOGI("File offset is 0x%08lx", (long) pOuterGFD->Tell()); + + buf = new uint8_t[pCDE->fUncompressedSize]; + if (buf == NULL) { + /* a very real possibility */ + LOGI(" ZIP unable to allocate buffer of %u bytes", + pCDE->fUncompressedSize); + dierr = kDIErrMalloc; + goto bail; + } + + /* unpack or copy the data */ + if (pCDE->fCompressionMethod == kCompressDeflated) { + dierr = InflateGFDToBuffer(pOuterGFD, pCDE->fCompressedSize, + pCDE->fUncompressedSize, buf); + if (dierr != kDIErrNone) + goto bail; + } else if (pCDE->fCompressionMethod == kCompressStored) { + dierr = pOuterGFD->Read(buf, pCDE->fUncompressedSize); + if (dierr != kDIErrNone) + goto bail; + } else { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + /* check the CRC32 */ + uint32_t crc; + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, buf, pCDE->fUncompressedSize); + + if (crc == pCDE->fCRC32) { + LOGI("+++ ZIP CRCs match"); + } else { + LOGI("ZIP CRC mismatch: inflated crc32=0x%08x, stored=0x%08x", + crc, pCDE->fCRC32); + dierr = kDIErrBadChecksum; + goto bail; + } + + *pBuf = buf; + *pLength = pCDE->fUncompressedSize; + + buf = NULL; + +bail: + delete[] buf; + return dierr; +} + +/* + * Uncompress data from "pOuterGFD" to "buf". + * + * "buf" must be able to hold "uncompSize" bytes. + */ +DIError OuterZip::InflateGFDToBuffer(GenericFD* pGFD, unsigned long compSize, + unsigned long uncompSize, uint8_t* buf) +{ + DIError dierr = kDIErrNone; + const size_t kReadBufSize = 65536; + uint8_t* readBuf = NULL; + z_stream zstream; + int zerr; + unsigned long compRemaining; + + readBuf = new uint8_t[kReadBufSize]; + if (readBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + compRemaining = compSize; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = buf; + zstream.avail_out = uncompSize; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + dierr = kDIErrInternal; + if (zerr == Z_VERSION_ERROR) { + LOGI("Installed zlib is not compatible with linked version (%s)", + ZLIB_VERSION); + } else { + LOGI("Call to inflateInit2 failed (zerr=%d)", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + unsigned long getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kReadBufSize) ? + kReadBufSize : compRemaining; + LOGI("+++ reading %ld bytes (%ld left)", getSize, + compRemaining); + + dierr = pGFD->Read(readBuf, getSize); + if (dierr != kDIErrNone) { + LOGI("inflate read failed"); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = readBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + dierr = kDIErrInternal; + LOGI("zlib inflate call failed (zerr=%d)", zerr); + goto z_bail; + } + + /* output buffer holds all, so no need to write the output */ + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if (zstream.total_out != uncompSize) { + dierr = kDIErrBadCompressedData; + LOGI("Size mismatch on inflated file (%ld vs %ld)", + zstream.total_out, uncompSize); + goto z_bail; + } + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] readBuf; + return dierr; +} + +/* + * Get the current date/time, in MS-DOS format. + */ +void OuterZip::GetMSDOSTime(uint16_t* pDate, uint16_t* pTime) +{ +#if 0 + /* this gets gmtime; we want localtime */ + SYSTEMTIME sysTime; + FILETIME fileTime; + ::GetSystemTime(&sysTime); + ::SystemTimeToFileTime(&sysTime, &fileTime); + ::FileTimeToDosDateTime(&fileTime, pDate, pTime); + //LOGI("+++ Windows date: %04x %04x %d", *pDate, *pTime, + // (*pTime >> 11) & 0x1f); +#endif + + time_t now = time(NULL); + DOSTime(now, pDate, pTime); + //LOGI("+++ Our date : %04x %04x %d", *pDate, *pTime, + // (*pTime >> 11) & 0x1f); +} + +/* + * Convert a time_t to MS-DOS date and time values. + */ +void OuterZip::DOSTime(time_t when, uint16_t* pDate, uint16_t* pTime) +{ + time_t even; + + *pDate = *pTime = 0; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ + ptm = localtime(&even); + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + *pDate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + *pTime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; +} + +/* + * Compress "length" bytes of data from "pSrc" to "pDst". + */ +DIError OuterZip::DeflateGFDToGFD(GenericFD* pDst, GenericFD* pSrc, + di_off_t srcLen, di_off_t* pCompLength, uint32_t* pCRC) +{ + DIError dierr = kDIErrNone; + const size_t kBufSize = 32768; + uint8_t* inBuf = NULL; + uint8_t* outBuf = NULL; + z_stream zstream; + uint32_t crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new uint8_t[kBufSize]; + outBuf = new uint8_t[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + dierr = kDIErrInternal; + if (zerr == Z_VERSION_ERROR) { + LOGI("Installed zlib is not compatible with linked version (%s)", + ZLIB_VERSION); + } else { + LOGI("Call to deflateInit2 failed (zerr=%d)", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + long getSize; + int flush; + + /* only read if the input is empty */ + if (zstream.avail_in == 0 && srcLen) { + getSize = (srcLen > (long) kBufSize) ? kBufSize : (long) srcLen; + LOGI("+++ reading %ld bytes", getSize); + + dierr = pSrc->Read(inBuf, getSize); + if (dierr != kDIErrNone) { + LOGI("deflate read failed"); + goto z_bail; + } + + srcLen -= getSize; + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (srcLen == 0) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGI("zlib deflate call failed (zerr=%d)", zerr); + dierr = kDIErrInternal; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != kBufSize)) + { + LOGI("+++ writing %ld bytes", zstream.next_out - outBuf); + dierr = pDst->Write(outBuf, zstream.next_out - outBuf); + if (dierr != kDIErrNone) { + LOGI("write failed in deflate"); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCompLength = zstream.total_out; + *pCRC = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return dierr; +} + +/* + * Set the "fExtension" field. + */ +void OuterZip::SetExtension(const char* ext) +{ + delete[] fExtension; + fExtension = StrcpyNew(ext); +} + + +/* + * =================================== + * OuterZip::LocalFileHeader + * =================================== + */ + +/* + * Read a local file header. + * + * On entry, "pGFD" points to the signature at the start of the header. + * On exit, "pGFD" points to the start of data. + */ +DIError OuterZip::LocalFileHeader::Read(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + uint8_t buf[kLFHLen]; + + dierr = pGFD->Read(buf, kLFHLen); + if (dierr != kDIErrNone) + goto bail; + + if (GetLongLE(&buf[0x00]) != kSignature) { + LOGI(" ZIP: whoops: didn't find expected signature"); + assert(false); + return kDIErrGeneric; + } + + fVersionToExtract = GetShortLE(&buf[0x04]); + fGPBitFlag = GetShortLE(&buf[0x06]); + fCompressionMethod = GetShortLE(&buf[0x08]); + fLastModFileTime = GetShortLE(&buf[0x0a]); + fLastModFileDate = GetShortLE(&buf[0x0c]); + fCRC32 = GetLongLE(&buf[0x0e]); + fCompressedSize = GetLongLE(&buf[0x12]); + fUncompressedSize = GetLongLE(&buf[0x16]); + fFileNameLength = GetShortLE(&buf[0x1a]); + fExtraFieldLength = GetShortLE(&buf[0x1c]); + + /* grab filename */ + if (fFileNameLength != 0) { + assert(fFileName == NULL); + fFileName = new uint8_t[fFileNameLength+1]; + if (fFileName == NULL) { + dierr = kDIErrMalloc; + goto bail; + } else { + dierr = pGFD->Read(fFileName, fFileNameLength); + fFileName[fFileNameLength] = '\0'; + } + if (dierr != kDIErrNone) + goto bail; + } + + dierr = pGFD->Seek(fExtraFieldLength, kSeekCur); + if (dierr != kDIErrNone) + goto bail; + +bail: + return dierr; +} + +/* + * Write a local file header. + */ +DIError OuterZip::LocalFileHeader::Write(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + uint8_t buf[kLFHLen]; + + PutLongLE(&buf[0x00], kSignature); + PutShortLE(&buf[0x04], fVersionToExtract); + PutShortLE(&buf[0x06], fGPBitFlag); + PutShortLE(&buf[0x08], fCompressionMethod); + PutShortLE(&buf[0x0a], fLastModFileTime); + PutShortLE(&buf[0x0c], fLastModFileDate); + PutLongLE(&buf[0x0e], fCRC32); + PutLongLE(&buf[0x12], fCompressedSize); + PutLongLE(&buf[0x16], fUncompressedSize); + PutShortLE(&buf[0x1a], fFileNameLength); + PutShortLE(&buf[0x1c], fExtraFieldLength); + + dierr = pGFD->Write(buf, kLFHLen); + if (dierr != kDIErrNone) + goto bail; + + /* write filename */ + if (fFileNameLength != 0) { + dierr = pGFD->Write(fFileName, fFileNameLength); + if (dierr != kDIErrNone) + goto bail; + } + assert(fExtraFieldLength == 0); + +bail: + return dierr; +} + +/* + * Change the filename field. + */ +void OuterZip::LocalFileHeader::SetFileName(const char* name) +{ + delete[] fFileName; + fFileName = NULL; + fFileNameLength = 0; + + if (name != NULL) { + fFileNameLength = (uint16_t)strlen(name); + fFileName = new uint8_t[fFileNameLength+1]; + if (fFileName == NULL) { + LOGW("Malloc failure in SetFileName %u", fFileNameLength); + fFileName = NULL; + fFileNameLength = 0; + } else { + memcpy(fFileName, name, fFileNameLength); + fFileName[fFileNameLength] = '\0'; + LOGD("+++ OuterZip LFH filename set to '%s'", fFileName); + } + } +} + +/* + * Dump the contents of a LocalFileHeader object. + */ +void OuterZip::LocalFileHeader::Dump(void) const +{ + LOGI(" LocalFileHeader contents:"); + LOGI(" versToExt=%u gpBits=0x%04x compression=%u", + fVersionToExtract, fGPBitFlag, fCompressionMethod); + LOGI(" modTime=0x%04x modDate=0x%04x crc32=0x%08x", + fLastModFileTime, fLastModFileDate, fCRC32); + LOGI(" compressedSize=%u uncompressedSize=%u", + fCompressedSize, fUncompressedSize); + LOGI(" filenameLen=%u extraLen=%u", + fFileNameLength, fExtraFieldLength); +} + + +/* + * =================================== + * OuterZip::CentralDirEntry + * =================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "pGFD" should be positioned on the signature bytes for the + * entry. On exit, "pGFD" will point at the signature word for the next + * entry or for the EOCD. + */ +DIError OuterZip::CentralDirEntry::Read(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + uint8_t buf[kCDELen]; + + dierr = pGFD->Read(buf, kCDELen); + if (dierr != kDIErrNone) + goto bail; + + if (GetLongLE(&buf[0x00]) != kSignature) { + LOGI(" ZIP: whoops: didn't find expected signature"); + assert(false); + return kDIErrGeneric; + } + + fVersionMadeBy = GetShortLE(&buf[0x04]); + fVersionToExtract = GetShortLE(&buf[0x06]); + fGPBitFlag = GetShortLE(&buf[0x08]); + fCompressionMethod = GetShortLE(&buf[0x0a]); + fLastModFileTime = GetShortLE(&buf[0x0c]); + fLastModFileDate = GetShortLE(&buf[0x0e]); + fCRC32 = GetLongLE(&buf[0x10]); + fCompressedSize = GetLongLE(&buf[0x14]); + fUncompressedSize = GetLongLE(&buf[0x18]); + fFileNameLength = GetShortLE(&buf[0x1c]); + fExtraFieldLength = GetShortLE(&buf[0x1e]); + fFileCommentLength = GetShortLE(&buf[0x20]); + fDiskNumberStart = GetShortLE(&buf[0x22]); + fInternalAttrs = GetShortLE(&buf[0x24]); + fExternalAttrs = GetLongLE(&buf[0x26]); + fLocalHeaderRelOffset = GetLongLE(&buf[0x2a]); + + /* grab filename */ + if (fFileNameLength != 0) { + assert(fFileName == NULL); + fFileName = new uint8_t[fFileNameLength+1]; + if (fFileName == NULL) { + dierr = kDIErrMalloc; + goto bail; + } else { + dierr = pGFD->Read(fFileName, fFileNameLength); + fFileName[fFileNameLength] = '\0'; + } + if (dierr != kDIErrNone) + goto bail; + } + + /* skip over "extra field" */ + dierr = pGFD->Seek(fExtraFieldLength, kSeekCur); + if (dierr != kDIErrNone) + goto bail; + + /* grab comment, if any */ + if (fFileCommentLength != 0) { + assert(fFileComment == NULL); + fFileComment = new uint8_t[fFileCommentLength+1]; + if (fFileComment == NULL) { + dierr = kDIErrMalloc; + goto bail; + } else { + dierr = pGFD->Read(fFileComment, fFileCommentLength); + fFileComment[fFileCommentLength] = '\0'; + } + if (dierr != kDIErrNone) + goto bail; + } + +bail: + return dierr; +} + +/* + * Write a central dir entry. + */ +DIError OuterZip::CentralDirEntry::Write(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + uint8_t buf[kCDELen]; + + PutLongLE(&buf[0x00], kSignature); + PutShortLE(&buf[0x04], fVersionMadeBy); + PutShortLE(&buf[0x06], fVersionToExtract); + PutShortLE(&buf[0x08], fGPBitFlag); + PutShortLE(&buf[0x0a], fCompressionMethod); + PutShortLE(&buf[0x0c], fLastModFileTime); + PutShortLE(&buf[0x0e], fLastModFileDate); + PutLongLE(&buf[0x10], fCRC32); + PutLongLE(&buf[0x14], fCompressedSize); + PutLongLE(&buf[0x18], fUncompressedSize); + PutShortLE(&buf[0x1c], fFileNameLength); + PutShortLE(&buf[0x1e], fExtraFieldLength); + PutShortLE(&buf[0x20], fFileCommentLength); + PutShortLE(&buf[0x22], fDiskNumberStart); + PutShortLE(&buf[0x24], fInternalAttrs); + PutLongLE(&buf[0x26], fExternalAttrs); + PutLongLE(&buf[0x2a], fLocalHeaderRelOffset); + + dierr = pGFD->Write(buf, kCDELen); + if (dierr != kDIErrNone) + goto bail; + + /* write filename */ + if (fFileNameLength != 0) { + dierr = pGFD->Write(fFileName, fFileNameLength); + if (dierr != kDIErrNone) + goto bail; + } + assert(fExtraFieldLength == 0); + assert(fFileCommentLength == 0); + +bail: + return dierr; +} + +/* + * Change the filename field. + */ +void OuterZip::CentralDirEntry::SetFileName(const char* name) +{ + delete[] fFileName; + fFileName = NULL; + fFileNameLength = 0; + + if (name != NULL) { + fFileNameLength = (uint16_t)strlen(name); + fFileName = new uint8_t[fFileNameLength+1]; + if (fFileName == NULL) { + LOGI("Malloc failure in SetFileName %u", fFileNameLength); + fFileName = NULL; + fFileNameLength = 0; + } else { + memcpy(fFileName, name, fFileNameLength); + fFileName[fFileNameLength] = '\0'; + LOGI("+++ OuterZip CDE filename set to '%s'", fFileName); + } + } +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void OuterZip::CentralDirEntry::Dump(void) const +{ + LOGI(" CentralDirEntry contents:"); + LOGI(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u", + fVersionMadeBy, fVersionToExtract, fGPBitFlag, fCompressionMethod); + LOGI(" modTime=0x%04x modDate=0x%04x crc32=0x%08x", + fLastModFileTime, fLastModFileDate, fCRC32); + LOGI(" compressedSize=%u uncompressedSize=%u", + fCompressedSize, fUncompressedSize); + LOGI(" filenameLen=%u extraLen=%u commentLen=%u", + fFileNameLength, fExtraFieldLength, fFileCommentLength); + LOGI(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08x relOffset=%u", + fDiskNumberStart, fInternalAttrs, fExternalAttrs, + fLocalHeaderRelOffset); + + if (fFileName != NULL) { + LOGI(" filename: '%s'", fFileName); + } + if (fFileComment != NULL) { + LOGI(" comment: '%s'", fFileComment); + } +} + + +/* + * =================================== + * OuterZip::EndOfCentralDir + * =================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature. + */ +DIError OuterZip::EndOfCentralDir::ReadBuf(const uint8_t* buf, int len) +{ + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + LOGI(" Zip EOCD: expected >= %d bytes, found %d", + kEOCDLen, len); + return kDIErrBadArchiveStruct; + } + + if (GetLongLE(&buf[0x00]) != kSignature) + return kDIErrInternal; + + fDiskNumber = GetShortLE(&buf[0x04]); + fDiskWithCentralDir = GetShortLE(&buf[0x06]); + fNumEntries = GetShortLE(&buf[0x08]); + fTotalNumEntries = GetShortLE(&buf[0x0a]); + fCentralDirSize = GetLongLE(&buf[0x0c]); + fCentralDirOffset = GetLongLE(&buf[0x10]); + fCommentLen = GetShortLE(&buf[0x14]); + + return kDIErrNone; +} + +/* + * Write an end-of-central-directory section. + */ +DIError OuterZip::EndOfCentralDir::Write(GenericFD* pGFD) +{ + DIError dierr = kDIErrNone; + uint8_t buf[kEOCDLen]; + + PutLongLE(&buf[0x00], kSignature); + PutShortLE(&buf[0x04], fDiskNumber); + PutShortLE(&buf[0x06], fDiskWithCentralDir); + PutShortLE(&buf[0x08], fNumEntries); + PutShortLE(&buf[0x0a], fTotalNumEntries); + PutLongLE(&buf[0x0c], fCentralDirSize); + PutLongLE(&buf[0x10], fCentralDirOffset); + PutShortLE(&buf[0x14], fCommentLen); + + dierr = pGFD->Write(buf, kEOCDLen); + if (dierr != kDIErrNone) + goto bail; + + assert(fCommentLen == 0); + +bail: + return dierr; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void +OuterZip::EndOfCentralDir::Dump(void) const +{ + LOGI(" EndOfCentralDir contents:"); + LOGI(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u", + fDiskNumber, fDiskWithCentralDir, fNumEntries, fTotalNumEntries); + LOGI(" centDirSize=%u centDirOff=%u commentLen=%u", + fCentralDirSize, fCentralDirOffset, fCommentLen); +} diff --git a/diskimg/OzDOS.cpp b/diskimg/OzDOS.cpp new file mode 100644 index 0000000..9167a40 --- /dev/null +++ b/diskimg/OzDOS.cpp @@ -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; +} diff --git a/diskimg/Pascal.cpp b/diskimg/Pascal.cpp new file mode 100644 index 0000000..9af6f9f --- /dev/null +++ b/diskimg/Pascal.cpp @@ -0,0 +1,1863 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Implementation of DiskFSPascal class. + * + * Currently each file may only be open once. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + + +/* + * =========================================================================== + * DiskFSPascal + * =========================================================================== + */ + +const int kBlkSize = 512; +const int kVolHeaderBlock = 2; // first directory block +const int kMaxCatalogIterations = 64; // should be short, linear catalog +const int kHugeDir = 32; +static const char* kInvalidNameChars = "$=?,[#:"; + + +/* + * See if this looks like a Pascal 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 blkBuf[512]; + uint8_t volName[DiskFSPascal::kMaxVolumeName+1]; + + dierr = pImg->ReadBlockSwapped(kVolHeaderBlock, blkBuf, imageOrder, + DiskImg::kSectorOrderProDOS); + if (dierr != kDIErrNone) + goto bail; + + + if (!(blkBuf[0x00] == 0 && blkBuf[0x01] == 0) || + !(blkBuf[0x04] == 0 && blkBuf[0x05] == 0) || + !(blkBuf[0x06] > 0 && blkBuf[0x06] <= DiskFSPascal::kMaxVolumeName) || + 0) + { + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + /* volume name length is good, check the name itself */ + /* (this may be overly restrictive, but it's probably good to be) */ + memset(volName, 0, sizeof(volName)); + memcpy(volName, &blkBuf[0x07], blkBuf[0x06]); + if (!DiskFSPascal::IsValidVolumeName((const char*) volName)) + return kDIErrFilesystemNotFound; + +bail: + return dierr; +} + +/* + * Test to see if the image is a Pascal disk. + */ +/*static*/ DIError DiskFSPascal::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency) +{ + 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::kFormatPascal; + return kDIErrNone; + } + } + + LOGI(" Pascal 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 DiskFSPascal::Initialize(void) +{ + DIError dierr = kDIErrNone; + + fDiskIsGood = false; // hosed until proven innocent + fEarlyDamage = false; + + fVolumeUsage.Create(fpImg->GetNumBlocks()); + + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) + goto bail; + DumpVolHeader(); + + dierr = ProcessCatalog(); + if (dierr != kDIErrNone) + goto bail; + + dierr = ScanFileUsage(); + if (dierr != kDIErrNone) { + /* this might not be fatal; just means that *some* files are bad */ + goto bail; + } + + fDiskIsGood = CheckDiskIsGood(); + + fVolumeUsage.Dump(); + + //A2File* pFile; + //pFile = GetNextFile(NULL); + //while (pFile != NULL) { + // pFile->Dump(); + // pFile = GetNextFile(pFile); + //} + +bail: + return dierr; +} + +/* + * Read some interesting fields from the volume header. + */ +DIError DiskFSPascal::LoadVolHeader(void) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + int nameLen, maxFiles; + + dierr = fpImg->ReadBlock(kVolHeaderBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + /* vol header is same size as dir entry, but different layout */ + fStartBlock = GetShortLE(&blkBuf[0x00]); + assert(fStartBlock == 0); // verified in "TestImage" + fNextBlock = GetShortLE(&blkBuf[0x02]); + assert(GetShortLE(&blkBuf[0x04]) == 0); // type + nameLen = blkBuf[0x06] & 0x07; + memcpy(fVolumeName, &blkBuf[0x07], nameLen); + fVolumeName[nameLen] = '\0'; + fTotalBlocks = GetShortLE(&blkBuf[0x0e]); + fNumFiles = GetShortLE(&blkBuf[0x10]); + fAccessWhen = GetShortLE(&blkBuf[0x12]); // time of last access + fDateSetWhen = GetShortLE(&blkBuf[0x14]); // most recent date set + fStuff1 = GetShortLE(&blkBuf[0x16]); // filler + fStuff2 = GetShortLE(&blkBuf[0x18]); // filler + + if (fTotalBlocks != fpImg->GetNumBlocks()) { + // saw this most recently on a 40-track .APP image; not a problem + LOGI(" Pascal WARNING: total (%u) != img (%ld)", + fTotalBlocks, fpImg->GetNumBlocks()); + } + + /* + * Sanity checks. + */ + if (fNextBlock > 34) { + // directory really shouldn't be more than 6; I'm being generous + fpImg->AddNote(DiskImg::kNoteWarning, + "Pascal directory is too big (%d blocks); trimming.", + fNextBlock - fStartBlock); + } + + /* max #of file entries, including the vol dir header */ + maxFiles = ((fNextBlock - kVolHeaderBlock) * kBlkSize) / kDirectoryEntryLen; + if (fNumFiles > maxFiles-1) { + fpImg->AddNote(DiskImg::kNoteWarning, + "Pascal fNumFiles (%d) exceeds max files (%d); trimming.\n", + fNumFiles, maxFiles-1); + fEarlyDamage = true; + } + + SetVolumeID(); + +bail: + return dierr; +} + +/* + * Set the volume ID field. + */ +void DiskFSPascal::SetVolumeID(void) +{ + sprintf(fVolumeID, "Pascal %s:", fVolumeName); +} + +/* + * Dump what we pulled out of the volume header. + */ +void DiskFSPascal::DumpVolHeader(void) +{ + time_t access, dateSet; + + LOGI(" Pascal volume header for '%s'", fVolumeName); + LOGI(" startBlock=%d nextBlock=%d", + fStartBlock, fNextBlock); + LOGI(" totalBlocks=%d numFiles=%d access=0x%04x dateSet=0x%04x", + fTotalBlocks, fNumFiles, fAccessWhen, fDateSetWhen); + + access = A2FilePascal::ConvertPascalDate(fAccessWhen); + dateSet = A2FilePascal::ConvertPascalDate(fDateSetWhen); + LOGI(" -->access %.24s", ctime(&access)); + LOGI(" -->dateSet %.24s", ctime(&dateSet)); + + //LOGI("Unconvert access=0x%04x dateSet=0x%04x", + // A2FilePascal::ConvertPascalDate(access), + // A2FilePascal::ConvertPascalDate(dateSet)); +} + + +/* + * Read the catalog from the disk. + * + * No distinction is made for block boundaries, so we want to slurp the + * entire thing into memory. + * + * Sets "fDirectory". + */ +DIError DiskFSPascal::LoadCatalog(void) +{ + DIError dierr = kDIErrNone; + uint8_t* dirPtr; + int block, numBlocks; + + assert(fDirectory == NULL); + + numBlocks = fNextBlock - kVolHeaderBlock; + if (numBlocks <= 0 || numBlocks > kHugeDir) { + dierr = kDIErrBadDiskImage; + goto bail; + } + + fDirectory = new uint8_t[kBlkSize * numBlocks]; + if (fDirectory == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + block = kVolHeaderBlock; + dirPtr = fDirectory; + while (numBlocks--) { + dierr = fpImg->ReadBlock(block, dirPtr); + if (dierr != kDIErrNone) + goto bail; + + block++; + dirPtr += kBlkSize; + } + +bail: + if (dierr != kDIErrNone) { + delete[] fDirectory; + fDirectory = NULL; + } + return dierr; +} + +/* + * Write our copy of the catalog back out to disk. + */ +DIError DiskFSPascal::SaveCatalog(void) +{ + DIError dierr = kDIErrNone; + uint8_t* dirPtr; + int block, numBlocks; + + assert(fDirectory != NULL); + + numBlocks = fNextBlock - kVolHeaderBlock; + block = kVolHeaderBlock; + dirPtr = fDirectory; + while (numBlocks--) { + dierr = fpImg->WriteBlock(block, dirPtr); + if (dierr != kDIErrNone) + goto bail; + + block++; + dirPtr += kBlkSize; + } + +bail: + return dierr; +} + +/* + * Free the catalog storage. + */ +void DiskFSPascal::FreeCatalog(void) +{ + delete[] fDirectory; + fDirectory = NULL; +} + + +/* + * Process the catalog into A2File structures. + */ +DIError DiskFSPascal::ProcessCatalog(void) +{ + DIError dierr = kDIErrNone; + int i, nameLen; + A2FilePascal* pFile; + const uint8_t* dirPtr; + uint16_t prevNextBlock = fNextBlock; + + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + dirPtr = fDirectory + kDirectoryEntryLen; // skip vol dir entry + for (i = 0; i < fNumFiles; i++) { + pFile = new A2FilePascal(this); + + pFile->fStartBlock = GetShortLE(&dirPtr[0x00]); + pFile->fNextBlock = GetShortLE(&dirPtr[0x02]); + pFile->fFileType = (A2FilePascal::FileType) GetShortLE(&dirPtr[0x04]); + nameLen = dirPtr[0x06] & 0x0f; + memcpy(pFile->fFileName, &dirPtr[0x07], nameLen); + pFile->fFileName[nameLen] = '\0'; + pFile->fBytesRemaining = GetShortLE(&dirPtr[0x16]); + pFile->fModWhen = GetShortLE(&dirPtr[0x18]); + + /* check bytesRem before setting length field */ + if (pFile->fBytesRemaining > kBlkSize) { + LOGI(" Pascal found strange bytesRem %u on '%s', trimming", + pFile->fBytesRemaining, pFile->fFileName); + pFile->fBytesRemaining = kBlkSize; + pFile->SetQuality(A2File::kQualitySuspicious); + } + + pFile->fLength = pFile->fBytesRemaining + + (pFile->fNextBlock - pFile->fStartBlock -1) * kBlkSize; + + /* + * Check values. + */ + if (pFile->fStartBlock == pFile->fNextBlock) { + LOGI(" Pascal found zero-block file '%s'", pFile->fFileName); + pFile->SetQuality(A2File::kQualityDamaged); + } + if (pFile->fStartBlock < prevNextBlock) { + LOGI(" Pascal start of '%s' (%d) overlaps previous end (%d)", + pFile->fFileName, pFile->fStartBlock, prevNextBlock); + pFile->SetQuality(A2File::kQualityDamaged); + } + + if (pFile->fNextBlock > fpImg->GetNumBlocks()) { + LOGI(" Pascal invalid 'next' block %d (max %ld) '%s'", + pFile->fNextBlock, fpImg->GetNumBlocks(), pFile->fFileName); + pFile->fStartBlock = pFile->fNextBlock = 0; + pFile->fLength = 0; + pFile->SetQuality(A2File::kQualityDamaged); + } else if (pFile->fNextBlock > fTotalBlocks) { + LOGI(" Pascal 'next' block %d exceeds max (%d) '%s'", + pFile->fNextBlock, fTotalBlocks, pFile->fFileName); + pFile->SetQuality(A2File::kQualitySuspicious); + } + + //pFile->Dump(); + AddFileToList(pFile); + + dirPtr += kDirectoryEntryLen; + prevNextBlock = pFile->fNextBlock; + } + +bail: + FreeCatalog(); + return dierr; +} + + +/* + * Create the volume usage map. Since UCSD Pascal volumes have neither + * in-use maps nor index blocks, this is pretty straightforward. + */ +DIError DiskFSPascal::ScanFileUsage(void) +{ + int block; + + /* start with the boot blocks */ + SetBlockUsage(0, VolumeUsage::kChunkPurposeSystem); + SetBlockUsage(1, VolumeUsage::kChunkPurposeSystem); + + for (block = kVolHeaderBlock; block < fNextBlock; block++) { + SetBlockUsage(block, VolumeUsage::kChunkPurposeVolumeDir); + } + + A2FilePascal* pFile; + pFile = (A2FilePascal*) GetNextFile(NULL); + while (pFile != NULL) { + for (block = pFile->fStartBlock; block < pFile->fNextBlock; block++) + SetBlockUsage(block, VolumeUsage::kChunkPurposeUserData); + + pFile = (A2FilePascal*) GetNextFile(pFile); + } + + return kDIErrNone; +} + +/* + * Update an entry in the volume usage map. + */ +void DiskFSPascal::SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose) +{ + VolumeUsage::ChunkState cstate; + + fVolumeUsage.GetChunkState(block, &cstate); + if (cstate.isUsed) { + cstate.purpose = VolumeUsage::kChunkPurposeConflict; + LOGI(" Pascal conflicting uses for bl=%ld", block); + } else { + cstate.isUsed = true; + cstate.isMarkedUsed = true; + cstate.purpose = purpose; + } + fVolumeUsage.SetChunkState(block, &cstate); +} + + +/* + * Test a string for validity as a Pascal volume name. + * + * Volume names can only be 7 characters long, but otherwise obey the same + * rules as file names. + */ +/*static*/ bool DiskFSPascal::IsValidVolumeName(const char* name) +{ + if (name == NULL) { + assert(false); + return false; + } + if (strlen(name) > kMaxVolumeName) + return false; + return IsValidFileName(name); +} + +/* + * Test a string for validity as a Pascal file name. + * + * Filenames can be up to 15 characters long. All characters are valid. + * However, the system filer gets bent out of shape if you use spaces, + * control characters, any of the wildcards ($=?), or filer meta-characters + * (,[#:). It also converts all alpha characters to upper case, but we can + * take care of that later. + */ +/*static*/ bool DiskFSPascal::IsValidFileName(const char* name) +{ + assert(name != NULL); + + if (name[0] == '\0') + return false; + if (strlen(name) > A2FilePascal::kMaxFileName) + return false; + + /* must be A-Z 0-9 '.' */ + while (*name != '\0') { + if (*name <= 0x20 || *name >= 0x7f) // no space, del, or ctrl + return false; + //if (*name >= 'a' && *name <= 'z') // no lower case + // return false; + if (strchr(kInvalidNameChars, *name) != NULL) // filer metacharacters + return false; + + name++; + } + + return true; +} + +/* + * Put a Pascal filesystem image on the specified DiskImg. + */ +DIError DiskFSPascal::Format(DiskImg* pDiskImg, const char* volName) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + long formatBlocks; + + if (!IsValidVolumeName(volName)) + return kDIErrInvalidArg; + + /* set fpImg so calls that rely on it will work; we un-set it later */ + assert(fpImg == NULL); + SetDiskImg(pDiskImg); + + LOGI(" Pascal formatting disk image"); + + /* write ProDOS-style blocks */ + dierr = fpImg->OverrideFormat(fpImg->GetPhysicalFormat(), + DiskImg::kFormatGenericProDOSOrd, fpImg->GetSectorOrder()); + if (dierr != kDIErrNone) + goto bail; + + formatBlocks = pDiskImg->GetNumBlocks(); + if (formatBlocks != 280 && formatBlocks != 1600) { + LOGI(" Pascal: rejecting format req blocks=%ld", formatBlocks); + assert(false); + return kDIErrInvalidArg; + } + + /* + * We should now zero out the disk blocks, but this is done automatically + * on new disk images, so there's no need to do it here. + */ +// dierr = fpImg->ZeroImage(); + LOGI(" Pascal (not zeroing blocks)"); + + /* + * Start by writing blocks 0 and 1 (the boot blocks). The file + * APPLE3:FORMATTER.DATA holds images for 3.5" and 5.25" disks. + */ + dierr = WriteBootBlocks(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Write the disk volume entry. + */ + memset(blkBuf, 0, sizeof(blkBuf)); + PutShortLE(&blkBuf[0x00], 0); // start block + PutShortLE(&blkBuf[0x02], 6); // next block + PutShortLE(&blkBuf[0x04], 0); // "file" type + blkBuf[0x06] = (uint8_t)strlen(volName); + memcpy(&blkBuf[0x07], volName, strlen(volName)); + PutShortLE(&blkBuf[0x0e], (uint16_t) pDiskImg->GetNumBlocks()); + PutShortLE(&blkBuf[0x10], 0); // num files + PutShortLE(&blkBuf[0x12], 0); // last access date + PutShortLE(&blkBuf[0x14], 0xa87b); // last date set (Nov 7 1984) + dierr = fpImg->WriteBlock(kVolHeaderBlock, blkBuf); + if (dierr != kDIErrNone) { + LOGI(" Format: block %d write failed (err=%d)", + kVolHeaderBlock, dierr); + goto bail; + } + + /* check our work, and set some object fields, by reading what we wrote */ + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) { + LOGI(" GLITCH: couldn't read header we just wrote (err=%d)", dierr); + goto bail; + } + + +bail: + SetDiskImg(NULL); // shouldn't really be set by us + return dierr; +} + +/* + * Blocks 0 and 1 of a 5.25" bootable Pascal disk, formatted by + * APPLE3:FORMATTER from Pascal v1.3. + */ +const uint8_t gPascal525Block0[] = { + 0x01, 0xe0, 0x70, 0xb0, 0x04, 0xe0, 0x40, 0xb0, 0x39, 0xbd, 0x88, 0xc0, + 0x20, 0x20, 0x08, 0xa2, 0x00, 0xbd, 0x25, 0x08, 0x09, 0x80, 0x20, 0xfd, + 0xfb, 0xe8, 0xe0, 0x1d, 0xd0, 0xf3, 0xf0, 0xfe, 0xa9, 0x0a, 0x4c, 0x24, + 0xfc, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x42, 0x4f, 0x4f, 0x54, 0x20, 0x46, + 0x52, 0x4f, 0x4d, 0x20, 0x53, 0x4c, 0x4f, 0x54, 0x20, 0x34, 0x2c, 0x20, + 0x35, 0x20, 0x4f, 0x52, 0x20, 0x36, 0x8a, 0x85, 0x43, 0x4a, 0x4a, 0x4a, + 0x4a, 0x09, 0xc0, 0x85, 0x0d, 0xa9, 0x5c, 0x85, 0x0c, 0xad, 0x00, 0x08, + 0xc9, 0x06, 0xb0, 0x0a, 0x69, 0x02, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0x6c, + 0x0c, 0x00, 0xa9, 0x00, 0x8d, 0x78, 0x04, 0xa9, 0x0a, 0x85, 0x0e, 0xa9, + 0x80, 0x85, 0x3f, 0x85, 0x11, 0xa9, 0x00, 0x85, 0x10, 0xa9, 0x08, 0x85, + 0x02, 0xa9, 0x02, 0x85, 0x0f, 0xa9, 0x00, 0x20, 0x4c, 0x09, 0xa2, 0x4e, + 0xa0, 0x06, 0xb1, 0x10, 0xd9, 0x39, 0x09, 0xf0, 0x2b, 0x18, 0xa5, 0x10, + 0x69, 0x1a, 0x85, 0x10, 0x90, 0x02, 0xe6, 0x11, 0xca, 0xd0, 0xe9, 0xc6, + 0x0e, 0xd0, 0xcc, 0x20, 0x20, 0x08, 0xa6, 0x43, 0xbd, 0x88, 0xc0, 0xa2, + 0x00, 0xbd, 0x2a, 0x09, 0x09, 0x80, 0x20, 0xfd, 0xfb, 0xe8, 0xe0, 0x15, + 0xd0, 0xf3, 0xf0, 0xfe, 0xc8, 0xc0, 0x13, 0xd0, 0xc9, 0xad, 0x81, 0xc0, + 0xad, 0x81, 0xc0, 0xa9, 0xd0, 0x85, 0x3f, 0xa9, 0x30, 0x85, 0x02, 0xa0, + 0x00, 0xb1, 0x10, 0x85, 0x0f, 0xc8, 0xb1, 0x10, 0x20, 0x4c, 0x09, 0xad, + 0x89, 0xc0, 0xa9, 0xd0, 0x85, 0x3f, 0xa9, 0x10, 0x85, 0x02, 0xa0, 0x00, + 0xb1, 0x10, 0x18, 0x69, 0x18, 0x85, 0x0f, 0xc8, 0xb1, 0x10, 0x69, 0x00, + 0x20, 0x4c, 0x09, 0xa5, 0x43, 0xc9, 0x50, 0xf0, 0x08, 0x90, 0x1a, 0xad, + 0x80, 0xc0, 0x6c, 0xf8, 0xff, 0xa2, 0x00, 0x8e, 0xc4, 0xfe, 0xe8, 0x8e, + 0xc6, 0xfe, 0xe8, 0x8e, 0xb6, 0xfe, 0xe8, 0x8e, 0xb8, 0xfe, 0x4c, 0xfb, + 0x08, 0xa2, 0x00, 0x8e, 0xc0, 0xfe, 0xe8, 0x8e, 0xc2, 0xfe, 0xa2, 0x04, + 0x8e, 0xb6, 0xfe, 0xe8, 0x8e, 0xb8, 0xfe, 0x4c, 0xfb, 0x08, 0x4e, 0x4f, + 0x20, 0x46, 0x49, 0x4c, 0x45, 0x20, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, + 0x2e, 0x41, 0x50, 0x50, 0x4c, 0x45, 0x20, 0x0c, 0x53, 0x59, 0x53, 0x54, + 0x45, 0x4d, 0x2e, 0x41, 0x50, 0x50, 0x4c, 0x45, 0x4a, 0x08, 0xa5, 0x0f, + 0x29, 0x07, 0x0a, 0x85, 0x00, 0xa5, 0x0f, 0x28, 0x6a, 0x4a, 0x4a, 0x85, + 0xf0, 0xa9, 0x00, 0x85, 0x3e, 0x4c, 0x78, 0x09, 0xa6, 0x02, 0xf0, 0x22, + 0xc6, 0x02, 0xe6, 0x3f, 0xe6, 0x00, 0xa5, 0x00, 0x49, 0x10, 0xd0, 0x04, + 0x85, 0x00, 0xe6, 0xf0, 0xa4, 0x00, 0xb9, 0x8b, 0x09, 0x85, 0xf1, 0xa2, + 0x00, 0xe4, 0x02, 0xf0, 0x05, 0x20, 0x9b, 0x09, 0x90, 0xda, 0x60, 0x00, + 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x01, 0x03, 0x05, 0x07, 0x09, + 0x0b, 0x0d, 0x0f, 0xa6, 0x43, 0xa5, 0xf0, 0x0a, 0x0e, 0x78, 0x04, 0x20, + 0xa3, 0x0a, 0x4e, 0x78, 0x04, 0x20, 0x47, 0x0a, 0xb0, 0xfb, 0xa4, 0x2e, + 0x8c, 0x78, 0x04, 0xc4, 0xf0, 0xd0, 0xe6, 0xa5, 0x2d, 0xc5, 0xf1, 0xd0, + 0xec, 0x20, 0xdf, 0x09, 0xb0, 0xe7, 0x20, 0xc7, 0x09, 0x18, 0x60, 0xa0, + 0x00, 0xa2, 0x56, 0xca, 0x30, 0xfb, 0xb9, 0x00, 0x02, 0x5e, 0x00, 0x03, + 0x2a, 0x5e, 0x00, 0x03, 0x2a, 0x91, 0x3e, 0xc8, 0xd0, 0xed, 0x60, 0xa0, + 0x20, 0x88, 0xf0, 0x61, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x49, 0xd5, 0xd0, + 0xf4, 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xaa, 0xd0, 0xf2, 0xa0, + 0x56, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xad +}; +const uint8_t gPascal525Block1[] = { + 0xd0, 0xe7, 0xa9, 0x00, 0x88, 0x84, 0x26, 0xbc, 0x8c, 0xc0, 0x10, 0xfb, + 0x59, 0xd6, 0x02, 0xa4, 0x26, 0x99, 0x00, 0x03, 0xd0, 0xee, 0x84, 0x26, + 0xbc, 0x8c, 0xc0, 0x10, 0xfb, 0x59, 0xd6, 0x02, 0xa4, 0x26, 0x99, 0x00, + 0x02, 0xc8, 0xd0, 0xee, 0xbc, 0x8c, 0xc0, 0x10, 0xfb, 0xd9, 0xd6, 0x02, + 0xd0, 0x13, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xde, 0xd0, 0x0a, 0xea, + 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xaa, 0xf0, 0x5c, 0x38, 0x60, 0xa0, + 0xfc, 0x84, 0x26, 0xc8, 0xd0, 0x04, 0xe6, 0x26, 0xf0, 0xf3, 0xbd, 0x8c, + 0xc0, 0x10, 0xfb, 0xc9, 0xd5, 0xd0, 0xf0, 0xea, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0xc9, 0xaa, 0xd0, 0xf2, 0xa0, 0x03, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, + 0xc9, 0x96, 0xd0, 0xe7, 0xa9, 0x00, 0x85, 0x27, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0x2a, 0x85, 0x26, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x25, 0x26, 0x99, + 0x2c, 0x00, 0x45, 0x27, 0x88, 0x10, 0xe7, 0xa8, 0xd0, 0xb7, 0xbd, 0x8c, + 0xc0, 0x10, 0xfb, 0xc9, 0xde, 0xd0, 0xae, 0xea, 0xbd, 0x8c, 0xc0, 0x10, + 0xfb, 0xc9, 0xaa, 0xd0, 0xa4, 0x18, 0x60, 0x86, 0x2b, 0x85, 0x2a, 0xcd, + 0x78, 0x04, 0xf0, 0x48, 0xa9, 0x00, 0x85, 0x26, 0xad, 0x78, 0x04, 0x85, + 0x27, 0x38, 0xe5, 0x2a, 0xf0, 0x37, 0xb0, 0x07, 0x49, 0xff, 0xee, 0x78, + 0x04, 0x90, 0x05, 0x69, 0xfe, 0xce, 0x78, 0x04, 0xc5, 0x26, 0x90, 0x02, + 0xa5, 0x26, 0xc9, 0x0c, 0xb0, 0x01, 0xa8, 0x20, 0xf4, 0x0a, 0xb9, 0x15, + 0x0b, 0x20, 0x04, 0x0b, 0xa5, 0x27, 0x29, 0x03, 0x0a, 0x05, 0x2b, 0xaa, + 0xbd, 0x80, 0xc0, 0xb9, 0x21, 0x0b, 0x20, 0x04, 0x0b, 0xe6, 0x26, 0xd0, + 0xbf, 0x20, 0x04, 0x0b, 0xad, 0x78, 0x04, 0x29, 0x03, 0x0a, 0x05, 0x2b, + 0xaa, 0xbd, 0x81, 0xc0, 0xa6, 0x2b, 0x60, 0xea, 0xa2, 0x11, 0xca, 0xd0, + 0xfd, 0xe6, 0x46, 0xd0, 0x02, 0xe6, 0x47, 0x38, 0xe9, 0x01, 0xd0, 0xf0, + 0x60, 0x01, 0x30, 0x28, 0x24, 0x20, 0x1e, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x70, 0x2c, 0x26, 0x22, 0x1f, 0x1e, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, + 0x41, 0x50, 0x50, 0x4c, 0x45, 0x20, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, + 0x45, 0x52, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x2e, 0x2c, 0x20, 0x31, 0x39, + 0x38, 0x34, 0x2c, 0x20, 0x31, 0x39, 0x38, 0x35, 0x20, 0x43, 0x2e, 0x4c, + 0x45, 0x55, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb +}; + +/* + * Block 0 of a 3.5" bootable Pascal disk, formatted by + * APPLE3:FORMATTER from Pascal v1.3. Block 1 is zeroed out. + */ +const uint8_t gPascal35Block0[] = { + 0x01, 0xe0, 0x70, 0xb0, 0x04, 0xe0, 0x40, 0xb0, 0x39, 0xbd, 0x88, 0xc0, + 0x20, 0x20, 0x08, 0xa2, 0x00, 0xbd, 0x25, 0x08, 0x09, 0x80, 0x20, 0xfd, + 0xfb, 0xe8, 0xe0, 0x1d, 0xd0, 0xf3, 0xf0, 0xfe, 0xa9, 0x0a, 0x4c, 0x24, + 0xfc, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x42, 0x4f, 0x4f, 0x54, 0x20, 0x46, + 0x52, 0x4f, 0x4d, 0x20, 0x53, 0x4c, 0x4f, 0x54, 0x20, 0x34, 0x2c, 0x20, + 0x35, 0x20, 0x4f, 0x52, 0x20, 0x36, 0x8a, 0x85, 0x43, 0x4a, 0x4a, 0x4a, + 0x4a, 0x09, 0xc0, 0x85, 0x15, 0x8d, 0x5d, 0x09, 0xa9, 0x00, 0x8d, 0x78, + 0x04, 0x85, 0x14, 0xa9, 0x0a, 0x85, 0x0e, 0xa9, 0x80, 0x85, 0x13, 0x85, + 0x11, 0xa9, 0x00, 0x85, 0x10, 0x85, 0x0b, 0xa9, 0x02, 0x85, 0x0a, 0xa9, + 0x04, 0x85, 0x02, 0x20, 0x40, 0x09, 0xa2, 0x4e, 0xa0, 0x06, 0xb1, 0x10, + 0xd9, 0x2d, 0x09, 0xf0, 0x2b, 0x18, 0xa5, 0x10, 0x69, 0x1a, 0x85, 0x10, + 0x90, 0x02, 0xe6, 0x11, 0xca, 0xd0, 0xe9, 0xc6, 0x0e, 0xd0, 0xcc, 0x20, + 0x20, 0x08, 0xa6, 0x43, 0xbd, 0x88, 0xc0, 0xa2, 0x00, 0xbd, 0x1e, 0x09, + 0x09, 0x80, 0x20, 0xfd, 0xfb, 0xe8, 0xe0, 0x15, 0xd0, 0xf3, 0xf0, 0xfe, + 0xc8, 0xc0, 0x13, 0xd0, 0xc9, 0xad, 0x83, 0xc0, 0xad, 0x83, 0xc0, 0xa9, + 0xd0, 0x85, 0x13, 0xa0, 0x00, 0xb1, 0x10, 0x85, 0x0a, 0xc8, 0xb1, 0x10, + 0x85, 0x0b, 0xa9, 0x18, 0x85, 0x02, 0x20, 0x40, 0x09, 0xad, 0x8b, 0xc0, + 0xa9, 0xd0, 0x85, 0x13, 0xa0, 0x00, 0xb1, 0x10, 0x18, 0x69, 0x18, 0x85, + 0x0a, 0xc8, 0xb1, 0x10, 0x69, 0x00, 0x85, 0x0b, 0xa9, 0x08, 0x85, 0x02, + 0x20, 0x40, 0x09, 0xa5, 0x43, 0xc9, 0x50, 0xf0, 0x08, 0x90, 0x1a, 0xad, + 0x80, 0xc0, 0x6c, 0xf8, 0xff, 0xa2, 0x00, 0x8e, 0xc4, 0xfe, 0xe8, 0x8e, + 0xc6, 0xfe, 0xe8, 0x8e, 0xb6, 0xfe, 0xe8, 0x8e, 0xb8, 0xfe, 0x4c, 0xef, + 0x08, 0xa2, 0x00, 0x8e, 0xc0, 0xfe, 0xe8, 0x8e, 0xc2, 0xfe, 0xa2, 0x04, + 0x8e, 0xb6, 0xfe, 0xe8, 0x8e, 0xb8, 0xfe, 0x4c, 0xef, 0x08, 0x4e, 0x4f, + 0x20, 0x46, 0x49, 0x4c, 0x45, 0x20, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, + 0x2e, 0x41, 0x50, 0x50, 0x4c, 0x45, 0x20, 0x0c, 0x53, 0x59, 0x53, 0x54, + 0x45, 0x4d, 0x2e, 0x41, 0x50, 0x50, 0x4c, 0x45, 0xa9, 0x01, 0x85, 0x42, + 0xa0, 0xff, 0xb1, 0x14, 0x8d, 0x5c, 0x09, 0xa9, 0x00, 0x85, 0x44, 0xa5, + 0x13, 0x85, 0x45, 0xa5, 0x0a, 0x85, 0x46, 0xa5, 0x0b, 0x85, 0x47, 0x20, + 0x00, 0x00, 0x90, 0x03, 0x4c, 0x5b, 0x08, 0xc6, 0x02, 0xf0, 0x0c, 0xe6, + 0x13, 0xe6, 0x13, 0xe6, 0x0a, 0xd0, 0xdc, 0xe6, 0x0b, 0xd0, 0xd8, 0x60, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x41, + 0x50, 0x50, 0x4c, 0x45, 0x20, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x45, + 0x52, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x2e, 0x2c, 0x20, 0x31, 0x39, 0x38, + 0x34, 0x2c, 0x20, 0x31, 0x39, 0x38, 0x35, 0x20, 0x43, 0x2e, 0x4c, 0x45, + 0x55, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * Write the Pascal boot blocks onto the disk image. + */ +DIError DiskFSPascal::WriteBootBlocks(void) +{ + DIError dierr; + uint8_t block0[512]; + uint8_t block1[512]; + bool is525 = false; + + assert(fpImg->GetHasBlocks()); + if (fpImg->GetNumBlocks() == 280) + is525 = true; + else if (fpImg->GetNumBlocks() == 1600) + is525 = false; + else { + LOGI(" Pascal boot blocks for blocks=%ld unknown", + fpImg->GetNumBlocks()); + return kDIErrInternal; + } + + if (is525) { + memcpy(block0, gPascal525Block0, sizeof(block0)); + memcpy(block1, gPascal525Block1, sizeof(block1)); + } else { + memcpy(block0, gPascal35Block0, sizeof(block0)); + memset(block1, 0, sizeof(block1)); + } + + dierr = fpImg->WriteBlock(0, block0); + if (dierr != kDIErrNone) { + LOGI(" WriteBootBlocks: block0 write failed (err=%d)", dierr); + return dierr; + } + dierr = fpImg->WriteBlock(1, block1); + if (dierr != kDIErrNone) { + LOGI(" WriteBootBlocks: block1 write failed (err=%d)", dierr); + return dierr; + } + + return kDIErrNone; +} + +/* + * 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 DiskFSPascal::CheckDiskIsGood(void) +{ + //DIError dierr; + bool result = true; + + if (fEarlyDamage) + result = false; + + /* (don't need to check to see if the boot blocks or disk catalog are + marked in use -- the directory is defined by the set of blocks + in the volume header) */ + + /* + * 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; +} + +/* + * Run through the list of files and count up the free blocks. + */ +DIError DiskFSPascal::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const +{ + A2FilePascal* pFile; + long freeBlocks = 0; + uint16_t prevNextBlock = fNextBlock; + + pFile = (A2FilePascal*) GetNextFile(NULL); + while (pFile != NULL) { + freeBlocks += pFile->fStartBlock - prevNextBlock; + prevNextBlock = pFile->fNextBlock; + + pFile = (A2FilePascal*) GetNextFile(pFile); + } + freeBlocks += fTotalBlocks - prevNextBlock; + + *pTotalUnits = fTotalBlocks; + *pFreeUnits = freeBlocks; + *pUnitSize = kBlockSize; + + return kDIErrNone; +} + + +/* + * Normalize a Pascal path. Used when adding files from DiskArchive. + * + * "*pNormalizedBufLen" is used to pass in the length of the buffer and + * pass out the length of the string (should the buffer prove inadequate). + */ +DIError DiskFSPascal::NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) +{ + DIError dierr = kDIErrNone; + char tmpBuf[A2FilePascal::kMaxFileName+1]; + int len; + + DoNormalizePath(path, fssep, tmpBuf); + len = strlen(tmpBuf)+1; + + if (*pNormalizedBufLen < len) + dierr = kDIErrDataOverrun; + else + strcpy(normalizedBuf, tmpBuf); + *pNormalizedBufLen = len; + + return dierr; +} + +/* + * Normalize a Pascal pathname. Lower case becomes upper case, invalid + * characters get stripped. + * + * "outBuf" must be able to hold kMaxFileName+1 characters. + */ +void DiskFSPascal::DoNormalizePath(const char* name, char fssep, char* outBuf) +{ + char* outp = outBuf; + const char* cp; + + /* throw out leading pathname, if any */ + if (fssep != '\0') { + cp = strrchr(name, fssep); + if (cp != NULL) + name = cp+1; + } + + while (*name != '\0' && (outp - outBuf) < A2FilePascal::kMaxFileName) { + if (*name > 0x20 && *name < 0x7f && + strchr(kInvalidNameChars, *name) == NULL) + { + *outp++ = toupper(*name); + } + + name++; + } + + *outp = '\0'; + + if (*outBuf == '\0') { + /* nothing left */ + strcpy(outBuf, "BLANK"); + } +} + + +/* + * Create an empty file. It doesn't look like pascal normally allows you + * to create a zero-block file, so we create a 1-block file and set the + * "data in last block" field to zero. + * + * We don't know how big the file will be, so we can't do a "best fit" + * algorithm for placement. Instead, we just put it in the largest + * available free space. + * + * NOTE: the Pascal system will auto-delete zero-byte files. It expects a + * brand-new 1-block file to have a "bytes remaining" of 512. The files + * we create here are expected to be written to, not used as filler, so + * this behavior is actually a *good* thing. + */ +DIError DiskFSPascal::CreateFile(const CreateParms* pParms, A2File** ppNewFile) +{ + DIError dierr = kDIErrNone; + const bool createUnique = (GetParameter(kParm_CreateUnique) != 0); + char normalName[A2FilePascal::kMaxFileName+1]; + A2FilePascal* pNewFile = NULL; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + assert(pParms != NULL); + assert(pParms->pathName != NULL); + assert(pParms->storageType == A2FileProDOS::kStorageSeedling); + LOGI(" Pascal ---v--- CreateFile '%s'", pParms->pathName); + + /* compute maxFiles, which includes the vol dir header */ + int maxFiles = + ((fNextBlock - kVolHeaderBlock) * kBlkSize) / kDirectoryEntryLen; + if (fNumFiles >= maxFiles-1) { + LOGI("Pascal volume directory full (%d entries)", fNumFiles); + return kDIErrVolumeDirFull; + } + + *ppNewFile = NULL; + + DoNormalizePath(pParms->pathName, pParms->fssep, normalName); + + /* + * See if the file already exists. + * + * If "create unique" is set, we append digits until the name doesn't + * match any others. The name will be modified in place. + */ + if (createUnique) { + MakeFileNameUnique(normalName); + } else { + if (GetFileByName(normalName) != NULL) { + LOGI(" Pascal create: normalized name '%s' already exists", + normalName); + dierr = kDIErrFileExists; + goto bail; + } + } + + /* + * Find the largest gap in the file space. + * + * We get an index pointer and A2File pointer to the previous entry. If + * the blank space is at the head of the list, prevIdx will be zero and + * pPrevFile will be NULL. + */ + A2FilePascal* pPrevFile; + int prevIdx; + + dierr = FindLargestFreeArea(&prevIdx, &pPrevFile); + if (dierr != kDIErrNone) + goto bail; + assert(prevIdx >= 0); + + /* + * Make a new entry. + */ + time_t now; + pNewFile = new A2FilePascal(this); + if (pNewFile == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + if (pPrevFile == NULL) + pNewFile->fStartBlock = fNextBlock; + else + pNewFile->fStartBlock = pPrevFile->fNextBlock; + pNewFile->fNextBlock = pNewFile->fStartBlock +1; // alloc 1 block + pNewFile->fFileType = A2FilePascal::ConvertFileType(pParms->fileType); + memset(pNewFile->fFileName, 0, A2FilePascal::kMaxFileName); + strcpy(pNewFile->fFileName, normalName); + pNewFile->fBytesRemaining = 0; + now = time(NULL); + pNewFile->fModWhen = A2FilePascal::ConvertPascalDate(now); + + pNewFile->fLength = 0; + + /* + * Make a hole. + */ + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + if (fNumFiles > prevIdx) { + LOGI(" Pascal sliding last %d entries down a slot", + fNumFiles - prevIdx); + memmove(fDirectory + (prevIdx+2) * kDirectoryEntryLen, + fDirectory + (prevIdx+1) * kDirectoryEntryLen, + (fNumFiles - prevIdx) * kDirectoryEntryLen); + } + + /* + * Fill the hole. + */ + uint8_t* dirPtr; + dirPtr = fDirectory + (prevIdx+1) * kDirectoryEntryLen; + PutShortLE(&dirPtr[0x00], pNewFile->fStartBlock); + PutShortLE(&dirPtr[0x02], pNewFile->fNextBlock); + PutShortLE(&dirPtr[0x04], (uint16_t) pNewFile->fFileType); + dirPtr[0x06] = (uint8_t) strlen(pNewFile->fFileName); + memcpy(&dirPtr[0x07], pNewFile->fFileName, A2FilePascal::kMaxFileName); + PutShortLE(&dirPtr[0x16], pNewFile->fBytesRemaining); + PutShortLE(&dirPtr[0x18], pNewFile->fModWhen); + + /* + * Update the #of files. + */ + fNumFiles++; + PutShortLE(&fDirectory[0x10], fNumFiles); + + /* + * Flush. + */ + dierr = SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Add to the linear file list. + */ + InsertFileInList(pNewFile, pPrevFile); + + *ppNewFile = pNewFile; + pNewFile = NULL; + +bail: + delete pNewFile; + FreeCatalog(); + return dierr; +} + +/* + * Make the name pointed to by "fileName" unique. The name should already + * be FS-normalized, and be in a buffer that can hold at least kMaxFileName+1 + * bytes. + * + * (This is nearly identical to the code in the ProDOS implementation. I'd + * like to make it a general DiskFS function, but making the loop condition + * work requires setting up callbacks, which isn't hard here but is a little + * annoying in ProDOS because of the subdir buffer. So it's cut & paste + * for now.) + * + * Returns an error on failure, which should be impossible. + */ +DIError DiskFSPascal::MakeFileNameUnique(char* fileName) +{ + assert(fileName != NULL); + assert(strlen(fileName) <= A2FilePascal::kMaxFileName); + + if (GetFileByName(fileName) == NULL) + return kDIErrNone; + + LOGI(" Pascal found duplicate of '%s', making unique", fileName); + + int nameLen = strlen(fileName); + int dotOffset=0, dotLen=0; + char dotBuf[kMaxExtensionLen+1]; + + /* ensure the result will be null-terminated */ + memset(fileName + nameLen, 0, (A2FilePascal::kMaxFileName - nameLen) +1); + + /* + * If this has what looks like a filename extension, grab it. We want + * to preserve ".gif", ".c", etc. + */ + const char* cp = strrchr(fileName, '.'); + if (cp != NULL) { + int tmpOffset = cp - fileName; + if (tmpOffset > 0 && nameLen - tmpOffset <= kMaxExtensionLen) { + LOGI(" Pascal (keeping extension '%s')", cp); + assert(strlen(cp) <= kMaxExtensionLen); + strcpy(dotBuf, cp); + dotOffset = tmpOffset; + dotLen = nameLen - dotOffset; + } + } + + const int kMaxDigits = 999; + int digits = 0; + int digitLen; + int copyOffset; + char digitBuf[4]; + do { + if (digits == kMaxDigits) + return kDIErrFileExists; + digits++; + + /* not the most efficient way to do this, but it'll do */ + sprintf(digitBuf, "%d", digits); + digitLen = strlen(digitBuf); + if (nameLen + digitLen > A2FilePascal::kMaxFileName) + copyOffset = A2FilePascal::kMaxFileName - dotLen - digitLen; + else + copyOffset = nameLen - dotLen; + memcpy(fileName + copyOffset, digitBuf, digitLen); + if (dotLen != 0) + memcpy(fileName + copyOffset + digitLen, dotBuf, dotLen); + } while (GetFileByName(fileName) != NULL); + + LOGI(" Pascal converted to unique name: %s", fileName); + + return kDIErrNone; +} + +/* + * Find the largest chunk of free space on the disk. + * + * Returns the index to the directory entry of the file immediately before + * the chunk (where 0 is the directory header), and the corresponding + * A2File entry. + * + * If there's no free space left, returns kDIErrDiskFull. + */ +DIError DiskFSPascal::FindLargestFreeArea(int *pPrevIdx, A2FilePascal** ppPrevFile) +{ + A2FilePascal* pFile; + A2FilePascal* pPrevFile; + uint16_t prevNextBlock = fNextBlock; + int gapSize, maxGap, maxIndex, idx; + + maxIndex = -1; + maxGap = 0; + idx = 0; + *ppPrevFile = pPrevFile = NULL; + + pFile = (A2FilePascal*) GetNextFile(NULL); + while (pFile != NULL) { + gapSize = pFile->fStartBlock - prevNextBlock; + if (gapSize > maxGap) { + maxGap = gapSize; + maxIndex = idx; + *ppPrevFile = pPrevFile; + } + + idx++; + prevNextBlock = pFile->fNextBlock; + pPrevFile = pFile; + pFile = (A2FilePascal*) GetNextFile(pFile); + } + + gapSize = fTotalBlocks - prevNextBlock; + if (gapSize > maxGap) { + maxGap = gapSize; + maxIndex = idx; + *ppPrevFile = pPrevFile; + } + + LOGI("Pascal largest gap after entry %d '%s' (size=%d)", + maxIndex, + *ppPrevFile != NULL ? (*ppPrevFile)->GetPathName() : "(root)", + maxGap); + *pPrevIdx = maxIndex; + + if (maxIndex < 0) + return kDIErrDiskFull; + return kDIErrNone; +} + +/* + * Delete a file. Because Pascal doesn't have a block allocation map, this + * is a simple matter of crunching the directory entry out. + */ +DIError DiskFSPascal::DeleteFile(A2File* pGenericFile) +{ + DIError dierr = kDIErrNone; + A2FilePascal* pFile = (A2FilePascal*) pGenericFile; + uint8_t* pEntry; + int dirLen, offsetToNextEntry; + + if (pGenericFile == NULL) { + assert(false); + return kDIErrInvalidArg; + } + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + if (pGenericFile->IsFileOpen()) + return kDIErrFileOpen; + + LOGI(" Pascal deleting '%s'", pFile->GetPathName()); + + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + pEntry = FindDirEntry(pFile); + if (pEntry == NULL) { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + dirLen = (fNumFiles+1) * kDirectoryEntryLen; + offsetToNextEntry = (pEntry - fDirectory) + kDirectoryEntryLen; + if (dirLen == offsetToNextEntry) { + LOGI("+++ removing last entry"); + } else { + memmove(pEntry, pEntry+kDirectoryEntryLen, dirLen - offsetToNextEntry); + } + + assert(fNumFiles > 0); + fNumFiles--; + PutShortLE(&fDirectory[0x10], fNumFiles); + + dierr = SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Remove the A2File* from the list. + */ + DeleteFileFromList(pFile); + +bail: + FreeCatalog(); + return dierr; +} + +/* + * Rename a file. + */ +DIError DiskFSPascal::RenameFile(A2File* pGenericFile, const char* newName) +{ + DIError dierr = kDIErrNone; + A2FilePascal* pFile = (A2FilePascal*) pGenericFile; + char normalName[A2FilePascal::kMaxFileName+1]; + uint8_t* pEntry; + + if (pFile == NULL || newName == NULL) + return kDIErrInvalidArg; + if (!IsValidFileName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + /* not strictly necessary, but watch sanity check in Close/FindDirEntry */ + if (pGenericFile->IsFileOpen()) + return kDIErrFileOpen; + + DoNormalizePath(newName, '\0', normalName); + + LOGI(" Pascal renaming '%s' to '%s'", pFile->GetPathName(), normalName); + + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + pEntry = FindDirEntry(pFile); + if (pEntry == NULL) { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + pEntry[0x06] = (uint8_t)strlen(normalName); + memcpy(&pEntry[0x07], normalName, A2FilePascal::kMaxFileName); + strcpy(pFile->fFileName, normalName); + + dierr = SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + +bail: + FreeCatalog(); + return dierr; +} + +/* + * Set file info. + * + * Pascal does not have an aux type or access flags. It has a file type, + * but we don't allow the full range of ProDOS types. Attempting to change + * to an unsupported type results in "PDA" being used. + */ +DIError DiskFSPascal::SetFileInfo(A2File* pGenericFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) +{ + DIError dierr = kDIErrNone; + A2FilePascal* pFile = (A2FilePascal*) pGenericFile; + uint8_t* pEntry; + + if (pFile == NULL) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + LOGI("Pascal SetFileInfo '%s' fileType=0x%04x", + pFile->GetPathName(), fileType); + + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + pEntry = FindDirEntry(pFile); + if (pEntry == NULL) { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + A2FilePascal::FileType newType; + + newType = A2FilePascal::ConvertFileType(fileType); + PutShortLE(&pEntry[0x04], (uint16_t) newType); + + dierr = SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + + /* update our local copy */ + pFile->fFileType = newType; + +bail: + FreeCatalog(); + return dierr; +} + +/* + * Change the Pascal volume name. + */ +DIError DiskFSPascal::RenameVolume(const char* newName) +{ + DIError dierr = kDIErrNone; + char normalName[A2FilePascal::kMaxFileName+1]; + + if (!IsValidVolumeName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + DoNormalizePath(newName, '\0', normalName); + + dierr = LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + fDirectory[0x06] = (uint8_t)strlen(normalName); + memcpy(&fDirectory[0x07], normalName, fDirectory[0x06]); + strcpy(fVolumeName, normalName); + + SetVolumeID(); + + dierr = SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + +bail: + FreeCatalog(); + return dierr; +} + + +/* + * Find "pFile" in "fDirectory". + */ +uint8_t* DiskFSPascal::FindDirEntry(A2FilePascal* pFile) +{ + uint8_t* ptr; + int i; + + assert(fDirectory != NULL); + + ptr = fDirectory; // volume header; first iteration skips over it + for (i = 0; i < fNumFiles; i++) { + ptr += kDirectoryEntryLen; + + if (GetShortLE(&ptr[0x00]) == pFile->fStartBlock) { + if (memcmp(&ptr[0x07], pFile->fFileName, ptr[0x06]) != 0) { + assert(false); + LOGI("name/block mismatch on '%s' %d", + pFile->GetPathName(), pFile->fStartBlock); + return NULL; + } + return ptr; + } + } + + return NULL; +} + + +/* + * =========================================================================== + * A2FilePascal + * =========================================================================== + */ + +/* + * Convert Pascal file type to ProDOS file type. + */ +uint32_t A2FilePascal::GetFileType(void) const +{ + switch (fFileType) { + case kTypeUntyped: return 0x00; // NON + case kTypeXdsk: return 0x01; // BAD (was 0xf2 in v1.2.2) + case kTypeCode: return 0x02; // PCD + case kTypeText: return 0x03; // PTX + case kTypeInfo: return 0xf3; // no idea + case kTypeData: return 0x05; // PDA + case kTypeGraf: return 0xf4; // no idea + case kTypeFoto: return 0x08; // FOT + case kTypeSecurdir: return 0xf5; // no idea + default: + LOGI("Pascal WARNING: found invalid file type %d", fFileType); + return 0; + } +} + +/* + * Convert a ProDOS file type to a Pascal file type. + */ +/*static*/ A2FilePascal::FileType A2FilePascal::ConvertFileType(long prodosType) +{ + FileType newType; + + switch (prodosType) { + case 0x00: newType = kTypeUntyped; break; // NON + case 0x01: newType = kTypeXdsk; break; // BAD + case 0x02: newType = kTypeCode; break; // PCD + case 0x03: newType = kTypeText; break; // PTX + case 0xf3: newType = kTypeInfo; break; // ? + case 0x05: newType = kTypeData; break; // PDA + case 0xf4: newType = kTypeGraf; break; // ? + case 0x08: newType = kTypeFoto; break; // FOT + case 0xf5: newType = kTypeSecurdir; break; // ? + default: newType = kTypeData; break; // PDA for generic + } + + return newType; +} + + +/* + * Convert from Pascal compact date format to a time_t. + * + * Format yyyyyyydddddmmmm + * Month 0..12 (0 indicates invalid date) + * Day 0..31 + * Year 0..100 (1900-1999; 100 will be rejected) + * + * We follow the ProDOS protocol of "year < 40 == 1900 + year". We could + * probably make that 1970, but the time_t epoch ends before then. + * + * The Pascal Filer uses a special date with the year 100 in it to indicate + * file updates in progress. If the system comes up and sees a file with + * the year 100, it will assume that the file was created shortly before the + * system crashed, and will remove the file. + */ +/*static*/ time_t A2FilePascal::ConvertPascalDate(PascalDate pascalDate) +{ + int year, month, day; + + month = pascalDate & 0x0f; + if (!month) + return 0; + day = (pascalDate >> 4) & 0x1f; + year = (pascalDate >> 9) & 0x7f; + if (year == 100) { + // ought to mark the file as "suspicious"? + LOGI("Pascal WARNING: date with year=100"); + } + if (year < 40) + year += 100; + + struct tm tmbuf; + time_t when; + + tmbuf.tm_sec = 0; + tmbuf.tm_min = 0; + tmbuf.tm_hour = 0; + tmbuf.tm_mday = day; + tmbuf.tm_mon = month-1; + tmbuf.tm_year = year; + tmbuf.tm_wday = 0; + tmbuf.tm_yday = 0; + tmbuf.tm_isdst = -1; // let it figure DST and time zone + when = mktime(&tmbuf); + + if (when == (time_t) -1) + when = 0; + return when; +} + +/* + * Convert a time_t to a Pascal-format date. + * + * CiderPress uses kDateInvalid==-1 and kDateNone==-2. + */ +/*static*/ A2FilePascal::PascalDate A2FilePascal::ConvertPascalDate(time_t unixDate) +{ + uint32_t date, year; + struct tm* ptm; + + if (unixDate == 0 || unixDate == -1 || unixDate == -2) + return 0; + + ptm = localtime(&unixDate); + if (ptm == NULL) + return 0; // must've been invalid or unspecified + + year = ptm->tm_year; // years since 1900 + if (year >= 100) + year -= 100; + if (year < 0 || year >= 100) { + LOGW("WHOOPS: got year %u from %d", year, ptm->tm_year); + year = 70; + } + date = year << 9 | (ptm->tm_mon+1) | ptm->tm_mday << 4; + return (PascalDate) date; +} + + +/* + * Return the file modification date. + */ +time_t A2FilePascal::GetModWhen(void) const +{ + return ConvertPascalDate(fModWhen); +} + +/* + * Dump the contents of the A2File structure. + */ +void A2FilePascal::Dump(void) const +{ + LOGI("A2FilePascal '%s'", fFileName); + LOGI(" start=%d next=%d type=%d", + fStartBlock, fNextBlock, fFileType); + LOGI(" bytesRem=%d modWhen=0x%04x", + fBytesRemaining, fModWhen); +} + +/* + * Not a whole lot to do, since there's no fancy index blocks. + */ +DIError A2FilePascal::Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork /*=false*/) +{ + A2FDPascal* pOpenFile = NULL; + + if (!readOnly) { + if (fpDiskFS->GetDiskImg()->GetReadOnly()) + return kDIErrAccessDenied; + if (fpDiskFS->GetFSDamaged()) + return kDIErrBadDiskImage; + } + if (fpOpenFile != NULL) + return kDIErrAlreadyOpen; + if (rsrcFork) + return kDIErrForkNotFound; + + pOpenFile = new A2FDPascal(this); + + pOpenFile->fOffset = 0; + pOpenFile->fOpenEOF = fLength; + pOpenFile->fOpenBlocksUsed = fNextBlock - fStartBlock; + pOpenFile->fModified = false; + + fpOpenFile = pOpenFile; + *ppOpenFile = pOpenFile; + + return kDIErrNone; +} + + +/* + * =========================================================================== + * A2FDPascal + * =========================================================================== + */ + +/* + * Read a chunk of data from the current offset. + */ +DIError A2FDPascal::Read(void* buf, size_t len, size_t* pActual) +{ + LOGD(" Pascal reading %lu bytes from '%s' (offset=%ld)", + (unsigned long) len, fpFile->GetPathName(), (long) fOffset); + + A2FilePascal* pFile = (A2FilePascal*) fpFile; + + /* don't allow them to read past the end of the file */ + if (fOffset + (long)len > fOpenEOF) { + if (pActual == NULL) + return kDIErrDataUnderrun; + len = (size_t) (fOpenEOF - fOffset); + } + if (pActual != NULL) + *pActual = len; + long incrLen = len; + + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + long block = pFile->fStartBlock + (long) (fOffset / kBlkSize); + int bufOffset = (long) (fOffset % kBlkSize); // (& 0x01ff) + size_t thisCount; + + if (len == 0) + return kDIErrNone; + assert(fOpenEOF != 0); + + while (len) { + assert(block >= pFile->fStartBlock && block < pFile->fNextBlock); + + dierr = pFile->GetDiskFS()->GetDiskImg()->ReadBlock(block, blkBuf); + if (dierr != kDIErrNone) { + LOGI(" Pascal error reading file '%s'", pFile->fFileName); + return dierr; + } + thisCount = kBlkSize - bufOffset; + if (thisCount > len) + thisCount = len; + + memcpy(buf, blkBuf + bufOffset, thisCount); + len -= thisCount; + buf = (char*)buf + thisCount; + + bufOffset = 0; + block++; + } + + fOffset += incrLen; + + return dierr; +} + +/* + * Write data at the current offset. + * + * We make the customary assumptions here: we're writing to a brand-new file, + * and writing all data in one shot. On a Pascal disk, that makes this + * process almost embarrassingly simple. + */ +DIError A2FDPascal::Write(const void* buf, size_t len, size_t* pActual) +{ + DIError dierr = kDIErrNone; + A2FilePascal* pFile = (A2FilePascal*) fpFile; + DiskFSPascal* pDiskFS = (DiskFSPascal*) fpFile->GetDiskFS(); + uint8_t blkBuf[kBlkSize]; + size_t origLen = len; + + LOGD(" DOS Write len=%lu %s", (unsigned long) len, pFile->GetPathName()); + + if (len >= 0x01000000) { // 16MB + assert(false); + return kDIErrInvalidArg; + } + + assert(fOffset == 0); // big simplifying assumption + assert(fOpenEOF == 0); // another one + assert(fOpenBlocksUsed == 1); + assert(buf != NULL); + + /* + * Verify that there's enough room between this file and the next to + * hold the contents of the file. + */ + long blocksNeeded, blocksAvail; + A2FilePascal* pNextFile; + pNextFile = (A2FilePascal*) pDiskFS->GetNextFile(pFile); + if (pNextFile == NULL) + blocksAvail = pDiskFS->GetTotalBlocks() - pFile->fStartBlock; + else + blocksAvail = pNextFile->fStartBlock - pFile->fStartBlock; + + blocksNeeded = (len + kBlkSize -1) / kBlkSize; + LOGD("Pascal write '%s' %lu bytes: avail=%ld needed=%ld", + pFile->GetPathName(), (unsigned long) len, blocksAvail, blocksNeeded); + if (blocksAvail < blocksNeeded) + return kDIErrDiskFull; + + /* + * Write the data. + */ + long block; + block = pFile->fStartBlock; + while (len != 0) { + if (len >= (size_t) kBlkSize) { + /* full block write */ + dierr = pDiskFS->GetDiskImg()->WriteBlock(block, buf); + if (dierr != kDIErrNone) + goto bail; + + len -= kBlkSize; + buf = (uint8_t*) buf + kBlkSize; + } else { + /* partial block write */ + memset(blkBuf, 0, sizeof(blkBuf)); + memcpy(blkBuf, buf, len); + dierr = pDiskFS->GetDiskImg()->WriteBlock(block, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + len = 0; + } + + block++; + } + + /* + * Update FD state. + */ + fOpenBlocksUsed = blocksNeeded; + fOpenEOF = origLen; + fOffset = origLen; + fModified = true; + +bail: + return dierr; +} + +/* + * Seek to a new offset. + */ +DIError A2FDPascal::Seek(di_off_t offset, DIWhence whence) +{ + //di_off_t fileLen = ((A2FilePascal*) fpFile)->fLength; + + switch (whence) { + case kSeekSet: + if (offset < 0 || offset > fOpenEOF) + return kDIErrInvalidArg; + fOffset = offset; + break; + case kSeekEnd: + if (offset > 0 || offset < -fOpenEOF) + return kDIErrInvalidArg; + fOffset = fOpenEOF + offset; + break; + case kSeekCur: + if (offset < -fOffset || + offset >= (fOpenEOF - fOffset)) + { + return kDIErrInvalidArg; + } + fOffset += offset; + break; + default: + assert(false); + return kDIErrInvalidArg; + } + + assert(fOffset >= 0 && fOffset <= fOpenEOF); + return kDIErrNone; +} + +/* + * Return current offset. + */ +di_off_t A2FDPascal::Tell(void) +{ + return fOffset; +} + +/* + * Release file state, and tell our parent to destroy us. + * + * 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 A2FDPascal::Close(void) +{ + DIError dierr = kDIErrNone; + DiskFSPascal* pDiskFS = (DiskFSPascal*) fpFile->GetDiskFS(); + + if (fModified) { + A2FilePascal* pFile = (A2FilePascal*) fpFile; + uint8_t* pEntry; + + dierr = pDiskFS->LoadCatalog(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Update our internal copies of stuff. + */ + pFile->fLength = fOpenEOF; + pFile->fNextBlock = pFile->fStartBlock + (uint16_t) fOpenBlocksUsed; + pFile->fModWhen = A2FilePascal::ConvertPascalDate(time(NULL)); + + /* + * Update the "next block" value and the length-in-last-block. We + * have to scan through the directory to find our entry, rather + * than remember an offset at "open" time, on the off chance that + * somebody created or deleted a file after we were opened. + */ + pEntry = pDiskFS->FindDirEntry(pFile); + if (pEntry == NULL) { + // we deleted an open file? + assert(false); + dierr = kDIErrInternal; + goto bail; + } + uint16_t bytesInLastBlock; + bytesInLastBlock = (uint16_t)pFile->fLength % kBlkSize; + if (bytesInLastBlock == 0) + bytesInLastBlock = 512; // exactly filled out last block + + PutShortLE(&pEntry[0x02], pFile->fNextBlock); + PutShortLE(&pEntry[0x16], bytesInLastBlock); + PutShortLE(&pEntry[0x18], pFile->fModWhen); + + dierr = pDiskFS->SaveCatalog(); + if (dierr != kDIErrNone) + goto bail; + } + +bail: + pDiskFS->FreeCatalog(); + fpFile->CloseDescr(this); + return dierr; +} + +/* + * Return the #of sectors/blocks in the file. + */ +long A2FDPascal::GetSectorCount(void) const +{ + A2FilePascal* pFile = (A2FilePascal*) fpFile; + return (pFile->fNextBlock - pFile->fStartBlock) * 2; +} + +long A2FDPascal::GetBlockCount(void) const +{ + A2FilePascal* pFile = (A2FilePascal*) fpFile; + return pFile->fNextBlock - pFile->fStartBlock; +} + +/* + * Return the Nth track/sector in this file. + */ +DIError A2FDPascal::GetStorage(long sectorIdx, long* pTrack, long* pSector) const +{ + A2FilePascal* pFile = (A2FilePascal*) fpFile; + long pascalIdx = sectorIdx / 2; + long pascalBlock = pFile->fStartBlock + pascalIdx; + if (pascalBlock >= pFile->fNextBlock) + return kDIErrInvalidIndex; + + /* sparse blocks not possible on Pascal volumes */ + BlockToTrackSector(pascalBlock, (sectorIdx & 0x01) != 0, pTrack, pSector); + return kDIErrNone; +} + +/* + * Return the Nth 512-byte block in this file. + */ +DIError A2FDPascal::GetStorage(long blockIdx, long* pBlock) const +{ + A2FilePascal* pFile = (A2FilePascal*) fpFile; + long pascalBlock = pFile->fStartBlock + blockIdx; + if (pascalBlock >= pFile->fNextBlock) + return kDIErrInvalidIndex; + + *pBlock = pascalBlock; + assert(*pBlock < pFile->GetDiskFS()->GetDiskImg()->GetNumBlocks()); + return kDIErrNone; +} diff --git a/diskimg/ProDOS.cpp b/diskimg/ProDOS.cpp new file mode 100644 index 0000000..90d604d --- /dev/null +++ b/diskimg/ProDOS.cpp @@ -0,0 +1,5183 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Implementation of DiskFSProDOS class. + * + * We currently only allow one fork to be open at a time, and each file may + * only be opened once. + * + * BUG: does not keep VolumeUsage up to date. + */ +#include "StdAfx.h" +#include "DiskImgPriv.h" + +// disable Y2K+ dates when testing w/ProSel-16 vol rep (newer ProSel is OK) +//#define OLD_PRODOS_DATES + +#if defined(OLD_PRODOS_DATES) && !(defined(_DEBUG)) +# error "don't set OLD_PRODOS_DATES for production" +#endif + + +/* + * =========================================================================== + * DiskFSProDOS + * =========================================================================== + */ + +const int kBlkSize = 512; +const int kVolHeaderBlock = 2; // block where Volume Header resides +const int kFormatVolDirNumBlocks = 4; // #of volume header blocks for new volumes +const int kMinReasonableBlocks = 16; // min size for ProDOS volume +const int kExpectedBitmapStart = 6; // block# where vol bitmap should start +const int kMaxCatalogIterations = 1024; // theoretical max is 32768? +const int kMaxDirectoryDepth = 64; // not sure what ProDOS limit is +const int kEntriesPerBlock = 0x0d; // expected value for entries per blk +const int kEntryLength = 0x27; // expected value for dir entry len +const int kTypeDIR = 0x0f; + + +/* + * Directory header. All fields not marked as "only for subdirs" also apply + * to the volume directory header. + */ +typedef struct DiskFSProDOS::DirHeader { + uint8_t storageType; + char dirName[A2FileProDOS::kMaxFileName+1]; + DiskFSProDOS::ProDate createWhen; + uint8_t version; + uint8_t minVersion; + uint8_t access; + uint8_t entryLength; + uint8_t entriesPerBlock; + uint16_t fileCount; + /* the rest are only for subdirs */ + uint16_t parentPointer; + uint8_t parentEntry; + uint8_t parentEntryLength; +} DirHeader; + + +/* + * See if this looks like a ProDOS volume. + * + * We test a few fields in the volume directory header for validity. + */ +static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + int volDirEntryLength; + int volDirEntriesPerBlock; + + dierr = pImg->ReadBlockSwapped(kVolHeaderBlock, blkBuf, imageOrder, + DiskImg::kSectorOrderProDOS); + if (dierr != kDIErrNone) + goto bail; + + volDirEntryLength = blkBuf[0x23]; + volDirEntriesPerBlock = blkBuf[0x24]; + + + if (!(blkBuf[0x00] == 0 && blkBuf[0x01] == 0) || + !((blkBuf[0x04] & 0xf0) == 0xf0) || + !((blkBuf[0x04] & 0x0f) != 0) || + !(volDirEntryLength * volDirEntriesPerBlock <= kBlkSize) || + !(blkBuf[0x05] >= 'A' && blkBuf[0x05] <= 'Z') || + 0) + { + dierr = kDIErrFilesystemNotFound; + goto bail; + } + +bail: + return dierr; +} + +/* + * Test to see if the image is a ProDOS disk. + */ +/*static*/ DIError DiskFSProDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, + DiskImg::FSFormat* pFormat, FSLeniency leniency) +{ + 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::kFormatProDOS; + return kDIErrNone; + } + } + + LOGI(" ProDOS 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 has + * no files on it. + */ +DIError DiskFSProDOS::Initialize(InitMode initMode) +{ + DIError dierr = kDIErrNone; + char msg[kMaxVolumeName + 32]; + + fDiskIsGood = false; // hosed until proven innocent + fEarlyDamage = false; + + /* + * NOTE: we'd probably be better off with fTotalBlocks, since that's how + * big the disk *thinks* it is, especially on a CFFA or MacPart subvol. + * However, we know that the image block count is the absolute maximum, + * so while it may not be a tight bound it is an upper bound. + */ + fVolumeUsage.Create(fpImg->GetNumBlocks()); + + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) + goto bail; + DumpVolHeader(); + + dierr = ScanVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + if (initMode == kInitHeaderOnly) { + LOGI(" ProDOS - headerOnly set, skipping file load"); + goto bail; + } + + sprintf(msg, "Scanning %s", fVolumeName); + if (!fpImg->UpdateScanProgress(msg)) { + LOGI(" ProDOS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + /* volume dir is guaranteed to come first; if not, we need a lookup func */ + A2FileProDOS* pVolumeDir; + pVolumeDir = (A2FileProDOS*) GetNextFile(NULL); + + dierr = RecursiveDirAdd(pVolumeDir, kVolHeaderBlock, "", 0); + if (dierr != kDIErrNone) { + LOGI(" ProDOS RecursiveDirAdd failed"); + goto bail; + } + + sprintf(msg, "Processing %s", fVolumeName); + if (!fpImg->UpdateScanProgress(msg)) { + LOGI(" ProDOS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + dierr = ScanFileUsage(); + if (dierr != kDIErrNone) { + if (dierr == kDIErrCancelled) + goto bail; + + /* this might not be fatal; just means that *some* files are bad */ + LOGI("WARNING: ScanFileUsage returned err=%d", dierr); + dierr = kDIErrNone; + fpImg->AddNote(DiskImg::kNoteWarning, + "Some errors were encountered while scanning files."); + fEarlyDamage = true; // make sure we know it's damaged + } + + fDiskIsGood = CheckDiskIsGood(); + + if (fScanForSubVolumes != kScanSubDisabled) + (void) ScanForSubVolumes(); + + if (fpImg->GetNumBlocks() <= 1600) + fVolumeUsage.Dump(); + +// A2File* pFile; +// pFile = GetNextFile(NULL); +// while (pFile != NULL) { +// pFile->Dump(); +// pFile = GetNextFile(pFile); +// } + +bail: + return dierr; +} + +/* + * Read some interesting fields from the volume header. + * + * The "test" function verified certain things, e.g. the storage type + * is $f and the volume name length is nonzero. + */ +DIError DiskFSProDOS::LoadVolHeader(void) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + int nameLen; + + dierr = fpImg->ReadBlock(kVolHeaderBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + //fPrevBlock = GetShortLE(&blkBuf[0x00]); + //fNextBlock = GetShortLE(&blkBuf[0x02]); + nameLen = blkBuf[0x04] & 0x0f; + memcpy(fVolumeName, &blkBuf[0x05], nameLen); + fVolumeName[nameLen] = '\0'; + // 0x14-15 reserved + // undocumented: GS/OS writes the modification date to 0x16-19 + fModWhen = GetLongLE(&blkBuf[0x16]); + // undocumented: GS/OS uses 0x1a-1b for lower-case handling (see below) + fCreateWhen = GetLongLE(&blkBuf[0x1c]); + //fVersion = blkBuf[0x20]; + if (blkBuf[0x21] != 0) { + /* + * We don't care about the MIN_VERSION field, but it looks like GS/OS + * rejects anything with a nonzero value here. We want to add a note + * about it. + */ + fpImg->AddNote(DiskImg::kNoteInfo, + "Volume header has nonzero min_version; could confuse GS/OS."); + } + fAccess = blkBuf[0x22]; + //fEntryLength = blkBuf[0x23]; + //fEntriesPerBlock = blkBuf[0x24]; + fVolDirFileCount = GetShortLE(&blkBuf[0x25]); + fBitMapPointer = GetShortLE(&blkBuf[0x27]); + fTotalBlocks = GetShortLE(&blkBuf[0x29]); + + if (blkBuf[0x1b] & 0x80) { + /* + * Handle lower-case conversion; see GS/OS tech note #8. Unlike + * filenames, volume names are not allowed to contain spaces. If + * they try it we just ignore them. + * + * Technote 8 doesn't actually talk about volume names. By + * experimentation the field was discovered at offset 0x1a from + * the start of the block, which is marked as "reserved" in Beneath + * Apple ProDOS. + */ + uint16_t lcFlags = GetShortLE(&blkBuf[0x1a]); + + GenerateLowerCaseName(fVolumeName, fVolumeName, lcFlags, false); + } + + if (fTotalBlocks <= kVolHeaderBlock) { + /* incr to min; don't use max, or bitmap count may be too large */ + LOGI(" ProDOS found tiny fTotalBlocks (%d), increasing to minimum", + fTotalBlocks); + fpImg->AddNote(DiskImg::kNoteWarning, + "ProDOS filesystem blockcount (%d) too small, setting to %d.", + fTotalBlocks, kMinReasonableBlocks); + fTotalBlocks = kMinReasonableBlocks; + fEarlyDamage = true; + } else if (fTotalBlocks != fpImg->GetNumBlocks()) { + if (fTotalBlocks != 65535 || fpImg->GetNumBlocks() != 65536) { + LOGI(" ProDOS WARNING: total (%u) != img (%ld)", + fTotalBlocks, fpImg->GetNumBlocks()); + // could AddNote here, but not really necessary + } + + /* + * For safety (esp. vol bitmap read), constrain fTotalBlocks. We might + * consider not doing this for ".hdv", which can start small and then + * expand as files are added. (Check "fExpanded".) + */ + if (fTotalBlocks > fpImg->GetNumBlocks()) { + fpImg->AddNote(DiskImg::kNoteWarning, + "ProDOS filesystem blockcount (%d) exceeds disk image blocks (%ld).", + fTotalBlocks, fpImg->GetNumBlocks()); + fTotalBlocks = (uint16_t) fpImg->GetNumBlocks(); + fEarlyDamage = true; + } + } + + /* + * Test for funky volume bitmap pointer. Some disks (e.g. /RAM and + * ProSel-16) truncate the volume directory to eke a little more storage + * out of a disk. There's nothing wrong with that, but we don't want to + * try to use a volume bitmap pointer of zero or 0xffff, because it's + * probably garbage. + */ + if (fBitMapPointer != kExpectedBitmapStart) { + if (fBitMapPointer <= kVolHeaderBlock || + fBitMapPointer > kExpectedBitmapStart) + { + fpImg->AddNote(DiskImg::kNoteWarning, + "Volume bitmap pointer (%d) is probably invalid.", + fBitMapPointer); + fBitMapPointer = 6; // just fix it and hope for the best + fEarlyDamage = true; + } else { + fpImg->AddNote(DiskImg::kNoteInfo, + "Unusual volume bitmap start (%d).", fBitMapPointer); + // try it and see + } + } + + SetVolumeID(); + + /* + * Create a "magic" directory entry for the volume directory. + * + * Normally these values are pulled out of the file entry in the parent + * directory. Here, we synthesize them from the volume dir header. + */ + A2FileProDOS* pFile; + pFile = new A2FileProDOS(this); + if (pFile == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + A2FileProDOS::DirEntry* pEntry; + pEntry = &pFile->fDirEntry; + + int foundStorage; + foundStorage = (blkBuf[0x04] & 0xf0) >> 4; + if (foundStorage != A2FileProDOS::kStorageVolumeDirHeader) { + LOGI(" ProDOS WARNING: unexpected vol dir file type %d", + pEntry->storageType); + /* keep going */ + } + pEntry->storageType = A2FileProDOS::kStorageVolumeDirHeader; + strcpy(pEntry->fileName, fVolumeName); + //nameLen = blkBuf[0x04] & 0x0f; + //memcpy(pEntry->fileName, &blkBuf[0x05], nameLen); + //pEntry->fileName[nameLen] = '\0'; + pFile->SetPathName(":", pEntry->fileName); + pEntry->fileName[nameLen] = '\0'; + pEntry->fileType = kTypeDIR; + pEntry->keyPointer = kVolHeaderBlock; + dierr = DetermineVolDirLen(GetShortLE(&blkBuf[0x02]), &pEntry->blocksUsed); + if (dierr != kDIErrNone) { + goto bail; + } + pEntry->eof = pEntry->blocksUsed * 512; + pEntry->createWhen = GetLongLE(&blkBuf[0x1c]); + pEntry->version = blkBuf[0x20]; + pEntry->minVersion = blkBuf[0x21]; + pEntry->access = blkBuf[0x22]; + pEntry->auxType = 0; +// if (blkBuf[0x20] >= 5) + pEntry->modWhen = GetLongLE(&blkBuf[0x16]); + pEntry->headerPointer = 0; + + pFile->fSparseDataEof = pEntry->eof; + pFile->fSparseRsrcEof = -1; + + AddFileToList(pFile); + pFile = NULL; + +bail: + delete pFile; + return dierr; +} + +DIError DiskFSProDOS::DetermineVolDirLen(uint16_t nextBlock, uint16_t* pBlocksUsed) { + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + uint16_t blocksUsed = 1; + int iterCount = 0; + + // Traverse the volume directory chain, counting blocks. Normally this will have 4, but + // variations are possible. + while (nextBlock != 0) { + blocksUsed++; + + if (nextBlock < 2 || nextBlock >= fpImg->GetNumBlocks()) { + LOGI(" ProDOS ERROR: invalid volume dir link block %u", nextBlock); + dierr = kDIErrInvalidBlock; + goto bail; + } + dierr = fpImg->ReadBlock(nextBlock, blkBuf); + if (dierr != kDIErrNone) { + goto bail; + } + + nextBlock = GetShortLE(&blkBuf[0x02]); + + // Watch for infinite loop. + iterCount++; + if (iterCount > fpImg->GetNumBlocks()) { + LOGI(" ProDOS ERROR: infinite vol directory loop found"); + dierr = kDIErrDirectoryLoop; + goto bail; + } + } + +bail: + *pBlocksUsed = blocksUsed; + return dierr; +} + +/* + * Set the volume ID field. + */ +void DiskFSProDOS::SetVolumeID(void) +{ + sprintf(fVolumeID, "ProDOS /%s", fVolumeName); +} + +/* + * Dump what we pulled out of the volume header. + */ +void DiskFSProDOS::DumpVolHeader(void) +{ + LOGI(" ProDOS volume header for '%s'", fVolumeName); + LOGI(" CreateWhen=0x%08x access=0x%02x bitmap=%d totalbl=%d", + fCreateWhen, fAccess, fBitMapPointer, fTotalBlocks); + + time_t when; + when = A2FileProDOS::ConvertProDate(fCreateWhen); + LOGI(" CreateWhen is %.24s", ctime(&when)); + + //LOGI(" prev=%d next=%d bitmap=%d total=%d", + // fPrevBlock, fNextBlock, fBitMapPointer, fTotalBlocks); + //LOGI(" create date=0x%08lx access=0x%02x", fCreateWhen, fAccess); + //LOGI(" version=%d minVersion=%d entryLen=%d epb=%d", + // fVersion, fMinVersion, fEntryLength, fEntriesPerBlock); + //LOGI(" volume dir fileCount=%d", fFileCount); +} + +/* + * Load the disk's volume bitmap into the object's "fBlockUseMap" pointer. + * + * Does not attempt to analyze the data. + */ +DIError DiskFSProDOS::LoadVolBitmap(void) +{ + DIError dierr = kDIErrNone; + int bitBlock, numBlocks; + + if (fBitMapPointer <= kVolHeaderBlock) + return kDIErrBadDiskImage; + if (fTotalBlocks <= kVolHeaderBlock) + return kDIErrBadDiskImage; + + /* should not already be allocated */ + assert(fBlockUseMap == NULL); + delete[] fBlockUseMap; // just in case + + bitBlock = fBitMapPointer; + + numBlocks = GetNumBitmapBlocks(); // based on fTotalBlocks + assert(numBlocks > 0); + + fBlockUseMap = new uint8_t[kBlkSize * numBlocks]; + if (fBlockUseMap == NULL) + return kDIErrMalloc; + + while (numBlocks--) { + dierr = fpImg->ReadBlock(bitBlock + numBlocks, + fBlockUseMap + kBlkSize * numBlocks); + if (dierr != kDIErrNone) { + delete[] fBlockUseMap; + fBlockUseMap = NULL; + return dierr; + } + } + + return kDIErrNone; +} + +/* + * Save our copy of the volume bitmap. + */ +DIError DiskFSProDOS::SaveVolBitmap(void) +{ + DIError dierr = kDIErrNone; + int bitBlock, numBlocks; + + if (fBlockUseMap == NULL) { + assert(false); + return kDIErrNotReady; + } + assert(fBitMapPointer > kVolHeaderBlock); + assert(fTotalBlocks > kVolHeaderBlock); + + bitBlock = fBitMapPointer; + + numBlocks = GetNumBitmapBlocks(); + assert(numBlocks > 0); + + while (numBlocks--) { + dierr = fpImg->WriteBlock(bitBlock + numBlocks, + fBlockUseMap + kBlkSize * numBlocks); + if (dierr != kDIErrNone) + return dierr; + } + + return kDIErrNone; +} + +/* + * Throw away the volume bitmap, discarding any unsaved changes. + * + * It's okay to call this if the bitmap isn't loaded. + */ +void DiskFSProDOS::FreeVolBitmap(void) +{ + delete[] fBlockUseMap; + fBlockUseMap = NULL; +} + +/* + * Examine the volume bitmap, setting fields in the VolumeUsage map + * as appropriate. + */ +DIError DiskFSProDOS::ScanVolBitmap(void) +{ + DIError dierr; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) { + LOGI(" ProDOS failed to load volume bitmap (err=%d)", dierr); + return dierr; + } + + assert(fBlockUseMap != NULL); + + /* mark the boot blocks as system */ + SetBlockUsage(0, VolumeUsage::kChunkPurposeSystem); + SetBlockUsage(1, VolumeUsage::kChunkPurposeSystem); + + /* mark the bitmap blocks as system */ + int i; + for (i = GetNumBitmapBlocks(); i > 0; i--) + SetBlockUsage(fBitMapPointer + i -1, VolumeUsage::kChunkPurposeSystem); + + /* + * Set the "isMarkedUsed" flag in VolumeUsage for all used blocks. + */ + VolumeUsage::ChunkState cstate; + + long block = 0; + long numBytes = (fTotalBlocks + 7) / 8; + for (i = 0; i < numBytes; i++) { + uint8_t val = fBlockUseMap[i]; + + for (int j = 0; j < 8; j++) { + if (!(val & 0x80)) { + /* block is in use, mark it */ + if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) + { + assert(false); + // keep going, I guess + } + cstate.isMarkedUsed = true; + fVolumeUsage.SetChunkState(block, &cstate); + } + val <<= 1; + block++; + + if (block >= fTotalBlocks) + break; + } + if (block >= fTotalBlocks) + break; + } + + FreeVolBitmap(); + return dierr; +} + +/* + * Generate an empty block use map. Used by disk formatter. + */ +DIError DiskFSProDOS::CreateEmptyBlockMap(void) +{ + DIError dierr; + + /* load from disk; this is just to allocate the data structures */ + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + /* + * Set the bits, block by block. Not the most efficient way, but it's + * fast enough, and it exercises the standard set of functions. + */ + long block; + long firstEmpty = + kVolHeaderBlock + kFormatVolDirNumBlocks + GetNumBitmapBlocks(); + for (block = 0; block < firstEmpty; block++) + SetBlockUseEntry(block, true); + for ( ; block < fTotalBlocks; block++) + SetBlockUseEntry(block, false); + + dierr = SaveVolBitmap(); + FreeVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + return kDIErrNone; +} + +/* + * Get the state of an entry in the block use map. + * + * Returns "true" if it's in use, "false" otherwise. + */ +bool DiskFSProDOS::GetBlockUseEntry(long block) const +{ + assert(block >= 0 && block < fTotalBlocks); + assert(fBlockUseMap != NULL); + + int offset; + uint8_t mask; + + offset = block / 8; + mask = 0x80 >> (block & 0x07); + if (fBlockUseMap[offset] & mask) + return false; + else + return true; +} + +/* + * Change the state of an entry in the block use map. + */ +void DiskFSProDOS::SetBlockUseEntry(long block, bool inUse) +{ + assert(block >= 0 && block < fTotalBlocks); + assert(fBlockUseMap != NULL); + + if (block == 0 && !inUse) { + // shouldn't happen + assert(false); + } + + int offset; + uint8_t mask; + + offset = block / 8; + mask = 0x80 >> (block & 0x07); + if (!inUse) + fBlockUseMap[offset] |= mask; + else + fBlockUseMap[offset] &= ~mask; +} + +/* + * Check for entries in the block use map past the point where they should be. + * + * Returns "true" if bogus entries were found, "false" if all is well. + */ +bool DiskFSProDOS::ScanForExtraEntries(void) const +{ + assert(fBlockUseMap != NULL); + + int offset, endOffset; + + /* sloppy: we're not checking for excess bits within last byte */ + offset = (fTotalBlocks / 8) +1; + endOffset = GetNumBitmapBlocks() * kBlkSize; + + while (offset < endOffset) { + if (fBlockUseMap[offset] != 0) { + LOGI(" ProDOS found bogus bitmap junk 0x%02x at offset=%d", + fBlockUseMap[offset], offset); + return true; + } + offset++; + } + return false; +} + +/* + * Allocate a new block on a ProDOS volume. + * + * Only touches the in-memory copy. + * + * Returns the block number (0-65535) on success or -1 on failure. + */ +long DiskFSProDOS::AllocBlock(void) +{ + assert(fBlockUseMap != NULL); + +#if 0 // whoa... this is REALLY slow + /* + * Run through the entire set of blocks until we find one that's not + * allocated. We could probably make this faster by scanning bytes and + * then shifting bits, but this is easier and fast enough. + * + * We don't scan block 0 because (a) it should never be available and + * (b) it has a special meaning in some circumstances. We could probably + * start at kVolHeaderBlock+kVolHeaderNumBlocks. + */ + long block; + for (block = kVolHeaderBlock; block < fTotalBlocks; block++) { + if (!GetBlockUseEntry(block)) { + SetBlockUseEntry(block, true); + return block; + } + } +#endif + + int offset; + int maxOffset = (fTotalBlocks + 7) / 8; + + for (offset = 0; offset < maxOffset; offset++) { + if (fBlockUseMap[offset] != 0) { + /* got one, figure out which */ + int subBlock = 0; + uint8_t uch = fBlockUseMap[offset]; + while ((uch & 0x80) == 0) { + subBlock++; + uch <<= 1; + } + + long block = offset * 8 + subBlock; + assert(!GetBlockUseEntry(block)); + SetBlockUseEntry(block, true); + if (block == 0 || block == 1) { + LOGI("PRODOS: GLITCH: rejecting alloc of block 0"); + continue; + } + return block; + } + } + + LOGI("ProDOS: NOTE: AllocBlock just failed!"); + return -1; +} + +/* + * Tally up the number of free blocks. + */ +DIError DiskFSProDOS::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, + int* pUnitSize) const +{ + DIError dierr; + long block, freeBlocks; + freeBlocks = 0; + + dierr = const_cast(this)->LoadVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + for (block = 0; block < fTotalBlocks; block++) { + if (!GetBlockUseEntry(block)) + freeBlocks++; + } + + *pTotalUnits = fTotalBlocks; + *pFreeUnits = freeBlocks; + *pUnitSize = kBlockSize; + + const_cast(this)->FreeVolBitmap(); + return kDIErrNone; +} + +/* + * Update an entry in the VolumeUsage map. + * + * The VolumeUsage map spans the range of blocks + */ +void DiskFSProDOS::SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose) +{ + VolumeUsage::ChunkState cstate; + + fVolumeUsage.GetChunkState(block, &cstate); + if (cstate.isUsed) { + cstate.purpose = VolumeUsage::kChunkPurposeConflict; + LOGI(" ProDOS conflicting uses for bl=%ld", block); + } else { + cstate.isUsed = true; + cstate.purpose = purpose; + } + fVolumeUsage.SetChunkState(block, &cstate); +} + +/* + * Pass in the number of the first block of the directory. + * + * Start with "pParent" set to the magic entry for the volume dir. + */ +DIError DiskFSProDOS::RecursiveDirAdd(A2File* pParent, uint16_t dirBlock, + const char* basePath, int depth) +{ + DIError dierr = kDIErrNone; + DirHeader header; + uint8_t blkBuf[kBlkSize]; + int numEntries, iterations, foundCount; + bool first; + + /* if we get too deep, assume it's a loop */ + if (depth > kMaxDirectoryDepth) { + dierr = kDIErrDirectoryLoop; + goto bail; + } + + + if (dirBlock < kVolHeaderBlock || dirBlock >= fpImg->GetNumBlocks()) { + LOGI(" ProDOS ERROR: directory block %u out of range", dirBlock); + dierr = kDIErrInvalidBlock; + goto bail; + } + + numEntries = 1; + iterations = 0; + foundCount = 0; + first = true; + + while (dirBlock && iterations < kMaxCatalogIterations) { + dierr = fpImg->ReadBlock(dirBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + if (pParent->IsVolumeDirectory()) + SetBlockUsage(dirBlock, VolumeUsage::kChunkPurposeVolumeDir); + else + SetBlockUsage(dirBlock, VolumeUsage::kChunkPurposeSubdir); + + if (first) { + /* this is the directory header entry */ + dierr = GetDirHeader(blkBuf, &header); + if (dierr != kDIErrNone) + goto bail; + numEntries = header.fileCount; + //LOGI(" ProDOS got dir header numEntries = %d", numEntries); + } + + /* slurp the entries out of this block */ + dierr = SlurpEntries(pParent, &header, blkBuf, first, &foundCount, + basePath, dirBlock, depth); + if (dierr != kDIErrNone) + goto bail; + + dirBlock = GetShortLE(&blkBuf[0x02]); + if (dirBlock != 0 && + (dirBlock < 2 || dirBlock >= fpImg->GetNumBlocks())) + { + LOGI(" ProDOS ERROR: invalid dir link block %u in base='%s'", + dirBlock, basePath); + dierr = kDIErrInvalidBlock; + goto bail; + } + first = false; + iterations++; + } + if (iterations == kMaxCatalogIterations) { + LOGI(" ProDOS subdir iteration count exceeded"); + dierr = kDIErrDirectoryLoop; + goto bail; + } + if (foundCount != numEntries) { + /* not significant; just means somebody isn't updating correctly */ + LOGI(" ProDOS WARNING: numEntries=%d foundCount=%d in base='%s'", + numEntries, foundCount, basePath); + } + +bail: + return dierr; +} + +/* + * Slurp the entries out of a single ProDOS directory block. + * + * Recursively calls RecursiveDirAdd for directories. + * + * "*pFound" is increased by the number of valid entries found in this block. + */ +DIError DiskFSProDOS::SlurpEntries(A2File* pParent, const DirHeader* pHeader, + const uint8_t* blkBuf, bool skipFirst, int* pCount, + const char* basePath, uint16_t thisBlock, int depth) +{ + DIError dierr = kDIErrNone; + int entriesThisBlock = pHeader->entriesPerBlock; + const uint8_t* entryBuf; + A2FileProDOS* pFile; + + int idx = 0; + entryBuf = &blkBuf[0x04]; + if (skipFirst) { + entriesThisBlock--; + entryBuf += pHeader->entryLength; + idx++; + } + + for ( ; entriesThisBlock > 0 ; + entriesThisBlock--, idx++, entryBuf += pHeader->entryLength) + { + if (entryBuf >= blkBuf + kBlkSize) { + LOGI(" ProDOS whoops, just walked out of dirent buffer"); + return kDIErrBadDirectory; + } + + if ((entryBuf[0x00] & 0xf0) == A2FileProDOS::kStorageDeleted) { + /* skip deleted entries */ + continue; + } + + pFile = new A2FileProDOS(this); + if (pFile == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + A2FileProDOS::DirEntry* pEntry; + pEntry = &pFile->fDirEntry; + A2FileProDOS::InitDirEntry(pEntry, entryBuf); + + pFile->SetParent(pParent); + pFile->fParentDirBlock = thisBlock; + pFile->fParentDirIdx = idx; + + pFile->SetPathName(basePath, pEntry->fileName); + + if (pEntry->keyPointer <= kVolHeaderBlock) { + LOGI("ProDOS invalid key pointer %d on '%s'", + pEntry->keyPointer, pFile->GetPathName()); + pFile->SetQuality(A2File::kQualityDamaged); + } else + if (pEntry->storageType == A2FileProDOS::kStorageExtended) { + dierr = ReadExtendedInfo(pFile); + if (dierr != kDIErrNone) { + pFile->SetQuality(A2File::kQualityDamaged); + dierr = kDIErrNone; + } + } + + //pFile->Dump(); + AddFileToList(pFile); + (*pCount)++; + + if (!fpImg->UpdateScanProgress(NULL)) { + LOGI(" ProDOS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + if (pEntry->storageType == A2FileProDOS::kStorageDirectory) { + // don't need to check for kStorageVolumeDirHeader here + dierr = RecursiveDirAdd(pFile, pEntry->keyPointer, + pFile->GetPathName(), depth+1); + if (dierr != kDIErrNone) { + if (dierr == kDIErrCancelled) + goto bail; + + /* mark subdir as damaged and keep going */ + pFile->SetQuality(A2File::kQualityDamaged); + dierr = kDIErrNone; + } + } + } + +bail: + return dierr; +} + +/* + * Pull the directory header out of the first block of a directory. + */ +DIError DiskFSProDOS::GetDirHeader(const uint8_t* blkBuf, DirHeader* pHeader) +{ + int nameLen; + + pHeader->storageType = (blkBuf[0x04] & 0xf0) >> 4; + if (pHeader->storageType != A2FileProDOS::kStorageSubdirHeader && + pHeader->storageType != A2FileProDOS::kStorageVolumeDirHeader) + { + LOGI(" ProDOS WARNING: subdir header has wrong storage type (%d)", + pHeader->storageType); + /* keep going... might be bad idea */ + } + nameLen = blkBuf[0x04] & 0x0f; + memcpy(pHeader->dirName, &blkBuf[0x05], nameLen); + pHeader->dirName[nameLen] = '\0'; + pHeader->createWhen = GetLongLE(&blkBuf[0x1c]); + pHeader->version = blkBuf[0x20]; + pHeader->minVersion = blkBuf[0x21]; + pHeader->access = blkBuf[0x22]; + pHeader->entryLength = blkBuf[0x23]; + pHeader->entriesPerBlock = blkBuf[0x24]; + pHeader->fileCount = GetShortLE(&blkBuf[0x25]); + pHeader->parentPointer = GetShortLE(&blkBuf[0x27]); + pHeader->parentEntry = blkBuf[0x29]; + pHeader->parentEntryLength = blkBuf[0x2a]; + + if (pHeader->entryLength * pHeader->entriesPerBlock > kBlkSize || + pHeader->entryLength * pHeader->entriesPerBlock == 0) + { + LOGI(" ProDOS invalid subdir header: entryLen=%d, entriesPerBlock=%d", + pHeader->entryLength, pHeader->entriesPerBlock); + return kDIErrBadDirectory; + } + + return kDIErrNone; +} + +/* + * Read the information from the key block of an extended file. + * + * There's some "HFS Finder information" stuffed into the key block + * right after the data fork info, but I'm planning to ignore that. + */ +DIError DiskFSProDOS::ReadExtendedInfo(A2FileProDOS* pFile) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + + dierr = fpImg->ReadBlock(pFile->fDirEntry.keyPointer, blkBuf); + if (dierr != kDIErrNone) { + LOGI(" ProDOS ReadExtendedInfo: unable to read key block %d", + pFile->fDirEntry.keyPointer); + goto bail; + } + + pFile->fExtData.storageType = blkBuf[0x0000] & 0x0f; + pFile->fExtData.keyBlock = GetShortLE(&blkBuf[0x0001]); + pFile->fExtData.blocksUsed = GetShortLE(&blkBuf[0x0003]); + pFile->fExtData.eof = GetLongLE(&blkBuf[0x0005]); + pFile->fExtData.eof &= 0x00ffffff; + + pFile->fExtRsrc.storageType = blkBuf[0x0100] & 0x0f; + pFile->fExtRsrc.keyBlock = GetShortLE(&blkBuf[0x0101]); + pFile->fExtRsrc.blocksUsed = GetShortLE(&blkBuf[0x0103]); + pFile->fExtRsrc.eof = GetLongLE(&blkBuf[0x0105]); + pFile->fExtRsrc.eof &= 0x00ffffff; + + if (pFile->fExtData.keyBlock <= kVolHeaderBlock || + pFile->fExtRsrc.keyBlock <= kVolHeaderBlock) + { + LOGI(" ProDOS ReadExtendedInfo: found bad extended key blocks %d/%d", + pFile->fExtData.keyBlock, pFile->fExtRsrc.keyBlock); + return kDIErrBadFile; + } + +bail: + return dierr; +} + +/* + * Scan all of the files on the disk, reading their block usage into the + * volume usage map. This is important for detecting damage, and makes + * later accesses easier. + * + * As a side-effect, we set the "sparse" length for the file. + */ +DIError DiskFSProDOS::ScanFileUsage(void) +{ + DIError dierr = kDIErrNone; + A2FileProDOS* pFile; + long blockCount, indexCount, sparseCount; + uint16_t* blockList = NULL; + uint16_t* indexList = NULL; + + pFile = (A2FileProDOS*) GetNextFile(NULL); + while (pFile != NULL) { + if (!fpImg->UpdateScanProgress(NULL)) { + LOGI(" ProDOS cancelled by user"); + dierr = kDIErrCancelled; + goto bail; + } + + //pFile->Dump(); + if (pFile->GetQuality() == A2File::kQualityDamaged) + goto skip; + + if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageExtended) { + /* resource fork */ + if (!A2FileProDOS::IsRegularFile(pFile->fExtRsrc.storageType)) { + /* not expecting to find a directory here, but it happens */ + dierr = kDIErrBadFile; + } else { + dierr = pFile->LoadBlockList(pFile->fExtRsrc.storageType, + pFile->fExtRsrc.keyBlock, pFile->fExtRsrc.eof, + &blockCount, &blockList, &indexCount, &indexList); + } + if (dierr != kDIErrNone) { + LOGI(" ProDOS skipping scan rsrc '%s'", + pFile->fDirEntry.fileName); + pFile->SetQuality(A2File::kQualityDamaged); + goto skip; + } + ScanBlockList(blockCount, blockList, indexCount, indexList, + &sparseCount); + pFile->fSparseRsrcEof = + (di_off_t) pFile->fExtRsrc.eof - sparseCount * kBlkSize; + //LOGI(" SparseCount %d rsrcEof %d '%s'", + // sparseCount, pFile->fSparseRsrcEof, pFile->fDirEntry.fileName); + delete[] blockList; + blockList = NULL; + delete[] indexList; + indexList = NULL; + + /* data fork */ + if (!A2FileProDOS::IsRegularFile(pFile->fExtRsrc.storageType)) { + dierr = kDIErrBadFile; + } else { + dierr = pFile->LoadBlockList(pFile->fExtData.storageType, + pFile->fExtData.keyBlock, pFile->fExtData.eof, + &blockCount, &blockList, &indexCount, &indexList); + } + if (dierr != kDIErrNone) { + LOGI(" ProDOS skipping scan data '%s'", + pFile->fDirEntry.fileName); + pFile->SetQuality(A2File::kQualityDamaged); + goto skip; + } + ScanBlockList(blockCount, blockList, indexCount, indexList, + &sparseCount); + pFile->fSparseDataEof = + (di_off_t) pFile->fExtData.eof - sparseCount * kBlkSize; + //LOGI(" SparseCount %ld dataEof %ld -> %lld '%s'", + // sparseCount, pFile->fExtData.eof, pFile->fSparseDataEof, + // pFile->fDirEntry.fileName); + delete[] blockList; + blockList = NULL; + delete[] indexList; + indexList = NULL; + + /* mark the extended key block as in-use */ + SetBlockUsage(pFile->fDirEntry.keyPointer, + VolumeUsage::kChunkPurposeFileStruct); + } else if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageDirectory || + pFile->fDirEntry.storageType == A2FileProDOS::kStorageVolumeDirHeader) + { + /* we already got these during the recursive descent */ + /* (could do them here if we used "fake" directory entry + for volume dir to lead off the recursion) */ + goto skip; + } else if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageSeedling || + pFile->fDirEntry.storageType == A2FileProDOS::kStorageSapling || + pFile->fDirEntry.storageType == A2FileProDOS::kStorageTree) + { + /* standard file */ + dierr = pFile->LoadBlockList(pFile->fDirEntry.storageType, + pFile->fDirEntry.keyPointer, pFile->fDirEntry.eof, + &blockCount, &blockList, &indexCount, &indexList); + if (dierr != kDIErrNone) { + LOGI(" ProDOS skipping scan '%s'", + pFile->fDirEntry.fileName); + pFile->SetQuality(A2File::kQualityDamaged); + goto skip; + } + ScanBlockList(blockCount, blockList, indexCount, indexList, + &sparseCount); + pFile->fSparseDataEof = + (di_off_t) pFile->fDirEntry.eof - sparseCount * kBlkSize; + //LOGI(" +++ sparseCount=%ld blockCount=%ld sparseDataEof=%lld '%s'", + // sparseCount, blockCount, pFile->fSparseDataEof, + // pFile->fDirEntry.fileName); + + delete[] blockList; + blockList = NULL; + delete[] indexList; + indexList = NULL; + } else { + LOGI(" ProDOS found weird storage type %d on '%s', ignoring", + pFile->fDirEntry.storageType, pFile->fDirEntry.fileName); + pFile->SetQuality(A2File::kQualityDamaged); + } + + /* + * A completely empty file written as zero blocks (as opposed to simply + * having its EOF extended, e.g. "sparse seedlings") will have zero data + * blocks but possibly an EOF that doesn't land on 512 bytes. This can + * result in a slightly negative "sparse length", which we trim to zero + * here. + */ + //if (stricmp(pFile->fDirEntry.fileName, "EMPTY.SPARSE.R") == 0) + // LOGI("wahoo"); + if (pFile->fSparseDataEof < 0) + pFile->fSparseDataEof = 0; + if (pFile->fSparseRsrcEof < 0) + pFile->fSparseRsrcEof = 0; + +skip: + pFile = (A2FileProDOS*) GetNextFile(pFile); + } + + dierr = kDIErrNone; + +bail: + return dierr; +} + +/* + * Scan a block list into the volume usage map. + */ +void DiskFSProDOS::ScanBlockList(long blockCount, uint16_t* blockList, + long indexCount, uint16_t* indexList, long* pSparseCount) +{ + assert(blockList != NULL); + assert(indexCount == 0 || indexList != NULL); + assert(pSparseCount != NULL); + + *pSparseCount = 0; + + int i; + for (i = 0; i < blockCount; i++) { + if (blockList[i] != 0) { + SetBlockUsage(blockList[i], VolumeUsage::kChunkPurposeUserData); + } else { + (*pSparseCount)++; // sparse data block + } + } + + for (i = 0; i < indexCount; i++) { + if (indexList[i] != 0) { + SetBlockUsage(indexList[i], VolumeUsage::kChunkPurposeFileStruct); + } // else sparse index block + } +} + +/* + * ProDOS disks may contain other filesystems. The typical DOS-in-ProDOS + * strategy involves marking a bunch of blocks at the end of the disc as + * "in use" without creating a file to go along with them. + * + * We look for certain types of embedded volume by looking for disk + * usage patterns and then testing those with the standard disk testing + * facilities. + */ +DIError DiskFSProDOS::ScanForSubVolumes(void) +{ + DIError dierr = kDIErrNone; + VolumeUsage::ChunkState cstate; + int firstBlock, matchCount; + int block; + + /* this is guaranteed by constraint in volume header read */ + assert(fTotalBlocks <= fpImg->GetNumBlocks()); + + if (fTotalBlocks != 1600) { + LOGI(" ProDOS ScanForSub: not 800K disk (%ld)", + fpImg->GetNumBlocks()); + return kDIErrNone; // only scan 800K disks + } + + matchCount = 0; + for (block = fTotalBlocks-1; block >= 0; block--) { + if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) { + assert(false); + return kDIErrGeneric; + } + + if (!cstate.isMarkedUsed || cstate.isUsed) + break; + + matchCount++; + } + firstBlock = block+1; + + LOGI("MATCH COUNT %d", matchCount); + if (matchCount < 35*8) // 280 blocks on 35-track floppy + return kDIErrNone; + //if (matchCount % 8 != 0) { // must have 4K tracks + // LOGI(" ProDOS ScanForSub: matchCount %d odd number", + // matchCount); + // return kDIErrNone; + //} + + /* + * Try #1: this is a single DOS 3.3 volume (200K or less). + */ + if ((matchCount % 8) == 0 && matchCount <= (50*8)) { // max 50 tracks + DiskFS* pNewFS = NULL; + DiskImg* pNewImg = NULL; + LOGI(" Sub #1: looking for single DOS volume"); + dierr = FindSubVolume(firstBlock, matchCount, &pNewImg, &pNewFS); + if (dierr == kDIErrNone) { + AddSubVolumeToList(pNewImg, pNewFS); + MarkSubVolumeBlocks(firstBlock, matchCount); + return kDIErrNone; + } + } + + + /* + * Try #2: there are multiple 140K DOS 3.3 volumes here. + * + * We may want to override their volume numbers, but it looks like + * DOS Master disks have distinct volume numbers anyway. + */ + const int kBlkCount140 = 140*2; + if ((matchCount % (kBlkCount140)) == 0) { + int i, count; + bool found = false; + + count = matchCount / kBlkCount140; + LOGI(" Sub #2: looking for %d 140K volumes", + matchCount / kBlkCount140); + + for (i = 0; i < count; i++) { + DiskFS* pNewFS = NULL; + DiskImg* pNewImg = NULL; + LOGI(" Sub #2: looking for DOS volume at (%d)", + firstBlock + i * kBlkCount140); + dierr = FindSubVolume(firstBlock + i * kBlkCount140, + kBlkCount140, &pNewImg, &pNewFS); + if (dierr == kDIErrNone) { + AddSubVolumeToList(pNewImg, pNewFS); + MarkSubVolumeBlocks(firstBlock + i * kBlkCount140, + kBlkCount140); + found = true; + } + } + if (found) + return kDIErrNone; + } + + /* + * Try #3: there are five 160K DOS 3.3 volumes here (which works out + * to exactly 800K). The first DOS volume loses early tracks as + * needed to accommodate the ProDOS directory and up to 28K of + * boot files. + * + * Because the first 160K volume starts at the front of the disk, + * we need to restrict this to non-ProDOS sub-volumes, or we'll see + * a "ghost" volume in the first position. This stuff is going to + * fail if we test for ProDOS before we check for DOS 3.3. + */ + const int kBlkCount160 = 160*2; + if (matchCount == 1537 || matchCount == 1593) { + int i, count; + bool found = false; + + count = 1600 / kBlkCount160; + LOGI(" Sub #3: looking for %d 160K volumes", + matchCount / kBlkCount160); + + for (i = 0; i < count; i++) { + DiskFS* pNewFS = NULL; + DiskImg* pNewImg = NULL; + LOGI(" Sub #3: looking for DOS volume at (%d)", + i * kBlkCount160); + dierr = FindSubVolume(i * kBlkCount160, + kBlkCount160, &pNewImg, &pNewFS); + if (dierr == kDIErrNone) { + if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) { + AddSubVolumeToList(pNewImg, pNewFS); + if (i == 0) + MarkSubVolumeBlocks(firstBlock, kBlkCount160 - firstBlock); + else + MarkSubVolumeBlocks(i * kBlkCount160, kBlkCount160); + } else { + delete pNewFS; + delete pNewImg; + pNewFS = NULL; + pNewImg = NULL; + } + } + } + if (found) + return kDIErrNone; + } + + return kDIErrNone; +} + +/* + * Look for a sub-volume at the specified location. + * + * On success, "*ppDiskImg" and "*ppDiskFS" are newly-allocated objects + * of the appropriate kind. + */ +DIError DiskFSProDOS::FindSubVolume(long blockStart, long blockCount, + DiskImg** ppDiskImg, DiskFS** ppDiskFS) +{ + DIError dierr = kDIErrNone; + DiskFS* pNewFS = NULL; + DiskImg* pNewImg = NULL; + + pNewImg = new DiskImg; + if (pNewImg == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + dierr = pNewImg->OpenImage(fpImg, blockStart, blockCount); + if (dierr != kDIErrNone) { + LOGI(" Sub: OpenImage(%ld,%ld) failed (err=%d)", + blockStart, blockCount, dierr); + goto bail; + } + + dierr = pNewImg->AnalyzeImage(); + if (dierr != kDIErrNone) { + LOGI(" Sub: analysis failed (err=%d)", dierr); + goto bail; + } + + if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown || + pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown) + { + LOGI(" Sub: unable to identify filesystem"); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + /* open a DiskFS for the sub-image */ + LOGI(" Sub DiskImg succeeded, opening DiskFS"); + pNewFS = pNewImg->OpenAppropriateDiskFS(); + if (pNewFS == NULL) { + LOGI(" Sub: OpenAppropriateDiskFS failed"); + dierr = kDIErrUnsupportedFSFmt; + goto bail; + } + + /* load the files from the sub-image */ + dierr = pNewFS->Initialize(pNewImg, kInitFull); + if (dierr != kDIErrNone) { + LOGE(" Sub: error %d reading list of files from disk", dierr); + goto bail; + } + +bail: + if (dierr != kDIErrNone) { + delete pNewFS; + delete pNewImg; + } else { + assert(pNewImg != NULL && pNewFS != NULL); + *ppDiskImg = pNewImg; + *ppDiskFS = pNewFS; + } + return dierr; +} + +/* + * Mark the blocks used by a sub-volume as in-use. + */ +void DiskFSProDOS::MarkSubVolumeBlocks(long block, long count) +{ + VolumeUsage::ChunkState cstate; + + while (count--) { + if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) { + assert(false); + return; + } + + assert(cstate.isMarkedUsed && !cstate.isUsed); + cstate.isUsed = true; + cstate.purpose = VolumeUsage::kChunkPurposeEmbedded; + if (fVolumeUsage.SetChunkState(block, &cstate) != kDIErrNone) { + assert(false); + return; + } + + block++; + } +} + +/* + * Put a ProDOS filesystem image on the specified DiskImg. + */ +DIError DiskFSProDOS::Format(DiskImg* pDiskImg, const char* volName) +{ + DIError dierr = kDIErrNone; + const bool allowLowerCase = (GetParameter(kParmProDOS_AllowLowerCase) != 0); + uint8_t blkBuf[kBlkSize]; + long formatBlocks; + + if (!IsValidVolumeName(volName)) + return kDIErrInvalidArg; + + /* set fpImg so calls that rely on it will work; we un-set it later */ + assert(fpImg == NULL); + SetDiskImg(pDiskImg); + + LOGI(" ProDOS formatting disk image"); + + /* write ProDOS blocks */ + dierr = fpImg->OverrideFormat(fpImg->GetPhysicalFormat(), + DiskImg::kFormatGenericProDOSOrd, fpImg->GetSectorOrder()); + if (dierr != kDIErrNone) + goto bail; + + formatBlocks = pDiskImg->GetNumBlocks(); + if (formatBlocks > 65536) { + LOGI(" ProDOS: rejecting format req blocks=%ld", formatBlocks); + assert(false); + return kDIErrInvalidArg; + } + if (formatBlocks == 65536) { + LOGI(" ProDOS: trimming FS size from 65536 to 65535"); + formatBlocks = 65535; + } + + /* + * We should now zero out the disk blocks, but on a 32MB volume that can + * take a little while. The blocks are zeroed for us when a disk is + * created, so this is really only needed if we're re-formatting an + * existing disk. CiderPress currently doesn't do that, so we're going + * to skip it here. + */ +// dierr = fpImg->ZeroImage(); + LOGI(" ProDOS (not zeroing blocks)"); + + /* + * Start by writing blocks 0 and 1 (the boot blocks). This is done from + * a standard boot block image that happens to be essentially the same + * for all types of disks. (Apparently these blocks are only used when + * booting 5.25" disks?) + */ + dierr = WriteBootBlocks(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Write the four-block disk volume entry. Start by writing the three + * empty ones (which only have the prev/next pointers), and finish by + * writing the first block, which has the volume directory header. + */ + int i; + memset(blkBuf, 0, sizeof(blkBuf)); + for (i = kVolHeaderBlock+1; i < kVolHeaderBlock+kFormatVolDirNumBlocks; i++) + { + PutShortLE(&blkBuf[0x00], i-1); + if (i == kVolHeaderBlock+kFormatVolDirNumBlocks-1) + PutShortLE(&blkBuf[0x02], 0); + else + PutShortLE(&blkBuf[0x02], i+1); + + dierr = fpImg->WriteBlock(i, blkBuf); + if (dierr != kDIErrNone) { + LOGI(" Format: block %d write failed (err=%d)", i, dierr); + goto bail; + } + } + + char upperName[A2FileProDOS::kMaxFileName+1]; + uint16_t lcFlags; + time_t now; + + now = time(NULL); + + /* + * Compute the lower-case flags, if desired. The test for "allowLowerCase" + * is probably bogus, because in most cases we just got created by the + * DiskImg and the app hasn't had time to set the "allow lower" flag. + * So it defaults to "enabled", which means the app needs to manually + * change the volume name to lower case. + */ + UpperCaseName(upperName, volName); + lcFlags = 0; + if (allowLowerCase) + lcFlags = GenerateLowerCaseBits(upperName, volName, false); + + PutShortLE(&blkBuf[0x00], 0); + PutShortLE(&blkBuf[0x02], kVolHeaderBlock+1); + blkBuf[0x04] = (uint8_t)(strlen(upperName) | (A2FileProDOS::kStorageVolumeDirHeader << 4)); + strncpy((char*) &blkBuf[0x05], upperName, A2FileProDOS::kMaxFileName); + PutLongLE(&blkBuf[0x16], A2FileProDOS::ConvertProDate(now)); + PutShortLE(&blkBuf[0x1a], lcFlags); + PutLongLE(&blkBuf[0x1c], A2FileProDOS::ConvertProDate(now)); + blkBuf[0x20] = 0; // GS/OS uses 5? + /* min_version is zero */ + blkBuf[0x22] = 0xe3; // access (format/rename/backup/write/read) + blkBuf[0x23] = 0x27; // entry_length: always $27 + blkBuf[0x24] = 0x0d; // entries_per_block: always $0d + /* file_count is zero - does not include volume dir */ + PutShortLE(&blkBuf[0x27], kVolHeaderBlock + kFormatVolDirNumBlocks); // bit_map_pointer + PutShortLE(&blkBuf[0x29], (uint16_t) formatBlocks); // total_blocks + dierr = fpImg->WriteBlock(kVolHeaderBlock, blkBuf); + if (dierr != kDIErrNone) { + LOGI(" Format: block %d write failed (err=%d)", + kVolHeaderBlock, dierr); + goto bail; + } + + /* check our work, and set some object fields, by reading what we wrote */ + dierr = LoadVolHeader(); + if (dierr != kDIErrNone) { + LOGI(" GLITCH: couldn't read header we just wrote (err=%d)", dierr); + goto bail; + } + + /* + * Generate the initial block usage map. The only entries in use are + * right at the start of the disk. + */ + CreateEmptyBlockMap(); + + /* don't do this -- assume they're going to call Initialize() later */ + //ScanVolBitmap(); + +bail: + SetDiskImg(NULL); // shouldn't really be set by us + return dierr; +} + + +/* + * The standard boot block found on ProDOS disks. The same thing appears + * to be written to both 5.25" and 3.5" disks, with some modifications + * made for HD images. + * + * This is block 0; block 1 is either zeroed out or filled with a repeating + * pattern. + */ +const uint8_t gFloppyBlock0[512] = { + 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x32, 0xa1, 0x86, 0x43, 0xc9, 0x03, 0x08, + 0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0, + 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e, 0xa9, + 0x03, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, 0x48, + 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x63, 0xb1, 0x48, 0x99, 0x94, 0x09, + 0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x1d, 0x09, 0xbd, 0x24, + 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x2b, 0x09, 0x9d, 0x7f, 0x0a, 0xca, 0x10, + 0xee, 0xa9, 0x09, 0x85, 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, 0xb0, + 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, 0x4c, 0x84, 0x4e, 0x84, + 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85, + 0x4b, 0x20, 0x12, 0x09, 0xb0, 0x68, 0xe6, 0x61, 0xe6, 0x61, 0xe6, 0x46, + 0xa5, 0x46, 0xc9, 0x06, 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, 0x0c, + 0xd0, 0x6d, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, 0x18, 0x6d, 0x23, 0x0c, + 0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a, + 0xf0, 0x55, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x02, 0x09, 0x29, 0x0f, 0xa8, + 0xb1, 0x4a, 0xd9, 0x02, 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0x29, 0xf0, + 0xc9, 0x20, 0xd0, 0x3b, 0xa0, 0x10, 0xb1, 0x4a, 0xc9, 0xff, 0xd0, 0x33, + 0xc8, 0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, + 0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20, + 0x12, 0x09, 0xb0, 0x17, 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, + 0xb1, 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, 0x11, 0x4a, 0xd0, 0xe7, + 0x4c, 0x00, 0x20, 0x4c, 0x3f, 0x09, 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, + 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa5, 0x60, + 0x85, 0x44, 0xa5, 0x61, 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, + 0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, 0x4b, 0xb4, 0xac, 0xa6, + 0x2b, 0x18, 0x60, 0x4c, 0xbc, 0x09, 0xa9, 0x9f, 0x48, 0xa9, 0xff, 0x48, + 0xa9, 0x01, 0xa2, 0x00, 0x4c, 0x79, 0xf4, 0x20, 0x58, 0xfc, 0xa0, 0x1c, + 0xb9, 0x50, 0x09, 0x99, 0xae, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x4d, 0x09, + 0xaa, 0xaa, 0xaa, 0xa0, 0xd5, 0xce, 0xc1, 0xc2, 0xcc, 0xc5, 0xa0, 0xd4, + 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf, + 0xd3, 0xa0, 0xaa, 0xaa, 0xaa, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b, + 0xaa, 0xbd, 0x80, 0xc0, 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, + 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, 0x29, 0x07, 0xc9, 0x04, + 0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, + 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85, + 0x27, 0xa6, 0x2b, 0xbd, 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, + 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, 0xbc, 0x88, 0xc0, 0x60, + 0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, + 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02, + 0xc6, 0x53, 0x38, 0x20, 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, + 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, 0x38, 0xc6, 0x52, 0xf0, + 0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t gHDBlock0[] = { + 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x1c, 0x09, 0x78, 0x86, 0x43, 0xc9, 0x03, + 0x08, 0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, + 0xa0, 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e, + 0xa9, 0x03, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, + 0x48, 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x5e, 0xb1, 0x48, 0x99, 0x94, + 0x09, 0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x32, 0x09, 0xbd, + 0x39, 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x40, 0x09, 0x9d, 0x7f, 0x0a, 0xca, + 0x10, 0xee, 0xa9, 0x09, 0x85, 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, + 0xb0, 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, 0x4c, 0x84, 0x4e, + 0x84, 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, + 0x85, 0x4b, 0x20, 0x27, 0x09, 0xb0, 0x66, 0xe6, 0x61, 0xe6, 0x61, 0xe6, + 0x46, 0xa5, 0x46, 0xc9, 0x06, 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, + 0x0c, 0xd0, 0x52, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, 0x18, 0x6d, 0x23, + 0x0c, 0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, + 0x0a, 0xf0, 0x71, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x20, 0x09, 0x29, 0x0f, + 0xa8, 0xb1, 0x4a, 0xd9, 0x20, 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0xa0, + 0x16, 0xb1, 0x4a, 0x4a, 0x6d, 0x1f, 0x09, 0x8d, 0x1f, 0x09, 0xa0, 0x11, + 0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, 0x85, + 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20, 0x27, + 0x09, 0xb0, 0x35, 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, 0xb1, + 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, 0x11, 0x4a, 0xd0, 0x18, 0xa2, + 0x01, 0xa9, 0x00, 0xa8, 0x91, 0x60, 0xc8, 0xd0, 0xfb, 0xe6, 0x61, 0xea, + 0xea, 0xca, 0x10, 0xf4, 0xce, 0x1f, 0x09, 0xf0, 0x07, 0xd0, 0xd8, 0xce, + 0x1f, 0x09, 0xd0, 0xca, 0x58, 0x4c, 0x00, 0x20, 0x4c, 0x47, 0x09, 0x02, + 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, 0x53, 0xa5, 0x60, 0x85, 0x44, 0xa5, + 0x61, 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, 0x3f, 0x45, 0x47, + 0x76, 0xf4, 0xd7, 0xd1, 0xb6, 0x4b, 0xb4, 0xac, 0xa6, 0x2b, 0x18, 0x60, + 0x4c, 0xbc, 0x09, 0x20, 0x58, 0xfc, 0xa0, 0x14, 0xb9, 0x58, 0x09, 0x99, + 0xb1, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x55, 0x09, 0xd5, 0xce, 0xc1, 0xc2, + 0xcc, 0xc5, 0xa0, 0xd4, 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, + 0xd2, 0xcf, 0xc4, 0xcf, 0xd3, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b, + 0xaa, 0xbd, 0x80, 0xc0, 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, + 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, 0x29, 0x07, 0xc9, 0x04, + 0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, + 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85, + 0x27, 0xa6, 0x2b, 0xbd, 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, + 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, 0xbc, 0x88, 0xc0, 0x60, + 0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, + 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02, + 0xc6, 0x53, 0x38, 0x20, 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, + 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, 0x38, 0xc6, 0x52, 0xf0, + 0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * Write the ProDOS boot blocks onto the disk image. + */ +DIError DiskFSProDOS::WriteBootBlocks(void) +{ + DIError dierr; + uint8_t block0[512]; + uint8_t block1[512]; + bool isHD; + + assert(fpImg->GetHasBlocks()); + + if (fpImg->GetNumBlocks() == 280 || fpImg->GetNumBlocks() == 1600) + isHD = false; + else + isHD = true; + + if (isHD) { + memcpy(block0, gHDBlock0, sizeof(block0)); + // repeating 0x42 0x48 pattern + int i; + uint8_t* ucp; + for (i = 0, ucp = block1; i < (int)sizeof(block1); i++) + *ucp++ = 0x42 + 6 * (i & 0x01); + } else { + memcpy(block0, gFloppyBlock0, sizeof(block0)); + memset(block1, 0, sizeof(block1)); + } + + dierr = fpImg->WriteBlock(0, block0); + if (dierr != kDIErrNone) { + LOGI(" WriteBootBlocks: block0 write failed (err=%d)", dierr); + return dierr; + } + dierr = fpImg->WriteBlock(1, block1); + if (dierr != kDIErrNone) { + LOGI(" WriteBootBlocks: block1 write failed (err=%d)", dierr); + return dierr; + } + + return kDIErrNone; +} + +/* + * Create a new, empty file. There are three different kinds of files we + * need to be able to handle: + * (1) Standard file. Create the directory entry and an empty "seedling" + * file with one block allocated. It does not appear that "sparse" + * allocation applies to seedlings. + * (2) Extended file. Create the directory entry, the extended key block, + * and allocate one seedling block for each fork. + * (3) Subdirectory. Allocate a block for the subdir and fill in the + * details in the subdir header. + * + * In all cases we need to add a new directory entry as well. + * + * By not flushing the updated block usage map and the updated directory + * block(s) until we're done, we can abort our changes at any time if we + * encounter a damaged sector or run out of disk space. We do need to be + * careful when updating our internal copies of things like file storage + * types and lengths, updating them only after everything else has + * succeeded. + * + * NOTE: if we detect an empty directory holder, "*ppNewFile" does NOT + * end up pointing at a file. + * + * NOTE: kParm_CreateUnique does *not* apply to creating subdirectories. + */ +DIError DiskFSProDOS::CreateFile(const CreateParms* pParms, A2File** ppNewFile) +{ + DIError dierr = kDIErrNone; + char* normalizedPath = NULL; + char* basePath = NULL; + char* fileName = NULL; + A2FileProDOS* pSubdir = NULL; + A2FileDescr* pOpenSubdir = NULL; + A2FileProDOS* pNewFile = NULL; + uint8_t* subdirBuf = NULL; + const bool allowLowerCase = (GetParameter(kParmProDOS_AllowLowerCase) != 0); + const bool createUnique = (GetParameter(kParm_CreateUnique) != 0); + char upperName[A2FileProDOS::kMaxFileName+1]; + char lowerName[A2FileProDOS::kMaxFileName+1]; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + assert(pParms != NULL); + assert(pParms->pathName != NULL); + assert(pParms->storageType == A2FileProDOS::kStorageSeedling || + pParms->storageType == A2FileProDOS::kStorageExtended || + pParms->storageType == A2FileProDOS::kStorageDirectory); + // kStorageVolumeDirHeader not allowed -- that's created by Format + LOGI(" ProDOS ---v--- CreateFile '%s'", pParms->pathName); + *ppNewFile = NULL; + + /* + * Normalize the pathname so that all components are ProDOS-safe + * and separated by ':'. + */ + assert(pParms->pathName != NULL); + dierr = DoNormalizePath(pParms->pathName, pParms->fssep, + &normalizedPath); + if (dierr != kDIErrNone) + goto bail; + assert(normalizedPath != NULL); + + /* + * Split the base path and filename apart. + */ + char* cp; + cp = strrchr(normalizedPath, A2FileProDOS::kFssep); + if (cp == NULL) { + assert(basePath == NULL); + fileName = normalizedPath; + } else { + fileName = new char[strlen(cp+1) +1]; + strcpy(fileName, cp+1); + *cp = '\0'; + basePath = normalizedPath; + } + normalizedPath = NULL; // either fileName or basePath points here now + + assert(fileName != NULL); + //LOGI(" ProDOS normalized to '%s':'%s'", + // basePath == NULL ? "" : basePath, fileName); + + /* + * Open the base path. If it doesn't exist, create it recursively. + */ + if (basePath != NULL) { + LOGI(" ProDOS Creating '%s' in '%s'", fileName, basePath); + /* open the named subdir, creating it if it doesn't exist */ + pSubdir = (A2FileProDOS*)GetFileByName(basePath); + if (pSubdir == NULL) { + LOGI(" ProDOS Creating subdir '%s'", basePath); + A2File* pNewSub; + CreateParms newDirParms; + newDirParms.pathName = basePath; + newDirParms.fssep = A2FileProDOS::kFssep; + newDirParms.storageType = A2FileProDOS::kStorageDirectory; + newDirParms.fileType = kTypeDIR; // 0x0f + newDirParms.auxType = 0; + newDirParms.access = 0xe3; // unlocked, backup bit set + newDirParms.createWhen = newDirParms.modWhen = time(NULL); + dierr = this->CreateFile(&newDirParms, &pNewSub); + if (dierr != kDIErrNone) + goto bail; + assert(pNewSub != NULL); + + pSubdir = (A2FileProDOS*) pNewSub; + } + + /* + * And now the annoying part. We need to reconstruct basePath out + * of the filenames actually present, rather than relying on the + * argument passed in. That's because some directories might have + * lower-case flags and some might not, and we do case-insensitive + * comparisons. It's not crucial for our inner workings, but the + * linear file list in the DiskFS should have accurate strings. + * (It'll work just fine, but the display might show the wrong values + * for parent directories until they reload the disk.) + * + * On the bright side, we know exactly how long the string needs + * to be, so we can just stomp on it in place. Assuming, of course, + * that the filename created matches up with what the filename + * normalizer came up with, which we can guarantee since (a) everybody + * uses the same normalizer and (b) the "uniqueify" stuff doesn't + * kick in for subdirs because we wouldn't be creating a new subdir + * if it didn't already exist. + * + * This is essentially the same as RegeneratePathName(), but that's + * meant for a situation where the filename already exists. + */ + A2FileProDOS* pBaseDir = pSubdir; + int basePathLen = strlen(basePath); + while (!pBaseDir->IsVolumeDirectory()) { + const char* fixedName = pBaseDir->GetFileName(); + int fixedLen = strlen(fixedName); + if (fixedLen > basePathLen) { + assert(false); + break; + } + assert(basePathLen == fixedLen || + *(basePath + (basePathLen-fixedLen-1)) == kDIFssep); + memcpy(basePath + (basePathLen-fixedLen), fixedName, fixedLen); + basePathLen -= fixedLen+1; + + pBaseDir = (A2FileProDOS*) pBaseDir->GetParent(); + assert(pBaseDir != NULL); + } + // check the math + if (pSubdir->IsVolumeDirectory()) + assert(basePathLen == 0); + else + assert(basePathLen == -1); + } else { + /* open the volume directory */ + LOGI(" ProDOS Creating '%s' in volume dir", fileName); + /* volume dir must be first in the list */ + pSubdir = (A2FileProDOS*) GetNextFile(NULL); + assert(pSubdir != NULL); + assert(pSubdir->IsVolumeDirectory()); + } + if (pSubdir == NULL) { + LOGI(" ProDOS Unable to open subdir '%s'", basePath); + dierr = kDIErrFileNotFound; + goto bail; + } + + /* + * Load the block usage map into memory. All changes, to the end of this + * function, are made to the in-memory copy and can be "undone" by simply + * throwing the temporary map away. + */ + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + return dierr; + + /* + * Load the subdir or volume dir into memory, and alloc a new directory + * entry. + */ + dierr = pSubdir->Open(&pOpenSubdir, false); + if (dierr != kDIErrNone) + goto bail; + + uint8_t* dirEntryPtr; + long dirLen; + uint16_t dirBlock, dirKeyBlock; + int dirEntrySlot; + dierr = AllocDirEntry(pOpenSubdir, &subdirBuf, &dirLen, &dirEntryPtr, + &dirKeyBlock, &dirEntrySlot, &dirBlock); + if (dierr != kDIErrNone) + goto bail; + + assert(subdirBuf != NULL); + assert(dirLen > 0); + assert(dirKeyBlock > 0); + assert(dirEntrySlot >= 0); + assert(dirBlock > 0); + + /* + * Create a copy of the filename with everything in upper case and spaces + * changed to periods. + */ + UpperCaseName(upperName, fileName); + + /* + * Make the name unique within the current directory. This requires + * appending digits until the name doesn't match any others. + * + * The filename buffer ("upperName") must be able to hold kMaxFileName+1 + * chars. It will be modified in place. + */ + if (createUnique && + pParms->storageType != A2FileProDOS::kStorageDirectory) + { + MakeFileNameUnique(subdirBuf, dirLen, upperName); + } else { + /* check to see if it already exists */ + if (NameExistsInDir(subdirBuf, dirLen, upperName)) { + if (pParms->storageType == A2FileProDOS::kStorageDirectory) + dierr = kDIErrDirectoryExists; + else + dierr = kDIErrFileExists; + goto bail; + } + } + + /* + * Allocate file storage and initialize: + * - For directory, a single block with the directory header. + * - For seedling, an empty block. + * - For extended, an extended key block entry and two empty blocks. + */ + long keyBlock; + int blocksUsed; + int newEOF; + keyBlock = -1; + blocksUsed = newEOF = -1; + + dierr = AllocInitialFileStorage(pParms, upperName, dirBlock, + dirEntrySlot, &keyBlock, &blocksUsed, &newEOF); + if (dierr != kDIErrNone) + goto bail; + + assert(blocksUsed > 0); + assert(keyBlock > 0); + assert(newEOF >= 0); + + /* + * Fill out the newly-created directory entry pointed to by "dirEntryPtr". + * + * ProDOS filenames are always stored in upper case. ProDOS 8 v1.8 and + * later allow lower-case names with '.' converting to ' '. We optionally + * set the flags here, using the original file name to decide which parts + * are lower case. (Some parts of the original may have been stomped + * when the name was made unique, so we need to watch for that.) + */ + dirEntryPtr[0x00] = (uint8_t)((pParms->storageType << 4) | strlen(upperName)); + strncpy((char*) &dirEntryPtr[0x01], upperName, A2FileProDOS::kMaxFileName); + if (pParms->fileType >= 0 && pParms->fileType <= 0xff) + dirEntryPtr[0x10] = (uint8_t) pParms->fileType; + else + dirEntryPtr[0x10] = 0; // HFS long type? + PutShortLE(&dirEntryPtr[0x11], (uint16_t) keyBlock); + PutShortLE(&dirEntryPtr[0x13], blocksUsed); + PutShortLE(&dirEntryPtr[0x15], newEOF); + dirEntryPtr[0x17] = 0; // high byte of EOF + PutLongLE(&dirEntryPtr[0x18], A2FileProDOS::ConvertProDate(pParms->createWhen)); + if (allowLowerCase) { + uint16_t lcBits; + lcBits = GenerateLowerCaseBits(upperName, fileName, false); + GenerateLowerCaseName(upperName, lowerName, lcBits, false); + lowerName[strlen(upperName)] = '\0'; + + PutShortLE(&dirEntryPtr[0x1c], lcBits); + } else { + strcpy(lowerName, upperName); + PutShortLE(&dirEntryPtr[0x1c], 0); // version, min_version + } + dirEntryPtr[0x1e] = pParms->access; + if (pParms->auxType >= 0 && pParms->auxType <= 0xffff) + PutShortLE(&dirEntryPtr[0x1f], (uint16_t) pParms->auxType); + else + PutShortLE(&dirEntryPtr[0x1f], 0); + PutLongLE(&dirEntryPtr[0x21], A2FileProDOS::ConvertProDate(pParms->modWhen)); + PutShortLE(&dirEntryPtr[0x25], dirKeyBlock); + + /* + * Write updated directory. If this succeeds, we can no longer undo + * what we have done by simply bailing. If this fails partway through, + * we might have a corrupted disk, so it's best to ensure that it's not + * going to fail before we call. + * + * Assuming this isn't a nibble image with I/O errors, the only way we + * can really fail is by running out of disk space. The block has been + * pre-allocated, so this should always work. + */ + dierr = pOpenSubdir->Write(subdirBuf, dirLen); + if (dierr != kDIErrNone) { + LOGI(" ProDOS directory write failed (dirLen=%ld)", dirLen); + goto bail; + } + + /* + * Flush updated block usage map. + */ + dierr = SaveVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Success! + * + * Create an A2File entry for this, and add it to the list. The calls + * below will re-process some of what we just created, which is slightly + * inefficient but helps guarantee that we aren't creating bogus data + * structures that won't match what we see when the disk is reloaded. + * + * - Regen or update internal VolumeUsage map?? Throw it away or mark + * it as invalid? + */ + pNewFile = new A2FileProDOS(this); + + A2FileProDOS::DirEntry* pEntry; + pEntry = &pNewFile->fDirEntry; + + A2FileProDOS::InitDirEntry(pEntry, dirEntryPtr); + + pNewFile->fParentDirBlock = dirBlock; + pNewFile->fParentDirIdx = (dirEntrySlot-1) % kEntriesPerBlock; + pNewFile->fSparseDataEof = 0; + pNewFile->fSparseRsrcEof = 0; + + /* + * Get the properly-cased filename for the file list. We already have + * a name in "lowerName", but it doesn't take AppleWorks aux type + * case stuff into account. If necessary, deal with it now. + */ + if (A2FileProDOS::UsesAppleWorksAuxType(pNewFile->fDirEntry.fileType)) { + DiskFSProDOS::GenerateLowerCaseName(pNewFile->fDirEntry.fileName, + lowerName, pNewFile->fDirEntry.auxType, true); + } + pNewFile->SetPathName(basePath == NULL ? "" : basePath, lowerName); + + if (pEntry->storageType == A2FileProDOS::kStorageExtended) { + dierr = ReadExtendedInfo(pNewFile); + if (dierr != kDIErrNone) { + LOGI(" ProDOS GLITCH: readback of extended block failed!"); + delete pNewFile; + goto bail; + } + } + + pNewFile->SetParent(pSubdir); + //pNewFile->Dump(); + + /* + * Because we're hierarchical, and we guarantee that the contents of + * subdirectories are grouped together, we must insert the file into an + * appropriate place in the list rather than just throwing it onto the + * end. + * + * The proper location for the new file in the linear list is after the + * previous file in our subdir. If we're the first item in the subdir, + * we get added right after the parent. If not, we need to scan, starting + * from the parent, for an entry in the file list whose key block pointer + * matches that of the previous item in the list. + * + * We wouldn't be this far if the disk were damaged, so we don't have to + * worry too much about weirdness. The directory entry allocator always + * returns the first available, so we know the previous entry is valid. + */ + uint8_t* prevDirEntryPtr; + prevDirEntryPtr = GetPrevDirEntry(subdirBuf, dirEntryPtr); + if (prevDirEntryPtr == NULL) { + /* previous entry is volume or subdir header */ + InsertFileInList(pNewFile, pNewFile->GetParent()); + LOGI("Inserted '%s' after '%s'", + pNewFile->GetPathName(), pNewFile->GetParent()->GetPathName()); + } else { + /* dig out the key block pointer and find the matching file */ + uint16_t prevKeyBlock; + assert((prevDirEntryPtr[0x00] & 0xf0) != 0); // verify storage type + prevKeyBlock = GetShortLE(&prevDirEntryPtr[0x11]); + A2File* pPrev; + pPrev = FindFileByKeyBlock(pNewFile->GetParent(), prevKeyBlock); + if (pPrev == NULL) { + /* should be impossible! */ + assert(false); + AddFileToList(pNewFile); + } else { + /* insert the new file in the list after the previous file */ + InsertFileInList(pNewFile, pPrev); + } + } +// LOGI("LIST NOW:"); +// DumpFileList(); + + *ppNewFile = pNewFile; + pNewFile = NULL; + +bail: + delete pNewFile; + if (pOpenSubdir != NULL) + pOpenSubdir->Close(); // writes updated dir entry in parent dir + FreeVolBitmap(); + delete[] normalizedPath; + delete[] subdirBuf; + delete[] fileName; + delete[] basePath; + LOGI(" ProDOS ---^--- CreateFile '%s' DONE", pParms->pathName); + return dierr; +} + +/* + * Run through the DiskFS file list, looking for an entry with a matching + * key block. + */ +A2File* DiskFSProDOS::FindFileByKeyBlock(A2File* pStart, uint16_t keyBlock) +{ + while (pStart != NULL) { + A2FileProDOS* pPro = (A2FileProDOS*) pStart; + + if (pPro->fDirEntry.keyPointer == keyBlock) + return pStart; + + pStart = GetNextFile(pStart); + } + + return NULL; +} + +/* + * Allocate the initial storage (key blocks, directory header) for a new file. + * + * Output values are the key block for the new file, the number of blocks + * used, and an EOF value. + * + * "upperName" is the upper-case name for the file. "dirBlock" and + * "dirEntrySlot" refer to the entry in the higher-level directory for this + * file, and are only needed when creating a new subdir (because the first + * entry in a subdir points to its entry in the parent dir). + */ +DIError DiskFSProDOS::AllocInitialFileStorage(const CreateParms* pParms, + const char* upperName, uint16_t dirBlock, int dirEntrySlot, + long* pKeyBlock, int* pBlocksUsed, int* pNewEOF) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + long keyBlock; + int blocksUsed; + int newEOF; + + blocksUsed = -1; + keyBlock = -1; + newEOF = 0; + memset(blkBuf, 0, sizeof(blkBuf)); + + if (pParms->storageType == A2FileProDOS::kStorageSeedling) { + keyBlock = AllocBlock(); + if (keyBlock == -1) { + dierr = kDIErrDiskFull; + goto bail; + } + blocksUsed = 1; + + /* write zeroed block */ + dierr = fpImg->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + } else if (pParms->storageType == A2FileProDOS::kStorageExtended) { + long dataBlock, rsrcBlock; + + dataBlock = AllocBlock(); + rsrcBlock = AllocBlock(); + keyBlock = AllocBlock(); + if (dataBlock < 0 || rsrcBlock < 0 || keyBlock < 0) { + dierr = kDIErrDiskFull; + goto bail; + } + blocksUsed = 3; + newEOF = kBlkSize; + + /* write zeroed block */ + dierr = fpImg->WriteBlock(dataBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + dierr = fpImg->WriteBlock(rsrcBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + /* fill in extended key block details */ + blkBuf[0x00] = blkBuf[0x100] = A2FileProDOS::kStorageSeedling; + PutShortLE(&blkBuf[0x01], (uint16_t) dataBlock); + PutShortLE(&blkBuf[0x101], (uint16_t) rsrcBlock); + blkBuf[0x03] = blkBuf[0x103] = 1; // blocks used (lo byte) + /* 3 bytes at 0x05 hold EOF, currently 0 */ + + dierr = fpImg->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + } else if (pParms->storageType == A2FileProDOS::kStorageDirectory) { + keyBlock = AllocBlock(); + if (keyBlock == -1) { + dierr = kDIErrDiskFull; + goto bail; + } + blocksUsed = 1; + newEOF = kBlkSize; + + /* fill in directory header fields */ + // 0x00: prev, set to zero + // 0x02: next, set to zero + blkBuf[0x04] = (uint8_t)((A2FileProDOS::kStorageSubdirHeader << 4) | strlen(upperName)); + strncpy((char*) &blkBuf[0x05], upperName, A2FileProDOS::kMaxFileName); + blkBuf[0x14] = 0x76; // 0x75 under old P8, 0x76 under GS/OS + PutLongLE(&blkBuf[0x1c], A2FileProDOS::ConvertProDate(pParms->createWhen)); + blkBuf[0x20] = 5; // 0 under 1.0, 3 under v1.4?, 5 under GS/OS + blkBuf[0x21] = 0; + blkBuf[0x22] = pParms->access; + blkBuf[0x23] = kEntryLength; + blkBuf[0x24] = kEntriesPerBlock; + PutShortLE(&blkBuf[0x25], 0); // file count + PutShortLE(&blkBuf[0x27], dirBlock); + blkBuf[0x29] = (uint8_t) dirEntrySlot; + blkBuf[0x2a] = kEntryLength; // the parent dir's entry length + + dierr = fpImg->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + } else { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + *pKeyBlock = keyBlock; + *pBlocksUsed = blocksUsed; + *pNewEOF = newEOF; + +bail: + return dierr; +} + +/* + * Scan for damaged files and mysterious or conflicting block usage map + * entries. + * + * Appends some entries to the DiskImg notes, so this should only be run + * once per DiskFS. + * + * This function doesn't set anything; it's effectively "const" except + * that LoadVolBitmap is inherently non-const. + * + * Returns "true" if disk appears to be perfect, "false" otherwise. + */ +bool DiskFSProDOS::CheckDiskIsGood(void) +{ + DIError dierr; + bool result = true; + int i; + + if (fEarlyDamage) + result = false; + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * Check the system blocks to see if any of them are marked as free. + * If so, refuse to write to this disk. + */ + if (!GetBlockUseEntry(0) || !GetBlockUseEntry(1)) { + fpImg->AddNote(DiskImg::kNoteWarning, "Block 0/1 marked as free."); + result = false; + } + for (i = GetNumBitmapBlocks(); i > 0; i--) { + if (!GetBlockUseEntry(fBitMapPointer + i -1)) { + fpImg->AddNote(DiskImg::kNoteWarning, + "One or more bitmap blocks are marked as free."); + result = false; + break; + } + } + + /* + * Check for used blocks that aren't marked in-use. + * + * This requires that VolumeUsage be accurate. Since this function is + * only run during initial startup, any later deviation between VU and + * the block use map is irrelevant. + */ + VolumeUsage::ChunkState cstate; + long blk, notMarked, extraUsed, conflicts; + notMarked = extraUsed = conflicts = 0; + for (blk = 0; blk < fVolumeUsage.GetNumChunks(); blk++) { + dierr = fVolumeUsage.GetChunkState(blk, &cstate); + if (dierr != kDIErrNone) { + fpImg->AddNote(DiskImg::kNoteWarning, + "Internal volume usage error on blk=%ld.", blk); + result = false; + goto bail; + } + + if (cstate.isUsed && !cstate.isMarkedUsed) + notMarked++; + if (!cstate.isUsed && cstate.isMarkedUsed) + extraUsed++; + if (cstate.purpose == VolumeUsage::kChunkPurposeConflict) + conflicts++; + } + if (extraUsed > 0) { + fpImg->AddNote(DiskImg::kNoteInfo, + "%ld block%s marked used but not part of any file.", + extraUsed, extraUsed == 1 ? " is" : "s are"); + // not a problem, really + } + if (notMarked > 0) { + fpImg->AddNote(DiskImg::kNoteWarning, + "%ld block%s used by files but not marked used.", + notMarked, notMarked == 1 ? " is" : "s are"); + result = false; // very bad -- any change could trash files + } + if (conflicts > 0) { + fpImg->AddNote(DiskImg::kNoteWarning, + "%ld block%s used by more than one file.", + conflicts, conflicts == 1 ? " is" : "s are"); + result = false; // kinda bad -- file deletion leads to trouble + } + + /* + * Check for bits set past the end of the actually-needed bits. For + * some reason P8 and GS/OS both examine these bits, and GS/OS will + * freak out completely and claim the disk is unrecognizeable ("would + * you like to format?") if they're set. + */ + if (ScanForExtraEntries()) { + fpImg->AddNote(DiskImg::kNoteWarning, + "Blocks past the end of the disk are marked 'in use' in the" + " volume bitmap."); + /* don't flunk the disk just for this */ + } + + /* + * 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; + } + +bail: + FreeVolBitmap(); + return result; +} + +/* + * Test a string for validity as a ProDOS volume name. Syntax is the same as + * ProDOS file names, but we also disallow spaces. + */ +/*static*/ bool DiskFSProDOS::IsValidVolumeName(const char* name) +{ + assert((int) A2FileProDOS::kMaxFileName == (int) kMaxVolumeName); + if (!IsValidFileName(name)) + return false; + while (*name != '\0') { + if (*name++ == ' ') + return false; + } + return true; +} + +/* + * Test a string for validity as a ProDOS file name. Names may be 1-15 + * characters long, must start with a letter, and may contain letters and + * digits. + * + * Lower case and spaces (a/k/a lower-case '.') are accepted. Trailing + * spaces are not allowed. + */ +/*static*/ bool DiskFSProDOS::IsValidFileName(const char* name) +{ + if (name == NULL) { + assert(false); + return false; + } + + /* must be 1-15 characters long */ + if (name[0] == '\0') + return false; + if (strlen(name) > A2FileProDOS::kMaxFileName) + return false; + + /* must begin with letter; this also catches zero-length filenames */ + if (toupper(name[0]) < 'A' || toupper(name[0]) > 'Z') + return false; + + /* no trailing spaces */ + if (name[strlen(name)-1] == ' ') + return false; + + /* must be A-Za-z 0-9 '.' ' ' */ + name++; + while (*name != '\0') { + if (!( (toupper(*name) >= 'A' && toupper(*name) <= 'Z') || + (*name >= '0' && *name <= '9') || + (*name == '.') || + (*name == ' ') + )) + { + return false; + } + + name++; + } + + return true; +} + +/* + * Generate lower case flags by comparing "upperName" to "lowerName". + * + * It's okay for "lowerName" to be longer than "upperName". The extra chars + * are just ignored. Similarly, "lowerName" does not need to be + * null-terminated. "lowerName" does need to point to storage with at least + * as many valid bytes as "upperName", though, or we could crash. + * + * Returns the mask to use in a ProDOS dir. If "forAppleWorks" is set to + * "true", the mask is modified for use with an AppleWorks aux type. + */ +/*static*/ uint16_t DiskFSProDOS::GenerateLowerCaseBits(const char* upperName, + const char* lowerName, bool forAppleWorks) +{ + uint16_t caseMask = 0x8000; + uint16_t caseBit = 0x8000; + int len, i; + char lowch; + + len = strlen(upperName); + assert(len <= A2FileProDOS::kMaxFileName); + + for (i = 0; i < len; i++) { + caseBit >>= 1; + lowch = A2FileProDOS::NameToLower(upperName[i]); + if (lowch == lowerName[i]) + caseMask |= caseBit; + } + + if (forAppleWorks) { + uint16_t adjusted; + caseMask <<= 1; + adjusted = caseMask << 8 | caseMask >> 8; + return adjusted; + } else { + if (caseMask == 0x8000) + return 0; // all upper case, don't freak out pre-v1.8 + else + return caseMask; + } +} + +/* + * Generate the lower-case version of a ProDOS filename, using the supplied + * lower case flags. "lowerName" must be able to hold 15 chars (enough for + * a filename or volname). + * + * The string will NOT be null-terminated, but the output buffer will be padded + * with NULs out to the maximum filename len. This makes it suitable for + * copying directly into directory block buffers. + * + * It's okay to pass the same buffer for "upperName" and "lowerName". + * + * "lcFlags" is either ProDOS directory flags or AppleWorks aux type flags, + * depending on the value of "fromAppleWorks". + */ +/*static*/ void DiskFSProDOS::GenerateLowerCaseName(const char* upperName, + char* lowerName, uint16_t lcFlags, bool fromAppleWorks) +{ + int nameLen = strlen(upperName); + int bit; + assert(nameLen <= A2FileProDOS::kMaxFileName); + + if (fromAppleWorks) { + /* handle AppleWorks lower-case-in-auxtype */ + uint16_t caseMask = // swap bytes + (lcFlags << 8) | (lcFlags >> 8); + for (bit = 0; bit < nameLen ; bit++) { + if ((caseMask & 0x8000) != 0) + lowerName[bit] = A2FileProDOS::NameToLower(upperName[bit]); + else + lowerName[bit] = upperName[bit]; + caseMask <<= 1; + } + for ( ; bit < A2FileProDOS::kMaxFileName; bit++) + lowerName[bit] = '\0'; + } else { + /* handle lower-case conversion; see GS/OS tech note #8 */ + if (lcFlags != 0 && !(lcFlags & 0x8000)) { + // Should be zero or 0x8000 plus other bits; shouldn't be + // bunch of bits without 0x8000 or 0x8000 by itself. Not + // really a problem, just unexpected. + assert(false); + memcpy(lowerName, upperName, A2FileProDOS::kMaxFileName); + return; + } + for (bit = 0; bit < nameLen; bit++) { + lcFlags <<= 1; + if ((lcFlags & 0x8000) != 0) + lowerName[bit] = A2FileProDOS::NameToLower(upperName[bit]); + else + lowerName[bit] = upperName[bit]; + } + } + for ( ; bit < A2FileProDOS::kMaxFileName; bit++) + lowerName[bit] = '\0'; +} + +/* + * Normalize a ProDOS path. Invokes DoNormalizePath and handles the buffer + * management (if the normalized path doesn't fit in "*pNormalizedBufLen" + * bytes, we set "*pNormalizedBufLen to the required length). + * + * This is invoked from the generalized "add" function in CiderPress, which + * doesn't want to understand the ins and outs of ProDOS pathnames. + */ +DIError DiskFSProDOS::NormalizePath(const char* path, char fssep, + char* normalizedBuf, int* pNormalizedBufLen) +{ + DIError dierr = kDIErrNone; + char* normalizedPath = NULL; + int len; + + assert(pNormalizedBufLen != NULL); + assert(normalizedBuf != NULL || *pNormalizedBufLen == 0); + + dierr = DoNormalizePath(path, fssep, &normalizedPath); + if (dierr != kDIErrNone) + goto bail; + + assert(normalizedPath != NULL); + len = strlen(normalizedPath); + if (normalizedBuf == NULL || *pNormalizedBufLen <= len) { + /* too short */ + dierr = kDIErrDataOverrun; + } else { + /* fits */ + strcpy(normalizedBuf, normalizedPath); + } + + *pNormalizedBufLen = len+1; // alloc room for the '\0' + +bail: + delete[] normalizedPath; + return dierr; +} + +/* + * Normalize a ProDOS path. This requires separating each path component + * out, making it ProDOS-compliant, and then putting it back in. + * The fssep could be anything, so we need to change it to kFssep. + * + * We don't try to identify duplicates here. If more than one subdir maps + * to the same thing, then you're just going to end up with lots of files + * in the same subdir. If this is unacceptable then it will have to be + * fixed at a higher level. + * + * Lower-case letters and spaces are left in place. They're expected to + * be removed later. + * + * The caller must delete[] "*pNormalizedPath". + */ +DIError DiskFSProDOS::DoNormalizePath(const char* path, char fssep, + char** pNormalizedPath) +{ + DIError dierr = kDIErrNone; + char* workBuf = NULL; + char* partBuf = NULL; + char* outputBuf = NULL; + char* start; + char* end; + char* outPtr; + + assert(path != NULL); + workBuf = new char[strlen(path)+1]; + partBuf = new char[strlen(path)+1 +1]; // need +1 for prepending letter + outputBuf = new char[strlen(path) * 2]; + if (workBuf == NULL || partBuf == NULL || outputBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + strcpy(workBuf, path); + outputBuf[0] = '\0'; + + outPtr = outputBuf; + start = workBuf; + while (*start != '\0') { + //char* origStart = start; // need for debug msg + int partIdx; + + if (fssep == '\0') { + end = NULL; + } else { + end = strchr(start, fssep); + if (end != NULL) + *end = '\0'; + } + partIdx = 0; + + /* + * Skip over everything up to the first letter. If we encounter a + * number or a '\0' first, insert a leading letter. + */ + while (*start != '\0') { + if (toupper(*start) >= 'A' && toupper(*start) <= 'Z') { + partBuf[partIdx++] = *start++; + break; + } + if (*start >= '0' && *start <= '9') { + partBuf[partIdx++] = 'A'; + break; + } + + start++; + } + if (partIdx == 0) + partBuf[partIdx++] = 'Z'; + + /* + * Continue copying, dropping all illegal chars. + */ + while (*start != '\0') { + if ((toupper(*start) >= 'A' && toupper(*start) <= 'Z') || + (*start >= '0' && *start <= '9') || + (*start == '.') || + (*start == ' ') ) + { + partBuf[partIdx++] = *start++; + } else { + start++; + } + } + + /* + * Truncate at 15 chars, preserving anything that looks like a + * filename extension. "partIdx" represents the length of the + * string at this point. "partBuf" holds the string, which we + * want to null-terminate before proceeding. + */ + partBuf[partIdx] = '\0'; + if (partIdx > A2FileProDOS::kMaxFileName) { + const char* pDot = strrchr(partBuf, '.'); + //int DEBUGDOTLEN = pDot - partBuf; + if (pDot != NULL && partIdx - (pDot-partBuf) <= kMaxExtensionLen) { + int dotLen = partIdx - (pDot-partBuf); + memmove(partBuf + (A2FileProDOS::kMaxFileName - dotLen), + pDot, dotLen); // don't use memcpy, move might overlap + } + partIdx = A2FileProDOS::kMaxFileName; + } + partBuf[partIdx] = '\0'; + + //LOGI(" ProDOS Converted component '%s' to '%s'", + // origStart, partBuf); + + if (outPtr != outputBuf) + *outPtr++ = A2FileProDOS::kFssep; + strcpy(outPtr, partBuf); + outPtr += partIdx; + + /* + * Continue with next segment. + */ + if (end == NULL) + break; + start = end+1; + } + + *outPtr = '\0'; + + LOGI(" ProDOS Converted path '%s' to '%s' (fssep='%c')", + path, outputBuf, fssep); + assert(*outputBuf != '\0'); + + *pNormalizedPath = outputBuf; + outputBuf = NULL; + +bail: + delete[] workBuf; + delete[] partBuf; + delete[] outputBuf; + return dierr; +} + +/* + * Create a copy of the filename with everything in upper case and spaces + * changed to periods. + * + * "upperName" must be a buffer that holds at least kMaxFileName+1 characters. + * If "name" is longer than kMaxFileName, it will be truncated. + */ +void DiskFSProDOS::UpperCaseName(char* upperName, const char* name) +{ + int i; + + for (i = 0; i < A2FileProDOS::kMaxFileName; i++) { + char ch = name[i]; + if (ch == '\0') + break; + else if (ch == ' ') + upperName[i] = '.'; + else + upperName[i] = toupper(ch); + } + + /* null terminate with prejudice -- we memcpy this buffer into subdirs */ + for ( ; i <= A2FileProDOS::kMaxFileName; i++) + upperName[i] = '\0'; +} + +/* + * Allocate a new directory entry. We start by reading the entire thing + * into memory. If the current set of allocated directory blocks is full, + * and we're not operating on the volume dir, we extend the directory. + * + * This just allocates the space; it does not fill in any details, except + * for the prev/next block pointers and the file count in the header. (One + * small exception: if we have to extend the directory, the "prev/next" fields + * of the new block will be filled in.) + * + * The volume in-use block map must be loaded before this is called. If + * this needs to extend the directory, a new block will be allocated. + * + * Returns a pointer to the new entry, and a whole bunch of other stuff: + * "ppDir" gets a pointer to newly-allocated memory with the whole directory + * "pDirLen" is the size of the *ppDir buffer + * "ppDirEntry" gets a memory pointer to the start of the created entry + * "pDirKeyBlock" gets the key block of the directory as a whole + * "pDirEntrySlot" gets the slot number within the directory block (first is 1) + * "pDirBlock" gets the actual block in which the created entry resides + * + * The caller should Write the entire thing to "pOpenSubdir" after filling + * in the new details for the entry. + * + * Possible reasons for failure: disk is out of space, volume dir is out + * of space, pOpenSubdir is screwy. + * + * We guarantee that we will return the first available entry in the current + * directory. + */ +DIError DiskFSProDOS::AllocDirEntry(A2FileDescr* pOpenSubdir, uint8_t** ppDir, + long* pDirLen, uint8_t** ppDirEntry, uint16_t* pDirKeyBlock, + int* pDirEntrySlot, uint16_t* pDirBlock) +{ + assert(pOpenSubdir != NULL); + *ppDirEntry = NULL; + *pDirLen = -1; + *pDirKeyBlock = 0; + *pDirEntrySlot = -1; + *pDirBlock = 0; + + DIError dierr = kDIErrNone; + uint8_t* dirBuf = NULL; + long dirLen; + A2FileProDOS* pFile; + long newBlock = -1; + + /* + * Load the subdir into memory. + */ + pFile = (A2FileProDOS*) pOpenSubdir->GetFile(); + dirLen = (long) pFile->GetDataLength(); + if (dirLen < 512 || (dirLen % 512) != 0) { + LOGI(" ProDOS GLITCH: funky dir EOF %ld (quality=%d)", + dirLen, pFile->GetQuality()); + dierr = kDIErrBadFile; + goto bail; + } + dirBuf = new uint8_t[dirLen]; + if (dirBuf == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + dierr = pOpenSubdir->Read(dirBuf, dirLen); + if (dierr != kDIErrNone) + goto bail; + + if (dirBuf[0x23] != kEntryLength || + dirBuf[0x24] != kEntriesPerBlock) + { + LOGI(" ProDOS GLITCH: funky entries per block %d", dirBuf[0x24]); + dierr = kDIErrBadDirectory; + goto bail; + } + + /* + * Find the first available entry (storage_type is zero). We need to + * step through this by blocks, because the data is block-oriented. + * If we run off the end of the last block, (re)alloc a new one. + */ + uint8_t* pDirEntry; + int blockIdx; + int entryIdx; + + pDirEntry = NULL; // make the compiler happy + entryIdx = -1; // make the compiler happy + + for (blockIdx = 0; blockIdx < dirLen / 512; blockIdx++) { + pDirEntry = dirBuf + 512*blockIdx + 4; // skip 4 bytes of prev/next + + for (entryIdx = 0; entryIdx < kEntriesPerBlock; + entryIdx++, pDirEntry += kEntryLength) + { + if ((pDirEntry[0x00] & 0xf0) == 0) { + LOGI(" ProDOS Found empty dir entry in slot %d", entryIdx); + break; // found one; break out of inner loop + } + } + if (entryIdx < kEntriesPerBlock) + break; // out of outer loop + } + if (blockIdx == dirLen / 512) { + if (((dirBuf[0x04] & 0xf0) >> 4) == A2FileProDOS::kStorageVolumeDirHeader) + { + /* can't extend the volume dir */ + dierr = kDIErrVolumeDirFull; + goto bail; + } + + LOGI(" ProDOS ran out of directory space, adding another block"); + + /* + * Request an unused block from the system. Point the "next" pointer + * in the last block at it, so that when we go to write this dir + * we will know where to put it. + */ + uint8_t* pBlock; + pBlock = dirBuf + 512 * (blockIdx-1); + if (pBlock[0x02] != 0) { + LOGI(" ProDOS GLITCH: adding to block with nonzero next ptr!"); + dierr = kDIErrBadDirectory; + goto bail; + } + + newBlock = AllocBlock(); + if (newBlock < 0) { + dierr = kDIErrDiskFull; + goto bail; + } + + PutShortLE(&pBlock[0x02], (uint16_t) newBlock); // set "next" + + /* + * Extend our memory buffer to hold the new entry. + */ + uint8_t* newSpace = new uint8_t[dirLen + 512]; + if (newSpace == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + memcpy(newSpace, dirBuf, dirLen); + memset(newSpace + dirLen, 0, 512); + delete[] dirBuf; + dirBuf = newSpace; + dirLen += 512; + + /* + * Set the "prev" pointer in the new block to point at the last + * block of the existing directory structure. + */ + long lastBlock; + dierr = pOpenSubdir->GetStorage(blockIdx-1, &lastBlock); + if (dierr != kDIErrNone) + goto bail; + pBlock = dirBuf + 512 * blockIdx; + PutShortLE(&pBlock[0x00], (uint16_t) lastBlock); // set "prev" + assert(GetShortLE(&pBlock[0x02]) == 0); // "next" pointer + + /* + * Finally, point pDirEntry at the first entry in the new area. + */ + pDirEntry = pBlock + 4; + entryIdx = 0; + assert(pDirEntry[0x00] == 0x00); + } + + /* + * Success. Update the file count in the header. + */ + uint16_t count; + count = GetShortLE(&dirBuf[0x25]); + count++; + PutShortLE(&dirBuf[0x25], count); + + long whichBlock; + + *ppDir = dirBuf; + *pDirLen = dirLen; + *ppDirEntry = pDirEntry; + *pDirKeyBlock = pFile->fDirEntry.keyPointer; + *pDirEntrySlot = entryIdx +1; + if (blockIdx == ((A2FDProDOS*)pOpenSubdir)->GetBlockCount()) { + /* not yet added to block list, so can't use GetStorage */ + assert(newBlock > 0); + *pDirBlock = (uint16_t) newBlock; + } else { + assert(newBlock < 0); + dierr = pOpenSubdir->GetStorage(blockIdx, &whichBlock); + assert(dierr == kDIErrNone); + *pDirBlock = (uint16_t) whichBlock; + } + dirBuf = NULL; + +bail: + delete[] dirBuf; + return dierr; +} + +/* + * Given a pointer to a directory buffer and a pointer to an entry, find the + * previous entry. (This is handy when trying to figure out where to insert + * a new entry into the DiskFS linear file list.) + * + * If the previous entry is the first in the list (i.e. it's a volume or + * subdir header), this returns NULL. + * + * This is a little awkward because the directories are chopped up into + * 512-byte blocks, with 13 entries per block (which doesn't completely fill + * the block, leaving gaps we have to skip around). If the previous entry is + * in the same block we can just return (ptr-0x27), but if it's in a previous + * block we need to return the last entry in the previous. + */ +uint8_t* DiskFSProDOS::GetPrevDirEntry(uint8_t* buf, uint8_t* ptr) +{ + assert(buf != NULL); + assert(ptr != NULL); + + const int kStartOffset = 4; + + if (ptr == buf + kStartOffset || ptr == buf + kStartOffset + kEntryLength) + return NULL; + + while (ptr - buf > 512) + buf += 512; + + assert((ptr - buf - kStartOffset) % kEntryLength == 0); + + if (ptr == buf + kStartOffset) { + /* whoops, went too far */ + buf -= 512; + return buf + kStartOffset + kEntryLength * (kEntriesPerBlock-1); + } else { + return ptr - kEntryLength; + } +} + +/* + * Make the name pointed to by "fileName" unique within the directory + * loaded in "subdirBuf". The name should already be trimmed to 15 chars + * or less and converted to upper-case only, and be in a buffer that can + * hold at least kMaxFileName+1 bytes. + * + * Returns an error on failure, which should only happen if there are a + * large number of files with similar names. + */ +DIError DiskFSProDOS::MakeFileNameUnique(const uint8_t* dirBuf, long dirLen, + char* fileName) +{ + assert(dirBuf != NULL); + assert(dirLen > 0); + assert((dirLen % 512) == 0); + assert(fileName != NULL); + assert(strlen(fileName) <= A2FileProDOS::kMaxFileName); + + if (!NameExistsInDir(dirBuf, dirLen, fileName)) + return kDIErrNone; + + LOGI(" ProDOS found duplicate of '%s', making unique", fileName); + + int nameLen = strlen(fileName); + int dotOffset=0, dotLen=0; + char dotBuf[kMaxExtensionLen+1]; + + /* ensure the result will be null-terminated */ + memset(fileName + nameLen, 0, (A2FileProDOS::kMaxFileName - nameLen) +1); + + /* + * If this has what looks like a filename extension, grab it. We want + * to preserve ".gif", ".c", etc., since the filetypes don't necessarily + * do everything we need. + * + * This will tend to screw up the upper/lower case stuff, especially + * since what we think is a '.' might actually be a ' '. We could work + * around this, but it's probably not necessary. + */ + const char* cp = strrchr(fileName, '.'); + if (cp != NULL) { + int tmpOffset = cp - fileName; + if (tmpOffset > 0 && nameLen - tmpOffset <= kMaxExtensionLen) { + LOGI(" ProDOS (keeping extension '%s')", cp); + assert(strlen(cp) <= kMaxExtensionLen); + strcpy(dotBuf, cp); + dotOffset = tmpOffset; + dotLen = nameLen - dotOffset; + } + } + + const int kMaxDigits = 999; + int digits = 0; + int digitLen; + int copyOffset; + char digitBuf[4]; + do { + if (digits == kMaxDigits) + return kDIErrFileExists; + digits++; + + /* not the most efficient way to do this, but it'll do */ + sprintf(digitBuf, "%d", digits); + digitLen = strlen(digitBuf); + if (nameLen + digitLen > A2FileProDOS::kMaxFileName) + copyOffset = A2FileProDOS::kMaxFileName - dotLen - digitLen; + else + copyOffset = nameLen - dotLen; + memcpy(fileName + copyOffset, digitBuf, digitLen); + if (dotLen != 0) + memcpy(fileName + copyOffset + digitLen, dotBuf, dotLen); + } while (NameExistsInDir(dirBuf, dirLen, fileName)); + + LOGI(" ProDOS converted to unique name: %s", fileName); + + return kDIErrNone; +} + +/* + * Determine whether the specified file name exists in the raw directory + * buffer. + * + * This should be called with the upper-case-only version of the filename. + */ +bool DiskFSProDOS::NameExistsInDir(const uint8_t* dirBuf, long dirLen, + const char* fileName) +{ + const uint8_t* pDirEntry; + int blockIdx; + int entryIdx; + int nameLen = strlen(fileName); + + assert(nameLen <= A2FileProDOS::kMaxFileName); + + for (blockIdx = 0; blockIdx < dirLen / 512; blockIdx++) { + pDirEntry = dirBuf + 512*blockIdx + 4; // skip 4 bytes of prev/next + + for (entryIdx = 0; entryIdx < kEntriesPerBlock; + entryIdx++, pDirEntry += kEntryLength) + { + /* skip directory header */ + if (blockIdx == 0 && entryIdx == 0) + continue; + + if ((pDirEntry[0x00] & 0xf0) != 0 && + (pDirEntry[0x00] & 0x0f) == nameLen && + strncmp((char*) &pDirEntry[0x01], fileName, nameLen) == 0) + { + return true; + } + } + } + + return false; +} + +/* + * Delete a file. + * + * There are three fairly simple steps: (1) mark all blocks used by the file as + * free, (2) set the storage type in the directory entry to 0, and (3) + * decrement the file count in the directory header. We then remove it from + * the DiskFS file list. + * + * We only allow deletion of a subdirectory when the subdir is empty. + */ +DIError DiskFSProDOS::DeleteFile(A2File* pGenericFile) +{ + DIError dierr = kDIErrNone; + long blockCount = -1; + long indexCount = -1; + uint16_t* blockList = NULL; + uint16_t* indexList = NULL; + + if (pGenericFile == NULL) { + assert(false); + return kDIErrInvalidArg; + } + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + if (pGenericFile->IsFileOpen()) + return kDIErrFileOpen; + + /* + * If they try to delete all entries, we don't want to spit back a + * failure message over our "fake" volume dir entry. So we just silently + * ignore the request. + */ + if (pGenericFile->IsVolumeDirectory()) { + LOGI("ProDOS not deleting volume directory"); + return kDIErrNone; + } + + A2FileProDOS* pFile = (A2FileProDOS*) pGenericFile; + + LOGI(" Deleting '%s'", pFile->GetPathName()); + + dierr = LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + switch (pFile->fDirEntry.storageType) { + case A2FileProDOS::kStorageExtended: + // handle rsrc fork here, fall out for data fork + dierr = pFile->LoadBlockList( + pFile->fExtRsrc.storageType, + pFile->fExtRsrc.keyBlock, + pFile->fExtRsrc.eof, + &blockCount, &blockList, + &indexCount, &indexList); + if (dierr != kDIErrNone) + goto bail; + FreeBlocks(blockCount, blockList); + if (indexList != NULL) // no indices for seedling + FreeBlocks(indexCount, indexList); + delete[] blockList; + delete[] indexList; + indexList = NULL; + + // handle the key block "manually" + blockCount = 1; + blockList = new uint16_t[blockCount]; + blockList[0] = pFile->fDirEntry.keyPointer; + FreeBlocks(blockCount, blockList); + delete[] blockList; + blockList = NULL; + + dierr = pFile->LoadBlockList( + pFile->fExtData.storageType, + pFile->fExtData.keyBlock, + pFile->fExtData.eof, + &blockCount, &blockList, + &indexCount, &indexList); + break; // fall out + + case A2FileProDOS::kStorageDirectory: + dierr = pFile->LoadDirectoryBlockList( + pFile->fDirEntry.keyPointer, + pFile->fDirEntry.eof, + &blockCount, &blockList); + break; // fall out + + case A2FileProDOS::kStorageSeedling: + case A2FileProDOS::kStorageSapling: + case A2FileProDOS::kStorageTree: + dierr = pFile->LoadBlockList( + pFile->fDirEntry.storageType, + pFile->fDirEntry.keyPointer, + pFile->fDirEntry.eof, + &blockCount, &blockList, + &indexCount, &indexList); + break; // fall out + + default: + LOGI("ProDOS can't delete unknown storage type %d", + pFile->fDirEntry.storageType); + dierr = kDIErrBadDirectory; + break; // fall out + } + + if (dierr != kDIErrNone) + goto bail; + + FreeBlocks(blockCount, blockList); + if (indexList != NULL) + FreeBlocks(indexCount, indexList); + + /* + * Update the directory entry. After this point, failure gets ugly. + * + * It might be "proper" to open the subdir file, find the correct entry, + * and write it back, but the A2FileProDOS structure has the directory + * block and entry index stored in it. Makes it a little easier. + */ + uint8_t blkBuf[kBlkSize]; + uint8_t* ptr; + assert(pFile->fParentDirBlock > 0); + assert(pFile->fParentDirIdx >= 0 && + pFile->fParentDirIdx < kEntriesPerBlock); + dierr = fpImg->ReadBlock(pFile->fParentDirBlock, blkBuf); + if (dierr != kDIErrNone) { + LOGI("ProDOS unable to read directory block %u", + pFile->fParentDirBlock); + goto bail; + } + + ptr = blkBuf + 4 + pFile->fParentDirIdx * kEntryLength; + if ((*ptr) >> 4 != pFile->fDirEntry.storageType) { + LOGI("ProDOS GLITCH: mismatched storage types (%d vs %d)", + (*ptr) >> 4, pFile->fDirEntry.storageType); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + ptr[0x00] = 0; // zap both storage type and name length + dierr = fpImg->WriteBlock(pFile->fParentDirBlock, blkBuf); + if (dierr != kDIErrNone) { + LOGI("ProDOS unable to write directory block %u", + pFile->fParentDirBlock); + goto bail; + } + + /* + * Save our updated copy of the volume bitmap to disk. + */ + dierr = SaveVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + /* + * One last little thing: decrement the file count in the directory + * header. We can find the appropriate place pretty easily because + * we know it's the first block in pFile->fpParent, which for a dir is + * always the block pointed to by the key pointer. + * + * Strictly speaking, failure to update this correctly isn't fatal. I + * doubt most utilities pay any attention to this. Still, it's important + * to keep the filesystem in a consistent state, so we at least must + * report the error. They'll need to run the ProSel volume repair util + * to fix it. + */ + A2FileProDOS* pParent; + uint16_t fileCount; + int storageType; + pParent = (A2FileProDOS*) pFile->GetParent(); + assert(pParent != NULL); + assert(pParent->fDirEntry.keyPointer >= kVolHeaderBlock); + dierr = fpImg->ReadBlock(pParent->fDirEntry.keyPointer, blkBuf); + if (dierr != kDIErrNone) { + LOGI("ProDOS unable to read parent dir block %u", + pParent->fDirEntry.keyPointer); + goto bail; + } + ptr = NULL; + + storageType = (blkBuf[0x04] & 0xf0) >> 4; + if (storageType != A2FileProDOS::kStorageSubdirHeader && + storageType != A2FileProDOS::kStorageVolumeDirHeader) + { + LOGI("ProDOS invalid storage type %d in dir header block", + storageType); + DebugBreak(); + dierr = kDIErrBadDirectory; + goto bail; + } + fileCount = GetShortLE(&blkBuf[0x25]); + if (fileCount > 0) + fileCount--; + PutShortLE(&blkBuf[0x25], fileCount); + dierr = fpImg->WriteBlock(pParent->fDirEntry.keyPointer, blkBuf); + if (dierr != kDIErrNone) { + LOGI("ProDOS unable to write parent dir block %u", + pParent->fDirEntry.keyPointer); + goto bail; + } + + /* + * Remove the A2File* from the list. + */ + DeleteFileFromList(pFile); + +bail: + FreeVolBitmap(); + delete[] blockList; + delete[] indexList; + return kDIErrNone; +} + +/* + * Mark all of the blocks in the blockList as free. + * + * The in-use map must already be loaded. + */ +DIError DiskFSProDOS::FreeBlocks(long blockCount, uint16_t* blockList) +{ + VolumeUsage::ChunkState cstate; + int i; + + //LOGI(" +++ FreeBlocks (blockCount=%d blockList=0x%08lx)", + // blockCount, blockList); + assert(blockCount >= 0 && blockCount < 65536); + assert(blockList != NULL); + + cstate.isUsed = false; + cstate.isMarkedUsed = false; + cstate.purpose = VolumeUsage::kChunkPurposeUnknown; + + for (i = 0; i < blockCount; i++) { + if (blockList[i] == 0) // expected for "sparse" files + continue; + + if (!GetBlockUseEntry(blockList[i])) { + LOGI("WARNING: freeing unallocated block %u", blockList[i]); + assert(false); // impossible unless disk is "damaged" + } + SetBlockUseEntry(blockList[i], false); + + fVolumeUsage.SetChunkState(blockList[i], &cstate); + } + + return kDIErrNone; +} + +/* + * Rename a file. + * + * Pass in a pointer to the file and a string with the new filename (just + * the filename, not a pathname -- this function doesn't move files + * between directories). The new name must already be normalized. + * + * Renaming the magic volume directory "file" is not allowed. + * + * Things to note: + * - Renaming subdirs is annoying. The name has to be changed in two + * places, and the "pathname" value cached in A2FileProDOS must be + * updated for all children of the subdir. + * - Must check for duplicates. + * - If it's an AppleWorks file type, we need to change the aux type + * according to the upper/lower case flags. This holds even if the + * "allow lower case" flag is disabled. + */ +DIError DiskFSProDOS::RenameFile(A2File* pGenericFile, const char* newName) +{ + DIError dierr = kDIErrNone; + A2FileProDOS* pFile = (A2FileProDOS*) pGenericFile; + char upperName[A2FileProDOS::kMaxFileName+1]; + char upperComp[A2FileProDOS::kMaxFileName+1]; + + if (pFile == NULL || newName == NULL) + return kDIErrInvalidArg; + if (!IsValidFileName(newName)) + return kDIErrInvalidArg; + if (pFile->IsVolumeDirectory()) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (!fDiskIsGood) + return kDIErrBadDiskImage; + + LOGI(" ProDOS renaming '%s' to '%s'", pFile->GetPathName(), newName); + + /* + * Check for duplicates. We do this by getting the parent subdir and + * running through it looking for an upper-case-converted match. + * + * We start in the list at our parent node, knowing that the kids are + * grouped together after it. However, we can't stop right away, + * because some of the kids might be subdirectories themselves. So we + * will probably run through a significant chunk of the list. + */ + A2File* pParent = pFile->GetParent(); + A2File* pCur; + + UpperCaseName(upperName, newName); + pCur = GetNextFile(pParent); + assert(pCur != NULL); // at the very least, pFile is in this dir + while (pCur != NULL) { + if (pCur != pFile && pCur->GetParent() == pParent) { + /* one of our siblings; see if the name matches */ + UpperCaseName(upperComp, pCur->GetFileName()); + if (strcmp(upperName, upperComp) == 0) { + LOGI(" ProDOS rename dup found"); + return kDIErrFileExists; + } + } + + pCur = GetNextFile(pCur); + } + + /* + * Grab the directory block and update the filename in the entry. If this + * was a subdir we also need to update its directory header entry. To + * minimize the chances of a partial update, we load both blocks up + * front, modify both, then write them both back. + */ + uint8_t parentDirBuf[kBlkSize]; + uint8_t thisDirBuf[kBlkSize]; + + dierr = fpImg->ReadBlock(pFile->fParentDirBlock, parentDirBuf); + if (dierr != kDIErrNone) + goto bail; + if (pFile->IsDirectory()) { + dierr = fpImg->ReadBlock(pFile->fDirEntry.keyPointer, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + } + + /* compute lower case flags as needed */ + uint16_t lcFlags, lcAuxType; + bool allowLowerCase, isAW; + + allowLowerCase = GetParameter(kParmProDOS_AllowLowerCase) != 0; + isAW = A2FileProDOS::UsesAppleWorksAuxType((uint8_t)pFile->GetFileType()); + + if (allowLowerCase) + lcFlags = GenerateLowerCaseBits(upperName, newName, false); + else + lcFlags = 0; + if (isAW) + lcAuxType = GenerateLowerCaseBits(upperName, newName, true); + else + lcAuxType = 0; + + /* + * Possible optimization: if "upperName" matches what's in the block on + * disk and the "lcFlags"/"lcAuxType" values match as well, we don't + * need to write the blocks back. + * + * It's difficult to test for this earlier, because we need to do the + * update if (a) they're just changing the capitalization or (b) we're + * changing the capitalization for them because the "allow lower case" + * flag got turned off. + */ + + /* find the right entry, and copy our filename in */ + uint8_t* ptr; + assert(pFile->fParentDirIdx >= 0 && + pFile->fParentDirIdx < kEntriesPerBlock); + ptr = parentDirBuf + 4 + pFile->fParentDirIdx * kEntryLength; + if ((*ptr) >> 4 != pFile->fDirEntry.storageType) { + LOGI("ProDOS GLITCH: mismatched storage types (%d vs %d)", + (*ptr) >> 4, pFile->fDirEntry.storageType); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + ptr[0x00] = (uint8_t)((ptr[0x00] & 0xf0) | strlen(upperName)); + memcpy(&ptr[0x01], upperName, A2FileProDOS::kMaxFileName); + PutShortLE(&ptr[0x1c], lcFlags); // version/min_version + if (isAW) + PutShortLE(&ptr[0x1f], lcAuxType); + + if (pFile->IsDirectory()) { + ptr = thisDirBuf + 4; + if ((*ptr) >> 4 != A2FileProDOS::kStorageSubdirHeader) { + LOGI("ProDOS GLITCH: bad storage type in subdir header (%d)", + (*ptr) >> 4); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + ptr[0x00] = (uint8_t)((ptr[0x00] & 0xf0) | strlen(upperName)); + memcpy(&ptr[0x01], upperName, A2FileProDOS::kMaxFileName); + PutShortLE(&ptr[0x1c], lcFlags); // version/min_version + } + + /* write the updated data back to the disk */ + dierr = fpImg->WriteBlock(pFile->fParentDirBlock, parentDirBuf); + if (dierr != kDIErrNone) + goto bail; + if (pFile->IsDirectory()) { + dierr = fpImg->WriteBlock(pFile->fDirEntry.keyPointer, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + } + + /* + * At this point the ProDOS filesystem is back in a consistent state. + * Everything we do from here on is self-inflicted. + * + * We need to update this entry's A2FileProDOS::fDirEntry.fileName, + * as well as the A2FileProDOS::fPathName. If this was a subdir, then + * we need to update A2FileProDOS::fPathName for all files inside the + * directory (including children of children). + * + * The latter is somewhat awkward, so we just re-acquire the pathname + * for every file on the disk. Less efficient but easier to code. + */ + if (isAW) + GenerateLowerCaseName(upperName, pFile->fDirEntry.fileName, + lcAuxType, true); + else + GenerateLowerCaseName(upperName, pFile->fDirEntry.fileName, + lcFlags, false); + assert(pFile->fDirEntry.fileName[A2FileProDOS::kMaxFileName] == '\0'); + + if (pFile->IsDirectory()) { + /* do all files that come after us */ + pCur = pFile; + while (pCur != NULL) { + RegeneratePathName((A2FileProDOS*) pCur); + pCur = GetNextFile(pCur); + } + } else { + RegeneratePathName(pFile); + } + + LOGI("Okay!"); + +bail: + return dierr; +} + +/* + * Regenerate fPathName for the specified file. + * + * Has no effect on the magic volume dir entry. + * + * This could be implemented more efficiently, but it's only used when + * renaming files, so there's not much point. + */ +DIError DiskFSProDOS::RegeneratePathName(A2FileProDOS* pFile) +{ + A2FileProDOS* pParent; + char* buf = NULL; + int len; + + /* nothing to do here */ + if (pFile->IsVolumeDirectory()) + return kDIErrNone; + + /* compute the length of the path name */ + len = strlen(pFile->GetFileName()); + pParent = (A2FileProDOS*) pFile->GetParent(); + while (!pParent->IsVolumeDirectory()) { + len++; // leave space for the ':' + len += strlen(pParent->GetFileName()); + + pParent = (A2FileProDOS*) pParent->GetParent(); + } + + buf = new char[len+1]; + if (buf == NULL) + return kDIErrMalloc; + + /* generate the new path name */ + int partLen; + partLen = strlen(pFile->GetFileName()); + strcpy(buf + len - partLen, pFile->GetFileName()); + len -= partLen; + + pParent = (A2FileProDOS*) pFile->GetParent(); + while (!pParent->IsVolumeDirectory()) { + assert(len > 0); + buf[--len] = kDIFssep; + + partLen = strlen(pParent->GetFileName()); + strncpy(buf + len - partLen, pParent->GetFileName(), partLen); + len -= partLen; + assert(len >= 0); + + pParent = (A2FileProDOS*) pParent->GetParent(); + } + + LOGI("Replacing '%s' with '%s'", pFile->GetPathName(), buf); + pFile->SetPathName("", buf); + delete[] buf; + + return kDIErrNone; +} + +/* + * Change the attributes of the specified file. + * + * Subdirectories have access bits in the subdir header as well as their + * file entry. The BASIC.SYSTEM "lock" command only changes the access + * bits of the file; the permissions inside the subdir remain 0xe3. (Which + * might explain why you can still add files to a locked subdir.) I'm going + * to mimic this behavior. + * + * This does, of course, mean that there's no meaning in attempts to change + * the file access permissions of the volume directory. + */ +DIError DiskFSProDOS::SetFileInfo(A2File* pGenericFile, uint32_t fileType, + uint32_t auxType, uint32_t accessFlags) +{ + DIError dierr = kDIErrNone; + A2FileProDOS* pFile = (A2FileProDOS*) pGenericFile; + + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + if (pFile == NULL) { + assert(false); + return kDIErrInvalidArg; + } + if ((fileType & ~(0xff)) != 0 || + (auxType & ~(0xffff)) != 0 || + (accessFlags & ~(0xff)) != 0) + { + return kDIErrInvalidArg; + } + if (pFile->IsVolumeDirectory()) { + LOGI(" ProDOS refusing to change file info for volume dir"); + return kDIErrAccessDenied; // not quite right + } + + LOGI("ProDOS changing values for '%s' to 0x%02x 0x%04x 0x%02x", + pFile->GetPathName(), fileType, auxType, accessFlags); + + /* load the directory block for this file */ + uint8_t thisDirBuf[kBlkSize]; + dierr = fpImg->ReadBlock(pFile->fParentDirBlock, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + + /* find the right entry, and set the fields */ + uint8_t* ptr; + assert(pFile->fParentDirIdx >= 0 && + pFile->fParentDirIdx < kEntriesPerBlock); + ptr = thisDirBuf + 4 + pFile->fParentDirIdx * kEntryLength; + if ((*ptr) >> 4 != pFile->fDirEntry.storageType) { + LOGI("ProDOS GLITCH: mismatched storage types (%d vs %d)", + (*ptr) >> 4, pFile->fDirEntry.storageType); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + if ((size_t) (*ptr & 0x0f) != strlen(pFile->fDirEntry.fileName)) { + LOGW("ProDOS GLITCH: wrong file? (len=%d vs %u)", + *ptr & 0x0f, (unsigned int) strlen(pFile->fDirEntry.fileName)); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + + ptr[0x10] = (uint8_t) fileType; + ptr[0x1e] = (uint8_t) accessFlags; + PutShortLE(&ptr[0x1f], (uint16_t) auxType); + + dierr = fpImg->WriteBlock(pFile->fParentDirBlock, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + + /* update our local copy */ + pFile->fDirEntry.fileType = (uint8_t) fileType; + pFile->fDirEntry.auxType = (uint16_t) auxType; + pFile->fDirEntry.access = (uint8_t) accessFlags; + +bail: + return dierr; +} + +/* + * Change the disk volume name. + * + * This is a lot like renaming a subdirectory, except that there's no parent + * directory to update, and the name of the volume dir doesn't affect the + * pathname of anything else. There's also no risk of a duplicate. + * + * Internally we need to update the "fake" entry and the cached copies in + * fVolumeName and fVolumeID. + */ +DIError DiskFSProDOS::RenameVolume(const char* newName) +{ + DIError dierr = kDIErrNone; + char upperName[A2FileProDOS::kMaxFileName+1]; + A2FileProDOS* pFile; + + if (!IsValidVolumeName(newName)) + return kDIErrInvalidArg; + if (fpImg->GetReadOnly()) + return kDIErrAccessDenied; + + pFile = (A2FileProDOS*) GetNextFile(NULL); + assert(pFile != NULL); + assert(strcmp(pFile->GetFileName(), fVolumeName) == 0); + + LOGI(" ProDOS renaming volume '%s' to '%s'", + pFile->GetPathName(), newName); + + /* + * Figure out the lower-case flags. + */ + uint16_t lcFlags; + bool allowLowerCase; + + UpperCaseName(upperName, newName); + allowLowerCase = GetParameter(kParmProDOS_AllowLowerCase) != 0; + if (allowLowerCase) + lcFlags = GenerateLowerCaseBits(upperName, newName, false); + else + lcFlags = 0; + + /* + * Update the volume dir header. + */ + uint8_t thisDirBuf[kBlkSize]; + uint8_t* ptr; + assert(pFile->fDirEntry.keyPointer == kVolHeaderBlock); + + dierr = fpImg->ReadBlock(pFile->fDirEntry.keyPointer, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + + ptr = thisDirBuf + 4; + if ((*ptr) >> 4 != A2FileProDOS::kStorageVolumeDirHeader) { + LOGI("ProDOS GLITCH: bad storage type in voldir header (%d)", + (*ptr) >> 4); + assert(false); + dierr = kDIErrBadDirectory; + goto bail; + } + ptr[0x00] = (uint8_t)((ptr[0x00] & 0xf0) | strlen(upperName)); + memcpy(&ptr[0x01], upperName, A2FileProDOS::kMaxFileName); + PutShortLE(&ptr[0x16], lcFlags); // reserved fields + + dierr = fpImg->WriteBlock(pFile->fDirEntry.keyPointer, thisDirBuf); + if (dierr != kDIErrNone) + goto bail; + + /* + * Set the volume name, based on the upper-case name and lower-case flags + * we just wrote. If "allowLowerCase" was set to false, it may not be + * the same as what's in "newName". + */ + char lowerName[A2FileProDOS::kMaxFileName+1]; + memset(lowerName, 0, sizeof(lowerName)); // lowerName won't be term'ed + GenerateLowerCaseName(upperName, lowerName, lcFlags, false); + + strcpy(fVolumeName, lowerName); + SetVolumeID(); + strcpy(pFile->fDirEntry.fileName, lowerName); + + /* update the entry in the linear file list */ + pFile->SetPathName(":", fVolumeName); + +bail: + return dierr; +} + + +/* + * =========================================================================== + * A2FileProDOS + * =========================================================================== + */ + +/* + * Convert from ProDOS compact date format to a time_t. + * + * Byte 0 and 1: yyyyyyymmmmddddd + * Byte 2 and 3: 000hhhhh00mmmmmm + * + * The field is set entirely to zero if no date was assigned (which cannot + * be a valid date since "day" ranges from 1 to 31). If this is found then + * ((time_t) 0) is returned. + */ +/*static*/ time_t A2FileProDOS::ConvertProDate(ProDate proDate) +{ + uint16_t prodosDate, prodosTime; + int year, month, day, hour, minute, second; + + if (proDate == 0) + return 0; + + prodosDate = (uint16_t) (proDate & 0x0000ffff); + prodosTime = (uint16_t) ((proDate >> 16) & 0x0000ffff); + + second = 0; + minute = prodosTime & 0x3f; + hour = (prodosTime >> 8) & 0x1f; + day = prodosDate & 0x1f; + month = (prodosDate >> 5) & 0x0f; + year = (prodosDate >> 9) & 0x7f; + if (year < 40) + year += 100; /* P8 uses 0-39 for 2000-2039 */ + + struct tm tmbuf; + time_t when; + + tmbuf.tm_sec = second; + tmbuf.tm_min = minute; + tmbuf.tm_hour = hour; + tmbuf.tm_mday = day; + tmbuf.tm_mon = month-1; // ProDOS uses 1-12 + tmbuf.tm_year = year; + tmbuf.tm_wday = 0; + tmbuf.tm_yday = 0; + tmbuf.tm_isdst = -1; // let it figure DST and time zone + when = mktime(&tmbuf); + + if (when == (time_t) -1) + when = 0; + + return when; +} + +/* + * Convert a time_t to a ProDOS-format date. + * + * CiderPress uses kDateInvalid==-1 and kDateNone==-2. + */ +/*static*/ A2FileProDOS::ProDate A2FileProDOS::ConvertProDate(time_t unixDate) +{ + ProDate proDate; + uint32_t prodosDate, prodosTime; + struct tm* ptm; + int year; + + if (unixDate == 0 || unixDate == -1 || unixDate == -2) + return 0; + + ptm = localtime(&unixDate); + if (ptm == NULL) + return 0; // must've been invalid or unspecified + + year = ptm->tm_year; +#ifdef OLD_PRODOS_DATES + /* ProSel-16 volume repair complaints about dates < 1980 and >= Y2K */ + if (year > 100) + year -= 20; +#endif + + if (year >= 100) + year -= 100; + if (year < 0 || year >= 128) { + LOGI("WHOOPS: got year %d from %d", year, ptm->tm_year); + year = 70; + } + + prodosDate = year << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + prodosTime = ptm->tm_hour << 8 | ptm->tm_min; + + proDate = prodosTime << 16 | prodosDate; + return proDate; +} + +/* + * Return the file creation time as a time_t. + */ +time_t A2FileProDOS::GetCreateWhen(void) const +{ + return ConvertProDate(fDirEntry.createWhen); +} + +/* + * Return the file modification time as a time_t. + */ +time_t A2FileProDOS::GetModWhen(void) const +{ + return ConvertProDate(fDirEntry.modWhen); +} + +/* + * Set the full pathname to a combination of the base path and the + * current file's name. + * + * If we're in the volume directory, pass in "" for the base path (not NULL). + */ +void A2FileProDOS::SetPathName(const char* basePath, const char* fileName) +{ + assert(basePath != NULL && fileName != NULL); + if (fPathName != NULL) + delete[] fPathName; + + int baseLen = strlen(basePath); + fPathName = new char[baseLen + 1 + strlen(fileName)+1]; + strcpy(fPathName, basePath); + if (baseLen != 0 && + !(baseLen == 1 && basePath[0] == ':')) + { + *(fPathName + baseLen) = kFssep; + baseLen++; + } + strcpy(fPathName + baseLen, fileName); +} + +/* + * Convert a character in a ProDOS name to lower case. + * + * This is special in that '.' is considered upper case, with ' ' as its + * lower-case counterpart. + */ +/*static*/ char A2FileProDOS::NameToLower(char ch) +{ + if (ch == '.') + return ' '; + else + return tolower(ch); +} + +/* + * Init the fields in the DirEntry struct from the values in the ProDOS + * directory entry pointed to by "entryBuf". + * + * Deals with lower case conversions on the filename. + */ +/*static*/ void A2FileProDOS::InitDirEntry(A2FileProDOS::DirEntry* pEntry, + const uint8_t* entryBuf) +{ + int nameLen; + + pEntry->storageType = (entryBuf[0x00] & 0xf0) >> 4; + nameLen = entryBuf[0x00] & 0x0f; + memcpy(pEntry->fileName, &entryBuf[0x01], nameLen); + pEntry->fileName[nameLen] = '\0'; + pEntry->fileType = entryBuf[0x10]; + pEntry->keyPointer = GetShortLE(&entryBuf[0x11]); + pEntry->blocksUsed = GetShortLE(&entryBuf[0x13]); + pEntry->eof = GetLongLE(&entryBuf[0x15]); + pEntry->eof &= 0x00ffffff; + pEntry->createWhen = GetLongLE(&entryBuf[0x18]); + pEntry->version = entryBuf[0x1c]; + pEntry->minVersion = entryBuf[0x1d]; + pEntry->access = entryBuf[0x1e]; + pEntry->auxType = GetShortLE(&entryBuf[0x1f]); + pEntry->modWhen = GetLongLE(&entryBuf[0x21]); + pEntry->headerPointer = GetShortLE(&entryBuf[0x25]); + + /* generate the name into the buffer; does not null-terminate */ + if (UsesAppleWorksAuxType(pEntry->fileType)) { + DiskFSProDOS::GenerateLowerCaseName(pEntry->fileName, pEntry->fileName, + pEntry->auxType, true); + } else if (pEntry->minVersion & 0x80) { + DiskFSProDOS::GenerateLowerCaseName(pEntry->fileName, pEntry->fileName, + GetShortLE(&entryBuf[0x1c]), false); + } + pEntry->fileName[sizeof(pEntry->fileName)-1] = '\0'; +} + +/* + * Open one fork of this file. + * + * I really, really dislike forked files. + */ +DIError A2FileProDOS::Open(A2FileDescr** ppOpenFile, bool readOnly, + bool rsrcFork /*= false*/) +{ + DIError dierr = kDIErrNone; + A2FDProDOS* pOpenFile = NULL; + + LOGI(" ProDOS Open(ro=%d, rsrc=%d) on '%s'", + readOnly, rsrcFork, fPathName); + //Dump(); + + if (!readOnly) { + if (fpDiskFS->GetDiskImg()->GetReadOnly()) + return kDIErrAccessDenied; + if (fpDiskFS->GetFSDamaged()) + return kDIErrBadDiskImage; + } + + if (fpOpenFile != NULL) { + dierr = kDIErrAlreadyOpen; + goto bail; + } + if (rsrcFork && fDirEntry.storageType != kStorageExtended) { + dierr = kDIErrForkNotFound; + goto bail; + } + + pOpenFile = new A2FDProDOS(this); + if (pOpenFile == NULL) + return kDIErrMalloc; + + pOpenFile->fOpenRsrcFork = false; + + if (fDirEntry.storageType == kStorageExtended) { + if (rsrcFork) { + dierr = LoadBlockList(fExtRsrc.storageType, fExtRsrc.keyBlock, + fExtRsrc.eof, &pOpenFile->fBlockCount, + &pOpenFile->fBlockList); + pOpenFile->fOpenEOF = fExtRsrc.eof; + pOpenFile->fOpenBlocksUsed = fExtRsrc.blocksUsed; + pOpenFile->fOpenStorageType = fExtRsrc.storageType; + pOpenFile->fOpenRsrcFork = true; + } else { + dierr = LoadBlockList(fExtData.storageType, fExtData.keyBlock, + fExtData.eof, &pOpenFile->fBlockCount, + &pOpenFile->fBlockList); + pOpenFile->fOpenEOF = fExtData.eof; + pOpenFile->fOpenBlocksUsed = fExtData.blocksUsed; + pOpenFile->fOpenStorageType = fExtData.storageType; + } + } else if (fDirEntry.storageType == kStorageDirectory || + fDirEntry.storageType == kStorageVolumeDirHeader) + { + dierr = LoadDirectoryBlockList(fDirEntry.keyPointer, + fDirEntry.eof, &pOpenFile->fBlockCount, + &pOpenFile->fBlockList); + pOpenFile->fOpenEOF = fDirEntry.eof; + pOpenFile->fOpenBlocksUsed = fDirEntry.blocksUsed; + pOpenFile->fOpenStorageType = fDirEntry.storageType; + } else if (fDirEntry.storageType == kStorageSeedling || + fDirEntry.storageType == kStorageSapling || + fDirEntry.storageType == kStorageTree) + { + dierr = LoadBlockList(fDirEntry.storageType, fDirEntry.keyPointer, + fDirEntry.eof, &pOpenFile->fBlockCount, + &pOpenFile->fBlockList); + pOpenFile->fOpenEOF = fDirEntry.eof; + pOpenFile->fOpenBlocksUsed = fDirEntry.blocksUsed; + pOpenFile->fOpenStorageType = fDirEntry.storageType; + } else { + LOGI("PrODOS can't open unknown storage type %d", + fDirEntry.storageType); + dierr = kDIErrBadDirectory; + goto bail; + } + if (dierr != kDIErrNone) { + LOGI(" ProDOS open failed"); + goto bail; + } + + pOpenFile->fOffset = 0; + //pOpenFile->DumpBlockList(); + + fpOpenFile = pOpenFile; // add it to our single-member "open file set" + *ppOpenFile = pOpenFile; + pOpenFile = NULL; + +bail: + delete pOpenFile; + return dierr; +} + +/* + * Gather a linear, non-sparse list of file blocks into an array. + * + * Pass in the storage type and top-level key block. Separation of + * extended files should have been handled by the caller. This loads the + * list for only one fork. + * + * There are two kinds of sparse: sparse *inside* data, and sparse + * *past* data. The latter is interesting, because there is no need + * to create space in index blocks to hold it. Thus, a sapling could + * hold a file with an EOF of 16MB. + * + * If "pIndexBlockCount" and "pIndexBlockList" are non-NULL, then we + * also accumulate the list of index blocks and return those as well. + * For a Tree-structured file, the first entry in the index list is + * the master index block. + * + * The caller must delete[] "*pBlockList" and "*pIndexBlockList". + */ +DIError A2FileProDOS::LoadBlockList(int storageType, uint16_t keyBlock, + long eof, long* pBlockCount, uint16_t** pBlockList, + long* pIndexBlockCount, uint16_t** pIndexBlockList) +{ + if (storageType == kStorageDirectory || + storageType == kStorageVolumeDirHeader) + { + assert(pIndexBlockList == NULL && pIndexBlockCount == NULL); + return LoadDirectoryBlockList(keyBlock, eof, pBlockCount, pBlockList); + } + + assert(keyBlock != 0); + assert(pBlockCount != NULL); + assert(pBlockList != NULL); + assert(*pBlockList == NULL); + if (storageType != kStorageSeedling && + storageType != kStorageSapling && + storageType != kStorageTree) + { + /* + * We can get here if somebody puts a bad storage type inside the + * extended key block of a forked file. Bad storage types on other + * kinds of files are caught earlier. + */ + LOGI(" ProDOS unexpected storageType %d in '%s'", + storageType, GetPathName()); + return kDIErrNotSupported; + } + + DIError dierr = kDIErrNone; + uint16_t* list = NULL; + long count; + + assert(eof < 1024*1024*16); + count = (eof + kBlkSize -1) / kBlkSize; + if (count == 0) + count = 1; + list = new uint16_t[count+1]; + if (list == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + if (pIndexBlockList != NULL) { + assert(pIndexBlockCount != NULL); + assert(*pIndexBlockList == NULL); + } + + /* this should take care of trailing sparse entries */ + memset(list, 0, sizeof(uint16_t) * count); + list[count] = kInvalidBlockNum; // overrun check + + if (storageType == kStorageSeedling) { + list[0] = keyBlock; + + if (pIndexBlockList != NULL) { + *pIndexBlockCount = 0; + *pIndexBlockList = NULL; + } + } else if (storageType == kStorageSapling) { + dierr = LoadIndexBlock(keyBlock, list, count); + if (dierr != kDIErrNone) + goto bail; + + if (pIndexBlockList != NULL) { + *pIndexBlockCount = 1; + *pIndexBlockList = new uint16_t[1]; + **pIndexBlockList = keyBlock; + } + } else if (storageType == kStorageTree) { + uint8_t blkBuf[kBlkSize]; + uint16_t* listPtr = list; + uint16_t* outIndexPtr = NULL; + long countDown = count; + int idx = 0; + + dierr = fpDiskFS->GetDiskImg()->ReadBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + if (pIndexBlockList != NULL) { + int numIndices = (count + kMaxBlocksPerIndex-1) / kMaxBlocksPerIndex; + numIndices++; // add one for the master index block + *pIndexBlockList = new uint16_t[numIndices]; + outIndexPtr = *pIndexBlockList; + *outIndexPtr++ = keyBlock; + *pIndexBlockCount = 1; + } + + while (countDown) { + long blockCount = countDown; + if (blockCount > kMaxBlocksPerIndex) + blockCount = kMaxBlocksPerIndex; + uint16_t idxBlock; + + idxBlock = blkBuf[idx] | (uint16_t) blkBuf[idx+256] << 8; + if (idxBlock == 0) { + /* fully sparse index block */ + //LOGI(" ProDOS that's seriously sparse (%d)!", idx); + memset(listPtr, 0, blockCount * sizeof(uint16_t)); + if (pIndexBlockList != NULL) { + *outIndexPtr++ = idxBlock; + (*pIndexBlockCount)++; + } + } else { + dierr = LoadIndexBlock(idxBlock, listPtr, blockCount); + if (dierr != kDIErrNone) + goto bail; + + if (pIndexBlockList != NULL) { + *outIndexPtr++ = idxBlock; + (*pIndexBlockCount)++; + } + } + + idx++; + listPtr += blockCount; + countDown -= blockCount; + } + } else { + assert(false); + } + + assert(list[count] == kInvalidBlockNum); + + dierr = ValidateBlockList(list, count); + if (dierr != kDIErrNone) + goto bail; + + *pBlockCount = count; + *pBlockList = list; + +bail: + if (dierr != kDIErrNone) { + delete[] list; + assert(*pBlockList == NULL); + + if (pIndexBlockList != NULL && *pIndexBlockList != NULL) { + delete[] *pIndexBlockList; + *pIndexBlockList = NULL; + } + } + return dierr; +} + +/* + * Make sure all values in the block list fall in accepted ranges. + * + * We allow zero (used for sparse blocks), but disallow values in the "system" + * area (block 1 through the end of the usage map). + * + * It's hard to say whether we should compare against the DiskImg block count + * (representing blocks we can physically read but aren't necessarily part + * of the filesystem) or the filesystem "total blocks" value from the volume + * header. Using the one in the volume header is correct, but sometimes the + * value is off on an otherwise reasonable disk. + * + * I'm falling on the side of generosity, allowing files that reference + * potentially bad data to appear okay. My main reason is that, except for + * CFFA volumes that have been tweaked by CiderPress users, very few ProDOS + * disks will have a large disparity between the two numbers unless somebody + * has trashed the volume dir header. + * + * What we really need is three states for each file: good, suspect, damaged. + */ +DIError A2FileProDOS::ValidateBlockList(const uint16_t* list, long count) +{ + DiskImg* pImg = fpDiskFS->GetDiskImg(); + bool foundBad = false; + + while (count--) { + if (*list > pImg->GetNumBlocks() || + (*list > 0 && *list <= 2)) // not enough, but it'll do + { + LOGI("Invalid block %d in '%s'", *list, fDirEntry.fileName); + SetQuality(kQualityDamaged); + return kDIErrBadFile; + } + if (*list > fpDiskFS->GetFSNumBlocks()) + foundBad = true; + list++; + } + + if (foundBad) { + LOGI(" --- found out-of-range block in '%s'", GetPathName()); + SetQuality(kQualitySuspicious); + } + + return kDIErrNone; +} + +/* + * Copy the entries from the index block in "block" to "list", copying + * at most "maxCount" entries. + */ +DIError A2FileProDOS::LoadIndexBlock(uint16_t block, uint16_t* list, + int maxCount) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + int i; + + if (maxCount > kMaxBlocksPerIndex) + maxCount = kMaxBlocksPerIndex; + + dierr = fpDiskFS->GetDiskImg()->ReadBlock(block, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + //LOGI("LOADING 0x%04x", block); + for (i = 0; i < maxCount; i++) { + *list++ = blkBuf[i] | (uint16_t) blkBuf[i+256] << 8; + } + +bail: + return dierr; +} + +/* + * Load the block list from a directory, which is essentially a linear + * linked list. + */ +DIError A2FileProDOS::LoadDirectoryBlockList(uint16_t keyBlock, + long eof, long* pBlockCount, uint16_t** pBlockList) +{ + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + uint16_t* list = NULL; + uint16_t* listPtr; + int iterations; + long count; + + assert(eof < 1024*1024*16); + count = (eof + kBlkSize -1) / kBlkSize; + if (count == 0) + count = 1; + list = new uint16_t[count+1]; + if (list == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + + /* this should take care of trailing sparse entries */ + memset(list, 0, sizeof(uint16_t) * count); + list[count] = kInvalidBlockNum; // overrun check + + iterations = 0; + listPtr = list; + + while (keyBlock && iterations < kMaxCatalogIterations) { + if (keyBlock < 2 || + keyBlock >= fpDiskFS->GetDiskImg()->GetNumBlocks()) + { + LOGI(" ProDOS ERROR: directory block %u out of range", keyBlock); + dierr = kDIErrInvalidBlock; + goto bail; + } + + *listPtr++ = keyBlock; + + dierr = fpDiskFS->GetDiskImg()->ReadBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + keyBlock = GetShortLE(&blkBuf[0x02]); + iterations++; + } + if (iterations == kMaxCatalogIterations) { + LOGI(" ProDOS subdir iteration count exceeded"); + dierr = kDIErrDirectoryLoop; + goto bail; + } + + assert(list[count] == kInvalidBlockNum); + + *pBlockCount = count; + *pBlockList = list; + +bail: + if (dierr != kDIErrNone) + delete[] list; + return dierr; +} + +/* + * Dump the contents. + */ +void A2FileProDOS::Dump(void) const +{ + LOGI(" ProDOS file '%s' (path='%s')", + fDirEntry.fileName, fPathName); + LOGI(" fileType=0x%02x auxType=0x%04x storage=%d", + fDirEntry.fileType, fDirEntry.auxType, fDirEntry.storageType); + LOGI(" keyPointer=%d blocksUsed=%d eof=%d", + fDirEntry.keyPointer, fDirEntry.blocksUsed, fDirEntry.eof); + LOGI(" access=0x%02x create=0x%08x mod=0x%08x", + fDirEntry.access, fDirEntry.createWhen, fDirEntry.modWhen); + LOGI(" version=%d minVersion=%d headerPtr=%d", + fDirEntry.version, fDirEntry.minVersion, fDirEntry.headerPointer); + if (fDirEntry.storageType == kStorageExtended) { + LOGI(" DATA storage=%d keyBlk=%d blkUsed=%d eof=%d", + fExtData.storageType, fExtData.keyBlock, fExtData.blocksUsed, + fExtData.eof); + LOGI(" RSRC storage=%d keyBlk=%d blkUsed=%d eof=%d", + fExtRsrc.storageType, fExtRsrc.keyBlock, fExtRsrc.blocksUsed, + fExtRsrc.eof); + } + LOGI(" * sparseData=%ld sparseRsrc=%ld", + (long) fSparseDataEof, (long) fSparseRsrcEof); +} + + +/* + * =========================================================================== + * A2FDProDOS + * =========================================================================== + */ + +/* + * Read a chunk of data from whichever fork is open. + */ +DIError A2FDProDOS::Read(void* buf, size_t len, size_t* pActual) +{ + LOGD(" ProDOS reading %lu bytes from '%s' (offset=%ld)", + (unsigned long) len, fpFile->GetPathName(), (long) fOffset); + //if (fBlockList == NULL) + // return kDIErrNotReady; + + if (fOffset + (long)len > fOpenEOF) { + if (pActual == NULL) + return kDIErrDataUnderrun; + len = (long) (fOpenEOF - fOffset); + } + if (pActual != NULL) + *pActual = len; +// + long incrLen = len; + + DIError dierr = kDIErrNone; + uint8_t blkBuf[kBlkSize]; + long blockIndex = (long) (fOffset / kBlkSize); + int bufOffset = (int) (fOffset % kBlkSize); // (& 0x01ff) + size_t thisCount; + long progressCounter = 0; + + if (len == 0) { + ///* one block allocated for empty file */ + //SetLastBlock(fBlockList[0], true); + return kDIErrNone; + } + assert(fOpenEOF != 0); + + assert(blockIndex >= 0 && blockIndex < fBlockCount); + + while (len) { + if (fBlockList[blockIndex] == 0) { + //LOGI(" ProDOS sparse index %d", blockIndex); + memset(blkBuf, 0, sizeof(blkBuf)); + } else { + //LOGI(" ProDOS non-sparse index %d", blockIndex); + dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock(fBlockList[blockIndex], + blkBuf); + if (dierr != kDIErrNone) { + LOGI(" ProDOS error reading block [%ld]=%d of '%s'", + blockIndex, fBlockList[blockIndex], fpFile->GetPathName()); + return dierr; + } + } + thisCount = kBlkSize - bufOffset; + if (thisCount > len) + thisCount = len; + + memcpy(buf, blkBuf + bufOffset, thisCount); + len -= thisCount; + buf = (char*)buf + thisCount; + + bufOffset = 0; + blockIndex++; + + progressCounter++; + if (progressCounter > 100 && len) { + progressCounter = 0; + /* + * Show progress within the current read request. This only + * kicks in for large reads, e.g. reformatting the entire file. + * For smaller reads, used when we're extracting w/o reformatting, + * "progressCounter" never gets large enough. + */ + if (!UpdateProgress(fOffset + incrLen - len)) { + dierr = kDIErrCancelled; + return dierr; + } + //::Sleep(100); // DEBUG DEBUG + } + } + + fOffset += incrLen; + + if (!UpdateProgress(fOffset)) + dierr = kDIErrCancelled; + + return dierr; +} + +/* + * Write data at the current offset. + * + * For simplicity, we assume that there can only be one of two situations: + * (1) We're writing a directory, which might expand by one block; or + * (2) We're writing all of a brand-new file in one shot. + * + * Modifies fOpenEOF, fOpenBlocksUsed, fStorageType, and sets fModified. + * + * HEY: ProSel-16 describes these as fragmented, and it's probably right. + * The correct way to do this is to allocate index blocks before allocating + * the blocks they refer to, so that we don't have to jump all over the disk + * to read the indexes (which, at the moment, appear at the end of the file). + * A bit tricky, but doable. + */ +DIError A2FDProDOS::Write(const void* buf, size_t len, size_t* pActual) +{ + DIError dierr = kDIErrNone; + A2FileProDOS* pFile = (A2FileProDOS*) fpFile; + DiskFSProDOS* pDiskFS = (DiskFSProDOS*) fpFile->GetDiskFS(); + bool allocSparse = (pDiskFS->GetParameter(DiskFS::kParmProDOS_AllocSparse) != 0); + uint8_t blkBuf[kBlkSize]; + uint16_t keyBlock; + bool allZero = true; + const uint8_t* scanPtr = (const uint8_t*)buf; + + if (len >= 0x01000000) { // 16MB + assert(false); + return kDIErrInvalidArg; + } + + /* use separate function for directories */ + if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageDirectory || + pFile->fDirEntry.storageType == A2FileProDOS::kStorageVolumeDirHeader) + { + return WriteDirectory(buf, len, pActual); + } + + dierr = pDiskFS->LoadVolBitmap(); + if (dierr != kDIErrNone) + goto bail; + + assert(fOffset == 0); // big simplifying assumption + assert(fOpenEOF == 0); // another one + assert(fOpenBlocksUsed == 1); + assert(buf != NULL); + + /* nothing to do for zero-length write; don't even set fModified */ + if (len == 0) + goto bail; + + if (pFile->fDirEntry.storageType != A2FileProDOS::kStorageExtended) + keyBlock = pFile->fDirEntry.keyPointer; + else { + if (fOpenRsrcFork) + keyBlock = pFile->fExtRsrc.keyBlock; + else + keyBlock = pFile->fExtData.keyBlock; + } + + /* + * See if the file is completely empty. This lets us do an optimization + * where we store it as a seedling. (GS/OS seems to do this, ProDOS 8 + * v2.0.3 tends to allocate the first block.) + */ + for (unsigned int i = 0; i < len; ++i, ++scanPtr) { + if (*scanPtr != 0x00) { + allZero = false; + break; + } + } + if (allZero) { + LOGI("+++ found file filled with %zd zeroes", len); + } + + /* + * Special-case seedling files. Just write the data into the key block + * and we're done. + */ + if (allZero || len <= (size_t)kBlkSize) { + memset(blkBuf, 0, sizeof(blkBuf)); + if (!allZero) { + memcpy(blkBuf, buf, len); + } else { + LOGI("+++ ProDOS storing large but empty file as seedling"); + } + dierr = pDiskFS->GetDiskImg()->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + fOpenEOF = len; + fOpenBlocksUsed = 1; + assert(fOpenStorageType == A2FileProDOS::kStorageSeedling); + fOffset += len; + fModified = true; + goto bail; + } + + /* + * Start by allocating space for the block list. The list is always the + * same size, regardless of sparse allocations. + * + * We over-alloc by one so we can have an overrun detection entry. + */ + fBlockCount = (len + kBlkSize-1) / kBlkSize; + assert(fBlockCount > 0); + delete[] fBlockList; + fBlockList = new uint16_t[fBlockCount+1]; + if (fBlockList == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + fBlockList[fBlockCount] = A2FileProDOS::kInvalidBlockNum; + + /* + * Write the data blocks to disk, allocating as we go. We have to treat + * the last entry specially because it might not fill an entire block. + */ + const uint8_t* blkPtr; + long blockIdx; + long progressCounter; + + progressCounter = 0; + blkPtr = (const uint8_t*) buf; + for (blockIdx = 0; blockIdx < fBlockCount; blockIdx++) { + long newBlock; + + if (blockIdx == fBlockCount-1) { + /* for last block, copy partial and move blkPtr */ + int copyLen = len - (blockIdx * kBlkSize); + assert(copyLen > 0 && copyLen <= kBlkSize); + memset(blkBuf, 0, sizeof(blkBuf)); + memcpy(blkBuf, blkPtr, copyLen); + blkPtr = blkBuf; + } + + if (allocSparse && IsEmptyBlock(blkPtr)) { + if (blockIdx == 0) { + // Fix for issues #18 and #49. GS/OS appears to get confused + // if the first entry in the master index block for a "tree" + // file is zero. We can avoid the problem by always allocating + // the first data block, which causes allocation of the first + // index block. (The "all zeroes" case was handled earlier, + // so if we got here we know this won't be an empty seedling.) + LOGI("+++ allocating storage for empty first block"); + newBlock = pDiskFS->AllocBlock(); + fOpenBlocksUsed++; + } else { + // Sparse. + newBlock = 0; + } + } else { + newBlock = pDiskFS->AllocBlock(); + fOpenBlocksUsed++; + } + + if (newBlock < 0) { + LOGI(" ProDOS disk full during write!"); + dierr = kDIErrDiskFull; + goto bail; + } + + fBlockList[blockIdx] = (uint16_t) newBlock; + + if (newBlock != 0) { + dierr = pDiskFS->GetDiskImg()->WriteBlock(newBlock, blkPtr); + if (dierr != kDIErrNone) + goto bail; + } + + blkPtr += kBlkSize; + + /* + * Update the progress counter and check to see if the "cancel" button + * has been hit. We don't call UpdateProgress on the last block + * because we could be passing an offset value larger than "len". + * Also, we don't want the progress bar to hit 100% until we've + * actually finished. + * + * We do NOT want to check this after we start writing index blocks. + * If we do, we need to make sure that whatever index blocks the file + * has match up with what we've allocated in the disk block map. + * + * We don't want to save the disk block map if the user cancels here, + * because then the blocks will be marked as "used" even though the + * index blocks for this file haven't been written yet. + * + * It's tricky to get this right, which is why we allocate space + * for the index blocks now -- running out of disk space and + * user cancellation are handled the same way. Once we get to the + * point where we're updating the file structure, we can neither be + * cancelled nor run out of space. (We can still hit a bad block, + * though, which we currently don't handle.) + */ + progressCounter++; // update every N blocks + if (progressCounter > 100 && blockIdx != fBlockCount) { + progressCounter = 0; + if (!UpdateProgress(blockIdx * kBlkSize)) { + dierr = kDIErrCancelled; + goto bail; + } + } + } + + assert(fBlockList[fBlockCount] == A2FileProDOS::kInvalidBlockNum); + + /* + * Now we have a full block map. Allocate any needed index blocks and + * write them. + */ +#if 0 // now done earlier + /* + * If our block map is empty, i.e. the entire file is sparse, then + * there's no need to create a sapling. We just leave the file in + * seedling form. This can only happen for a completely empty file. + */ + if (allZero) { + LOGI("+++ ProDOS storing large but empty file as seedling"); + /* make sure key block is empty */ + memset(blkBuf, 0, sizeof(blkBuf)); + dierr = pDiskFS->GetDiskImg()->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + fOpenStorageType = A2FileProDOS::kStorageSeedling; + fBlockList[0] = keyBlock; + } else +#endif + if (fBlockCount <= 256) { + /* sapling file, write an index block into the key block */ + //bool allzero = true; <-- should this be getting used? + assert(fBlockCount > 1); + memset(blkBuf, 0, sizeof(blkBuf)); + int i; + for (i = 0; i < fBlockCount; i++) { + //if (fBlockList[i] != 0) + // allzero = false; + blkBuf[i] = fBlockList[i] & 0xff; + blkBuf[256 + i] = (fBlockList[i] >> 8) & 0xff; + } + + dierr = pDiskFS->GetDiskImg()->WriteBlock(keyBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + fOpenStorageType = A2FileProDOS::kStorageSapling; + } else { + /* tree file, write two or more indexes and write master into key */ + uint8_t masterBlk[kBlkSize]; + int idx; + + memset(masterBlk, 0, sizeof(masterBlk)); + + for (idx = 0; idx < fBlockCount; ) { + long newBlock; + int i; + + memset(blkBuf, 0, sizeof(blkBuf)); + for (i = 0; i < 256 && idx < fBlockCount; i++, idx++) { + blkBuf[i] = fBlockList[idx] & 0xff; + blkBuf[256+i] = (fBlockList[idx] >> 8) & 0xff; + } + + /* allocate a new index block, if needed */ + if (allocSparse && IsEmptyBlock(blkBuf)) + newBlock = 0; + else { + newBlock = pDiskFS->AllocBlock(); + fOpenBlocksUsed++; + } + if (newBlock != 0) { + dierr = pDiskFS->GetDiskImg()->WriteBlock(newBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + } + + masterBlk[(idx-1) / 256] = (uint8_t) newBlock; + masterBlk[256 + (idx-1)/256] = (uint8_t) (newBlock >> 8); + } + + dierr = pDiskFS->GetDiskImg()->WriteBlock(keyBlock, masterBlk); + if (dierr != kDIErrNone) + goto bail; + fOpenStorageType = A2FileProDOS::kStorageTree; + } + + fOpenEOF = len; + fOffset += len; + fModified = true; + +bail: + if (dierr == kDIErrNone) + dierr = pDiskFS->SaveVolBitmap(); + + /* + * We need to check UpdateProgress *after* the volume bitmap has been + * saved. Otherwise we'll have blocks allocated in the file's structure + * but not marked in-use in the map when the "dierr" check above fails. + */ + if (dierr == kDIErrNone) { + if (!UpdateProgress(fOffset)) + dierr = kDIErrCancelled; + } + + pDiskFS->FreeVolBitmap(); + return dierr; +} + +/* + * Determine whether a block is filled entirely with zeroes. + */ +bool A2FDProDOS::IsEmptyBlock(const uint8_t* blk) +{ + int i; + + for (i = 0; i < kBlkSize; i++) { + if (*blk++ != 0) + return false; + } + + return true; +} + +/* + * Write a directory, possibly extending it by one block. + * + * If we're growing, the extra block will already have been allocated, and is + * pointed to by the "next" pointer in the next-to-last block. (This + * pre-allocation makes our lives easier, and avoids a situation where we + * would have to update the volume bitmap when another function is already + * making lots of changes to it.) + */ +DIError A2FDProDOS::WriteDirectory(const void* buf, size_t len, size_t* pActual) +{ + DIError dierr = kDIErrNone; + + LOGD("ProDOS writing %lu bytes to directory '%s'", + (unsigned long) len, fpFile->GetPathName()); + + assert(len >= (size_t)kBlkSize); + assert((len % kBlkSize) == 0); + assert(len == (size_t)fOpenEOF || len == (size_t)fOpenEOF + kBlkSize); + + if (len > (size_t)fOpenEOF) { + /* + * Extend the block list, remembering that we add an extra item + * on the end to check for overruns. + */ + uint16_t* newBlockList; + + fBlockCount++; + newBlockList = new uint16_t[fBlockCount+1]; + memcpy(newBlockList, fBlockList, + sizeof(uint16_t) * fBlockCount); + newBlockList[fBlockCount] = A2FileProDOS::kInvalidBlockNum; + + uint8_t* blkPtr; + blkPtr = (uint8_t*)buf + fOpenEOF - kBlkSize; + assert(blkPtr >= buf); + assert(GetShortLE(&blkPtr[0x02]) != 0); + newBlockList[fBlockCount-1] = GetShortLE(&blkPtr[0x02]); + + delete[] fBlockList; + fBlockList = newBlockList; + + LOGI(" ProDOS updated block list for subdir:"); + DumpBlockList(); + } + + /* + * Now just run down the block list writing the directory. + */ + assert(len == (size_t)fBlockCount * kBlkSize); + int idx; + for (idx = 0; idx < fBlockCount; idx++) { + assert(fBlockList[idx] >= kVolHeaderBlock); + dierr = fpFile->GetDiskFS()->GetDiskImg()->WriteBlock(fBlockList[idx], + (uint8_t*)buf + idx * kBlkSize); + if (dierr != kDIErrNone) { + LOGI(" ProDOS failed writing dir, block=%d", fBlockList[idx]); + goto bail; + } + } + + fOpenEOF = len; + fOpenBlocksUsed = (uint16_t) fBlockCount; // very simple for subdirs + //fOpenStorageType + fModified = true; + +bail: + return dierr; +} + +/* + * Seek to a new position within the file. + */ +DIError A2FDProDOS::Seek(di_off_t offset, DIWhence whence) +{ + DIError dierr = kDIErrNone; + switch (whence) { + case kSeekSet: + if (offset < 0 || offset > fOpenEOF) + return kDIErrInvalidArg; + fOffset = offset; + break; + case kSeekEnd: + if (offset > 0 || offset < -fOpenEOF) + return kDIErrInvalidArg; + fOffset = fOpenEOF + offset; + break; + case kSeekCur: + if (offset < -fOffset || + offset >= (fOpenEOF - fOffset)) + { + return kDIErrInvalidArg; + } + fOffset += offset; + break; + default: + assert(false); + return kDIErrInvalidArg; + } + + assert(fOffset >= 0 && fOffset <= fOpenEOF); + + return dierr; +} + +/* + * Return current offset. + */ +di_off_t A2FDProDOS::Tell(void) +{ + //if (fBlockList == NULL) + // return kDIErrNotReady; + + return fOffset; +} + +/* + * Release file state. + * + * 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 A2FDProDOS::Close(void) +{ + DIError dierr = kDIErrNone; + + if (fModified) { + A2FileProDOS* pFile = (A2FileProDOS*) fpFile; + uint8_t blkBuf[kBlkSize]; + uint8_t newStorageType = fOpenStorageType; + uint16_t newBlocksUsed = fOpenBlocksUsed; + uint32_t newEOF = (uint32_t) fOpenEOF; // TODO: assert range + uint16_t combinedBlocksUsed; + uint32_t combinedEOF; + + /* + * If this is an extended file, fix the entries in the extended + * key block, and adjust the values to be stored in the directory. + */ + if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageExtended) { + /* these two don't change */ + newStorageType = pFile->fDirEntry.storageType; + + dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock( + pFile->fDirEntry.keyPointer, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + int offset = 0; + if (fOpenRsrcFork) + offset = 256; + + blkBuf[0x00 + offset] = fOpenStorageType; + // key block doesn't change + PutShortLE(&blkBuf[0x03 + offset], newBlocksUsed); + blkBuf[0x05 + offset] = (uint8_t) newEOF; + blkBuf[0x06 + offset] = (uint8_t) (newEOF >> 8); + blkBuf[0x07 + offset] = (uint8_t) (newEOF >> 16); + + dierr = fpFile->GetDiskFS()->GetDiskImg()->WriteBlock( + pFile->fDirEntry.keyPointer, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + // file blocks used is sum of data and rsrc block counts +1 for key + combinedBlocksUsed = + GetShortLE(&blkBuf[0x03]) + GetShortLE(&blkBuf[0x103]) +1; + combinedEOF = 512; // for some reason this gets stuffed in + } else { + combinedBlocksUsed = newBlocksUsed; + combinedEOF = newEOF; + } + + /* + * Update fields in the file's directory entry. Unless, of course, + * this is the volume directory itself. + */ + if (pFile->fParentDirBlock != 0) { + dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock( + pFile->fParentDirBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + + uint8_t* pParentPtr; + pParentPtr = blkBuf + 0x04 + pFile->fParentDirIdx * kEntryLength; + assert(pParentPtr + kEntryLength < blkBuf + kBlkSize); + if (toupper(pParentPtr[0x01]) != toupper(pFile->fDirEntry.fileName[0])) + { + LOGW("ProDOS ERROR: parent pointer has wrong entry??"); + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + /* update the fields from the open file */ + pParentPtr[0x00] = + (pParentPtr[0x00] & 0x0f) | (newStorageType << 4); + PutShortLE(&pParentPtr[0x13], combinedBlocksUsed); + if (pFile->fDirEntry.storageType != A2FileProDOS::kStorageExtended) + { + PutShortLE(&pParentPtr[0x15], (uint16_t) newEOF); + pParentPtr[0x17] = (uint8_t) (newEOF >> 16); + } + /* don't update the mod date for now */ + //PutLongLE(&pParentPtr[0x21], A2FileProDOS::ConvertProDate(time(NULL))); + + dierr = fpFile->GetDiskFS()->GetDiskImg()->WriteBlock( + pFile->fParentDirBlock, blkBuf); + if (dierr != kDIErrNone) + goto bail; + } + + /* + * Find the #of sparse blocks. We do this to update the "sparse EOF", + * which determines the "compressed" size shown in the file list. We + * have two cases: normal file with sparse contents, and seedling file + * that is entirely sparse except for the first block. + * + * In the normal case, we walk through the list of data blocks, + * looking for gaps. In the seedling case, we just use the EOF. + * + * This is just for display. The value seen after adding a file should + * not change if you reload the disk image. + */ + int sparseBlocks = 0; + if (fBlockCount == 1 && fOpenEOF > kBlkSize) { + // 1023/1024 = 2 blocks = 1 sparse + // 1025 = 3 blocks = 2 sparse + sparseBlocks = (int)((fOpenEOF-1) / kBlkSize); + } else { + for (int i = 0; i < fBlockCount; i++) { + if (fBlockList[i] == 0) + sparseBlocks++; + } + } + + /* + * Update our internal copies of stuff. The EOFs have changed, and + * in theory we'd want to update the modification date. In practice + * we're usually shuffling data from one archive to another and want + * to preserve the mod date. (Could be a DiskFS global pref?) + */ + pFile->fDirEntry.storageType = newStorageType; + pFile->fDirEntry.blocksUsed = combinedBlocksUsed; + pFile->fDirEntry.eof = combinedEOF; + + if (newStorageType == A2FileProDOS::kStorageExtended) { + if (!fOpenRsrcFork) { + pFile->fExtData.storageType = fOpenStorageType; + pFile->fExtData.blocksUsed = newBlocksUsed; + pFile->fExtData.eof = newEOF; + pFile->fSparseDataEof = (di_off_t) newEOF - (sparseBlocks * kBlkSize); + if (pFile->fSparseDataEof < 0) + pFile->fSparseDataEof = 0; + } else { + pFile->fExtRsrc.storageType = fOpenStorageType; + pFile->fExtRsrc.blocksUsed = newBlocksUsed; + pFile->fExtRsrc.eof = newEOF; + pFile->fSparseRsrcEof = (di_off_t) newEOF - (sparseBlocks * kBlkSize); + if (pFile->fSparseRsrcEof < 0) + pFile->fSparseRsrcEof = 0; + } + } else { + pFile->fSparseDataEof = (di_off_t) newEOF - (sparseBlocks * kBlkSize); + if (pFile->fSparseDataEof < 0) + pFile->fSparseDataEof = 0; + } + // update mod date? + + //LOGI("File '%s' closed", pFile->GetPathName()); + //pFile->Dump(); + } + +bail: + fpFile->CloseDescr(this); + return dierr; +} + + +/* + * Return the #of sectors/blocks in the file. + */ +long A2FDProDOS::GetSectorCount(void) const +{ + //if (fBlockList == NULL) + // return kDIErrNotReady; + return fBlockCount * 2; +} + +long A2FDProDOS::GetBlockCount(void) const +{ + //if (fBlockList == NULL) + // return kDIErrNotReady; + return fBlockCount; +} + +/* + * Return the Nth track/sector in this file. + */ +DIError A2FDProDOS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const +{ + //if (fBlockList == NULL) + // return kDIErrNotReady; + long prodosIdx = sectorIdx / 2; + if (prodosIdx < 0 || prodosIdx >= fBlockCount) + return kDIErrInvalidIndex; + long prodosBlock = fBlockList[prodosIdx]; + + if (prodosBlock == 0) + *pTrack = *pSector = 0; // special-case to avoid returning (0,1) + else + BlockToTrackSector(prodosBlock, (sectorIdx & 0x01) != 0, pTrack, pSector); + return kDIErrNone; +} +/* + * Return the Nth 512-byte block in this file. + */ +DIError A2FDProDOS::GetStorage(long blockIdx, long* pBlock) const +{ + //if (fBlockList == NULL) + // return kDIErrNotReady; + if (blockIdx < 0 || blockIdx >= fBlockCount) + return kDIErrInvalidIndex; + long prodosBlock = fBlockList[blockIdx]; + + *pBlock = prodosBlock; + assert(*pBlock < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks()); + return kDIErrNone; +} + +/* + * Dump the list of blocks from an open file, skipping over + * "sparsed-out" entries. + */ +void A2FDProDOS::DumpBlockList(void) const +{ + long ll; + + LOGI(" ProDOS file block list (count=%ld)", fBlockCount); + for (ll = 0; ll <= fBlockCount; ll++) { + if (fBlockList[ll] != 0) { + LOGI(" %5ld: 0x%04x", ll, fBlockList[ll]); + } + } +} diff --git a/diskimg/RDOS.cpp b/diskimg/RDOS.cpp new file mode 100644 index 0000000..7cc698e --- /dev/null +++ b/diskimg/RDOS.cpp @@ -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 = ""; + 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; +} diff --git a/diskimg/README.md b/diskimg/README.md new file mode 100644 index 0000000..05f10d3 --- /dev/null +++ b/diskimg/README.md @@ -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. + diff --git a/diskimg/SCSIDefs.h b/diskimg/SCSIDefs.h new file mode 100644 index 0000000..58ae883 --- /dev/null +++ b/diskimg/SCSIDefs.h @@ -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*/ diff --git a/diskimg/SPTI.cpp b/diskimg/SPTI.cpp new file mode 100644 index 0000000..10f962f --- /dev/null +++ b/diskimg/SPTI.cpp @@ -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*/ diff --git a/diskimg/SPTI.h b/diskimg/SPTI.h new file mode 100644 index 0000000..0f14ef4 --- /dev/null +++ b/diskimg/SPTI.h @@ -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*/ diff --git a/diskimg/StdAfx.cpp b/diskimg/StdAfx.cpp new file mode 100644 index 0000000..610fe81 --- /dev/null +++ b/diskimg/StdAfx.cpp @@ -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 diff --git a/diskimg/StdAfx.h b/diskimg/StdAfx.h new file mode 100644 index 0000000..358b93d --- /dev/null +++ b/diskimg/StdAfx.h @@ -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 +#include +#include +#include + +#ifndef _WIN32 + +/* UNIX includes */ +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#ifdef HAVE_WINDOWS_CDROM +# include +#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*/ diff --git a/diskimg/TwoImg.cpp b/diskimg/TwoImg.cpp new file mode 100644 index 0000000..e42499a --- /dev/null +++ b/diskimg/TwoImg.cpp @@ -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("---"); +} diff --git a/diskimg/TwoImg.h b/diskimg/TwoImg.h new file mode 100644 index 0000000..c554ddf --- /dev/null +++ b/diskimg/TwoImg.h @@ -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*/ diff --git a/diskimg/UNIDOS.cpp b/diskimg/UNIDOS.cpp new file mode 100644 index 0000000..f47dd7a --- /dev/null +++ b/diskimg/UNIDOS.cpp @@ -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; +} diff --git a/diskimg/VolumeUsage.cpp b/diskimg/VolumeUsage.cpp new file mode 100644 index 0000000..14d40f5 --- /dev/null +++ b/diskimg/VolumeUsage.cpp @@ -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); + } + } +} diff --git a/diskimg/Win32BlockIO.cpp b/diskimg/Win32BlockIO.cpp new file mode 100644 index 0000000..45ae6de --- /dev/null +++ b/diskimg/Win32BlockIO.cpp @@ -0,0 +1,2127 @@ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Win32 block I/O routines. + * + * See the header file for an explanation of why. + */ +#include "StdAfx.h" +#ifdef _WIN32 +#include "DiskImgPriv.h" +#include "SCSIDefs.h" +#include "ASPI.h" +#include "SPTI.h" + + +/* + * This is an ugly hack until I can figure out the right way to do this. To + * help prevent people from trashing their Windows volumes, we refuse to + * open "C:\" or physical0 for writing. On most systems, this is fine. + * + * The problem is that, on some systems with a mix of SATA and IDE devices, + * the boot disk is not physical disk 0. There should be a way to determine + * which physical disk is used for booting, or which physical disk + * corresponds to "C:", but I haven't been able to find it. + * + * So, for now, allow a global setting that disables the protection. I'm + * doing it this way rather than passing a parameter through because it + * requires adding an argument to several layers of "open", and I'm hoping + * to make this go away. + */ +//extern bool DiskImgLib::gAllowWritePhys0; + + +/* + * =========================================================================== + * Win32VolumeAccess + * =========================================================================== + */ + +/* + * Open a logical volume. + */ +DIError Win32VolumeAccess::Open(const WCHAR* deviceName, bool readOnly) +{ + DIError dierr = kDIErrNone; + + assert(deviceName != NULL); + + if (fpBlockAccess != NULL) { + assert(false); + return kDIErrAlreadyOpen; + } + +#ifdef WANT_ASPI + if (strncmp(deviceName, kASPIDev, strlen(kASPIDev)) == 0) { + fpBlockAccess = new ASPIBlockAccess; + if (fpBlockAccess == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = fpBlockAccess->Open(deviceName, readOnly); + if (dierr != kDIErrNone) + goto bail; + } else +#endif + if (deviceName[0] >= 'A' && deviceName[0] <= 'Z') { + fpBlockAccess = new LogicalBlockAccess; + if (fpBlockAccess == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = fpBlockAccess->Open(deviceName, readOnly); + if (dierr != kDIErrNone) + goto bail; + } else if (deviceName[0] >= '0' && deviceName[0] <= '9') { + fpBlockAccess = new PhysicalBlockAccess; + if (fpBlockAccess == NULL) { + dierr = kDIErrMalloc; + goto bail; + } + dierr = fpBlockAccess->Open(deviceName, readOnly); + if (dierr != kDIErrNone) + goto bail; + } else { + LOGI(" Win32VA: '%s' isn't the name of a device", deviceName); + return kDIErrInternal; + } + + // Need to do this now so we can use floppy geometry. + dierr = fpBlockAccess->DetectCapacity(&fTotalBlocks); + if (dierr != kDIErrNone) + goto bail; + assert(fTotalBlocks >= 0); + +bail: + if (dierr != kDIErrNone) { + delete fpBlockAccess; + fpBlockAccess = NULL; + } + return dierr; +} + +/* + * Close the device. + */ +void Win32VolumeAccess::Close(void) +{ + if (fpBlockAccess != NULL) { + DIError dierr; + LOGI(" Win32VolumeAccess closing"); + + dierr = FlushCache(true); + if (dierr != kDIErrNone) { + LOGI("WARNING: Win32VA Close: FlushCache failed (err=%ld)", + dierr); + // not much we can do + } + dierr = fpBlockAccess->Close(); + if (dierr != kDIErrNone) { + LOGI("WARNING: Win32VolumeAccess BlockAccess Close failed (err=%ld)", + dierr); + } + delete fpBlockAccess; + fpBlockAccess = NULL; + } +} + +/* + * Read a range of blocks from the device. + * + * Because some things like to read partial blocks, we cache the last block + * we read whenever the caller asks for a single block. This results in + * increased data copying, but since we know we're reading from a logical + * volume it's safe to assume that memory is *many* times faster than + * reading from this handle. + * + * Returns with an error if any of the blocks could not be read. + */ +DIError Win32VolumeAccess::ReadBlocks(long startBlock, short blockCount, + void* buf) +{ + DIError dierr = kDIErrNone; + + assert(startBlock >= 0); + assert(blockCount > 0); + assert(buf != NULL); + assert(fpBlockAccess != NULL); + + if (blockCount == 1) { + if (fBlockCache.IsBlockInCache(startBlock)) { + fBlockCache.GetFromCache(startBlock, buf); + return kDIErrNone; + } + } else { + // If they're reading in large stretches, we don't need to use + // the cache. There is some chance that what we're about to + // read might include dirty blocks currently in the cache, so + // we flush what we have before the read. + dierr = FlushCache(true); + if (dierr != kDIErrNone) + return dierr; + } + + /* go read from the volume */ + dierr = fpBlockAccess->ReadBlocks(startBlock, blockCount, buf); + if (dierr != kDIErrNone) + goto bail; + + + /* if we're doing single-block reads, put it in the cache */ + if (blockCount == 1) { + if (!fBlockCache.IsRoomInCache(startBlock)) { + dierr = FlushCache(true); // make room + if (dierr != kDIErrNone) + goto bail; + } + + // after flushing, this should never fail + dierr = fBlockCache.PutInCache(startBlock, buf, false); + if (dierr != kDIErrNone) + goto bail; + } + +bail: + return dierr; +} + +/* + * Write a range of blocks to the device. For the most part this just + * writes to the cache. + * + * Returns with an error if any of the blocks could not be read. + */ +DIError Win32VolumeAccess::WriteBlocks(long startBlock, short blockCount, + const void* buf) +{ + DIError dierr = kDIErrNone; + + assert(startBlock >= 0); + assert(blockCount > 0); + assert(buf != NULL); + + if (blockCount == 1) { + /* is this block already in the cache? */ + if (!fBlockCache.IsBlockInCache(startBlock)) { + /* not present, make sure it fits */ + if (!fBlockCache.IsRoomInCache(startBlock)) { + dierr = FlushCache(true); // make room + if (dierr != kDIErrNone) + goto bail; + } + } + + // after flushing, this should never fail + dierr = fBlockCache.PutInCache(startBlock, buf, true); + if (dierr != kDIErrNone) + goto bail; + } else { + // If they're writing in large stretches, we don't need to use + // the cache. We need to flush the cache in case what we're about + // to write would overwrite something in the cache -- if we don't, + // what's in the cache will effectively revert what we're about to + // write. + dierr = FlushCache(true); + if (dierr != kDIErrNone) + goto bail; + dierr = DoWriteBlocks(startBlock, blockCount, buf); + if (dierr != kDIErrNone) + goto bail; + } + +bail: + return dierr; +} + +/* + * Write all blocks in the cache to disk if any of them are dirty. In some + * ways this is wasteful -- we could be writing stuff that isn't dirty, + * which isn't a great idea on (say) a CF volume -- but in practice we + * don't mix a lot of adjacent reads and writes, so this is pretty + * harmless. + * + * The goal was to write whole tracks on floppies. It's easy enough to + * disable the cache for CF devices if need be. + * + * If "purge" is set, we discard the blocks after writing them. + */ +DIError Win32VolumeAccess::FlushCache(bool purge) +{ + DIError dierr = kDIErrNone; + + //LOGI(" Win32VA: FlushCache (%d)", purge); + + if (fBlockCache.IsDirty()) { + long firstBlock; + int numBlocks; + void* buf; + + fBlockCache.GetCachePointer(&firstBlock, &numBlocks, &buf); + + LOGI("FlushCache writing first=%d num=%d (purge=%d)", + firstBlock, numBlocks, purge); + dierr = DoWriteBlocks(firstBlock, numBlocks, buf); + if (dierr != kDIErrNone) { + LOGI(" Win32VA: FlushCache write blocks failed (err=%d)", + dierr); + goto bail; + } + + // all written, clear the dirty flags + fBlockCache.Scrub(); + } + + if (purge) + fBlockCache.Purge(); + +bail: + return dierr; +} + + +/* + * =========================================================================== + * BlockAccess + * =========================================================================== + */ + +/* + * Detect the capacity of a drive using the SCSI READ CAPACITY command. Only + * works on CD-ROM drives and SCSI devices. + * + * Unfortunately, if you're accessing a hard drive through the BIOS, SPTI + * doesn't work. There must be a better way. + * + * On success, "*pNumBlocks" gets the number of 512-byte blocks. + */ +DIError Win32VolumeAccess::BlockAccess::DetectCapacitySPTI(HANDLE handle, + bool isCDROM, long* pNumBlocks) +{ +#ifndef HAVE_WINDOWS_CDROM + if (isCDROM) + return kDIErrCDROMNotSupported; +#endif + + DIError dierr = kDIErrNone; + uint32_t lba, blockLen; + + dierr = SPTI::GetDeviceCapacity(handle, &lba, &blockLen); + if (dierr != kDIErrNone) + goto bail; + + LOGI("READ CAPACITY reports lba=%u blockLen=%u (total=%u)", + lba, blockLen, lba*blockLen); + + if (isCDROM && blockLen != kCDROMSectorSize) { + LOGW("Unacceptable CD-ROM blockLen=%ld, bailing", blockLen); + dierr = kDIErrReadFailed; + goto bail; + } + + // The LBA is the last valid block on the disk. To get the disk size, + // we need to add one. + + *pNumBlocks = (blockLen/512) * (lba+1); + LOGI(" SPTI returning 512-byte block count %ld", *pNumBlocks); + +bail: + return dierr; +} + +/* + * Figure out how large this disk volume is by probing for readable blocks. + * We take some guesses for common sizes and then binary-search if necessary. + * + * CF cards typically don't have as much space as they're rated for, possibly + * because of bad-block mapping (either bad blocks or space reserved for when + * blocks do go bad). + * + * This sets "*pNumBlocks" on success. The largest size this will detect + * is currently 8GB. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::ScanCapacity(BlockAccess* pThis, + long* pNumBlocks) +{ + DIError dierr = kDIErrNone; + // max out at 8GB (-1 block) + const long kLargest = DiskImgLib::kVolumeMaxBlocks; + const long kTypicalSizes[] = { + // these must be in ascending order + 720*1024 / BlockAccess::kBlockSize, // 720K floppy + 1440*1024 / BlockAccess::kBlockSize, // 1.44MB floppy + 32*1024*1024 / BlockAccess::kBlockSize, // 32MB flash card + 64*1024*1024 / BlockAccess::kBlockSize, // 64MB flash card + 128*1024*1024 / BlockAccess::kBlockSize, // 128MB flash card + 256*1024*1024 / BlockAccess::kBlockSize, // 256MB flash card + //512*1024*1024 / BlockAccess::kBlockSize, // 512MB flash card + 2*1024*1024*(1024/BlockAccess::kBlockSize), // 2GB mark + kLargest + }; + long totalBlocks = 0; + int i; + + + // Trivial check to make sure *anything* works, and to establish block 0 + // as valid in case we have to bin-search. + if (!CanReadBlock(pThis, 0)) { + LOGI(" Win32VolumeAccess: can't read block 0"); + dierr = kDIErrReadFailed; + goto bail; + } + + for (i = 0; i < NELEM(kTypicalSizes); i++) { + if (!CanReadBlock(pThis, kTypicalSizes[i])) { + /* failed reading, see if N-1 is readable */ + if (CanReadBlock(pThis, kTypicalSizes[i] - 1)) { + /* found it */ + totalBlocks = kTypicalSizes[i]; + break; + } else { + /* we overshot, binary-search backwards */ + LOGI("OVERSHOT at %ld", kTypicalSizes[i]); + long good, bad; + if (i == 0) + good = 0; + else + good = kTypicalSizes[i-1]; // know this one is good + bad = kTypicalSizes[i]-1; // know this one is bad + + while (good != bad-1) { + long check = (good + bad) / 2; + assert(check > good); + assert(check < bad); + + if (CanReadBlock(pThis, check)) + good = check; + else + bad = check; + } + totalBlocks = bad; + break; + } + } + } + if (totalBlocks == 0) { + if (i == NELEM(kTypicalSizes)) { + /* huge volume, we never found a bad block */ + totalBlocks = kLargest; + } else { + /* we never found a good block */ + LOGI(" Win32VolumeAccess unable to determine size"); + dierr = kDIErrReadFailed; + goto bail; + } + } + + if (totalBlocks > (3 * 1024 * 1024) / BlockAccess::kBlockSize) { + LOGI(" GFDWinVolume: size is %ld (%.2fMB)", + totalBlocks, (float) totalBlocks / (2048.0)); + } else { + LOGI(" GFDWinVolume: size is %ld (%.2fKB)", + totalBlocks, (float) totalBlocks / 2.0); + } + *pNumBlocks = totalBlocks; + +bail: + return dierr; +} + +/* + * Figure out if the block at "blockNum" exists. + */ +/*static*/ bool Win32VolumeAccess::BlockAccess::CanReadBlock(BlockAccess* pThis, + long blockNum) +{ + DIError dierr; + uint8_t buf[BlockAccess::kBlockSize]; + + dierr = pThis->ReadBlocks(blockNum, 1, buf); + if (dierr == kDIErrNone) { + LOGI(" +++ Checked %ld (good)", blockNum); + return true; + } else { + LOGI(" +++ Checked %ld (bad)", blockNum); + return false; + } +} + + +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER 0xFFFFFFFF +#endif + + +/* + * Most of these definitions come from MSFT knowledge base article #174569, + * "BUG: Int 21 Read/Write Track on Logical Drive Fails on OSR2 and Later". + */ +#define VWIN32_DIOC_DOS_IOCTL 1 // Int 21h 4400h through 4411h +#define VWIN32_DIOC_DOS_INT25 2 +#define VWIN32_DIOC_DOS_INT26 3 +#define VWIN32_DIOC_DOS_INT13 4 +#define VWIN32_DIOC_DOS_DRIVEINFO 6 // Int 21h 730x commands + +typedef struct _DIOC_REGISTERS { + DWORD reg_EBX; + DWORD reg_EDX; + DWORD reg_ECX; + DWORD reg_EAX; + DWORD reg_EDI; + DWORD reg_ESI; + DWORD reg_Flags; +} DIOC_REGISTERS, *PDIOC_REGISTERS; + +#define CARRY_FLAG 1 + +#pragma pack(1) +typedef struct _DISKIO { + DWORD dwStartSector; // starting logical sector number + WORD wSectors; // number of sectors + DWORD dwBuffer; // address of read/write buffer +} DISKIO, *PDISKIO; +typedef struct _DRIVEMAPINFO { + BYTE dmiAllocationLength; + BYTE dmiInfoLength; + BYTE dmiFlags; + BYTE dmiInt13Unit; + DWORD dmiAssociatedDriveMap; + DWORD dmiPartitionStartRBA_lo; + DWORD dmiPartitionStartRBA_hi; +} DRIVEMAPINFO, *PDRIVEMAPINFO; +#pragma pack() + +#define kInt13StatusMissingAddrMark 2 // sector number above media format +#define kInt13StatusWriteProtected 3 // disk is write protected +#define kInt13StatusSectorNotFound 4 // sector number above drive cap +// == 10 for bad blocks?? +#define kInt13StatusTimeout 128 // drive not responding + +#if 0 +/* + * Determine the mapping between a logical device number (A=1, B=2, etc) + * and the Int13h unit number (floppy=00h, hard drive=80h, etc). + * + * Pass in the vwin32 handle. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::GetInt13Unit(HANDLE handle, + int driveNum, int* pInt13Unit) +{ + DIError dierr = kDIErrNone; + BOOL result; + DWORD cb; + DRIVEMAPINFO driveMapInfo = {0}; + DIOC_REGISTERS reg = {0}; + const int kGetDriveMapInfo = 0x6f; + const int kDeviceCategory1 = 0x08; // for older stuff + const int kDeviceCategory2 = 0x48; // for FAT32 + + assert(handle != NULL); + assert(driveNum > 0 && driveNum <= kNumLogicalVolumes); + assert(pInt13Unit != NULL); + + *pInt13Unit = -1; + + driveMapInfo.dmiAllocationLength = sizeof(DRIVEMAPINFO); // should be 16 + + reg.reg_EAX = 0x440d; // generic IOCTL + reg.reg_EBX = driveNum; + reg.reg_ECX = MAKEWORD(kGetDriveMapInfo, kDeviceCategory1); + reg.reg_EDX = (DWORD) &driveMapInfo; + + result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof(reg), + ®, sizeof(reg), &cb, 0); + if (result == 0) { + LOGI(" DeviceIoControl(Int21h, 6fh) FAILED (err=%ld)", + GetLastError()); + dierr = LastErrorToDIError(); + goto bail; + } + + if (reg.reg_Flags & CARRY_FLAG) { + LOGI(" --- retrying GDMI with alternate device category (ax=%ld)", + reg.reg_EAX); + reg.reg_EAX = 0x440d; // generic IOCTL + reg.reg_EBX = driveNum; + reg.reg_ECX = MAKEWORD(kGetDriveMapInfo, kDeviceCategory2); + reg.reg_EDX = (DWORD) &driveMapInfo; + result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof(reg), + ®, sizeof(reg), &cb, 0); + } + if (result == 0) { + LOGI(" DeviceIoControl(Int21h, 6fh)(retry) FAILED (err=%ld)", + GetLastError()); + dierr = LastErrorToDIError(); + goto bail; + } + if (reg.reg_Flags & CARRY_FLAG) { + LOGI(" --- GetDriveMapInfo call (retry) failed (ax=%ld)", + reg.reg_EAX); + dierr = kDIErrReadFailed; // close enough + goto bail; + } + + LOGI(" +++ DriveMapInfo len=%d flags=%d unit=%d", + driveMapInfo.dmiInfoLength, driveMapInfo.dmiFlags, + driveMapInfo.dmiInt13Unit); + LOGI(" +++ driveMap=0x%08lx RBA=0x%08lx 0x%08lx", + driveMapInfo.dmiAssociatedDriveMap, + driveMapInfo.dmiPartitionStartRBA_hi, + driveMapInfo.dmiPartitionStartRBA_lo); + + if (driveMapInfo.dmiInfoLength < 4) { + /* results not covered in reply? */ + dierr = kDIErrReadFailed; // not close, but it'll do + goto bail; + } + + *pInt13Unit = driveMapInfo.dmiInt13Unit; + +bail: + return dierr; +} +#endif + +#if 0 +/* + * Look up the geometry for a floppy disk whose total size is "totalBlocks". + * There is no BIOS function to detect the media size and geometry, so + * we have to do it this way. PC "FAT" disks have a size indication + * in the boot block, but we can't rely on that. + * + * Returns "true" if the geometry is known, "false" otherwise. When "true" + * is returned, "*pNumTracks", "*pNumHeads", and "*pNumSectors" will receive + * values if the pointers are non-NULL. + */ +/*static*/ bool Win32VolumeAccess::BlockAccess::LookupFloppyGeometry(long totalBlocks, + DiskGeometry* pGeometry) +{ + static const struct { + FloppyKind kind; + long blockCount; // total #of blocks on the disk + int numCyls; // #of cylinders + int numHeads; // #of heads per cylinder + int numSectors; // #of sectors/track + } floppyGeometry[] = { + { kFloppyUnknown, -1, -1, -1, -1 }, + { kFloppy525_360, 360*2, 40, 2, 9 }, + { kFloppy525_1200, 1200*2, 80, 2, 15 }, + { kFloppy35_720, 720*2, 80, 2, 9 }, + { kFloppy35_1440, 1440*2, 80, 2, 18 }, + { kFloppy35_2880, 2880*2, 80, 2, 36 } + }; + + /* verify that we can directly index the table with the enum */ + for (int chk = 0; chk < NELEM(floppyGeometry); chk++) { + assert(floppyGeometry[chk].kind == chk); + } + assert(chk == kFloppyMax); + + if (totalBlocks <= 0) { + // still auto-detecting volume size? + return false; + } + + int i; + for (i = 0; i < NELEM(floppyGeometry); i++) + if (floppyGeometry[i].blockCount == totalBlocks) + break; + + if (i == NELEM(floppyGeometry)) { + LOGI( "GetFloppyGeometry: no match for blocks=%ld", totalBlocks); + return false; + } + + pGeometry->numCyls = floppyGeometry[i].numCyls; + pGeometry->numHeads = floppyGeometry[i].numHeads; + pGeometry->numSectors = floppyGeometry[i].numSectors; + + return true; +} +#endif + +/* + * Convert a block number to a cylinder/head/sector offset. Also figures + * out what the last block on the current track is. Sectors are returned + * in 1-based form. + * + * Returns "true" on success, "false" on failure. + */ +/*static*/ bool Win32VolumeAccess::BlockAccess::BlockToCylinderHeadSector(long blockNum, + const DiskGeometry* pGeometry, int* pCylinder, int* pHead, + int* pSector, long* pLastBlockOnTrack) +{ + int cylinder, head, sector; + long lastBlockOnTrack; + int leftOver; + + cylinder = blockNum / (pGeometry->numSectors * pGeometry->numHeads); + leftOver = blockNum - cylinder * (pGeometry->numSectors * pGeometry->numHeads); + head = leftOver / pGeometry->numSectors; + sector = leftOver - (head * pGeometry->numSectors); + + assert(cylinder >= 0 && cylinder < pGeometry->numCyls); + assert(head >= 0 && head < pGeometry->numHeads); + assert(sector >= 0 && sector < pGeometry->numSectors); + + lastBlockOnTrack = blockNum + (pGeometry->numSectors - sector -1); + + if (pCylinder != NULL) + *pCylinder = cylinder; + if (pHead != NULL) + *pHead = head; + if (pSector != NULL) + *pSector = sector+1; + if (pLastBlockOnTrack != NULL) + *pLastBlockOnTrack = lastBlockOnTrack; + + return true; +} + +/* + * Get the floppy drive kind (*not* the media kind) using Int13h func 8. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::GetFloppyDriveKind(HANDLE handle, + int unitNum, FloppyKind* pKind) +{ + DIOC_REGISTERS reg = {0}; + DWORD cb; + BOOL result; + + reg.reg_EAX = MAKEWORD(0, 0x08); // Read Diskette Drive Parameters + reg.reg_EDX = MAKEWORD(unitNum, 0); + + result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, ®, + sizeof(reg), ®, sizeof(reg), &cb, 0); + + if (result == 0 || (reg.reg_Flags & CARRY_FLAG)) { + LOGI(" GetFloppyKind failed: result=%d flags=0x%04x AH=%d", + result, reg.reg_Flags, HIBYTE(reg.reg_EAX)); + return kDIErrGeneric; + } + + int bl = LOBYTE(reg.reg_EBX); + if (bl > 0 && bl < 6) + *pKind = (FloppyKind) bl; + else { + LOGI(" GetFloppyKind: unrecognized kind %d", bl); + return kDIErrGeneric; + } + + return kDIErrNone; +} + +/* + * Read one or more blocks using Int13h services. This only works on + * floppy drives due to Win9x limitations. + * + * The average BIOS will only read one or two tracks reliably, and may + * not work well when straddling tracks. It's up to the caller to + * ensure that the parameters are set properly. + * + * "cylinder" and "head" are 0-based, "sector" is 1-based. + * + * Returns 0 on success, the status code from AH on failure. If the call + * fails but AH is zero, -1 is returned. + */ +/*static*/ int Win32VolumeAccess::BlockAccess::ReadBlocksInt13h(HANDLE handle, + int unitNum, int cylinder, int head, int sector, short blockCount, void* buf) +{ + DIOC_REGISTERS reg = {0}; + DWORD cb; + BOOL result; + + if (unitNum < 0 || unitNum >= 4) { + assert(false); + return kDIErrInternal; + } + + for (int retry = 0; retry < kMaxFloppyRetries; retry++) { + reg.reg_EAX = MAKEWORD(blockCount, 0x02); // read N sectors + reg.reg_EBX = (DWORD) buf; + reg.reg_ECX = MAKEWORD(sector, cylinder); + reg.reg_EDX = MAKEWORD(unitNum, head); + + //LOGI(" DIOC Int13h read c=%d h=%d s=%d rc=%d", + // cylinder, head, sector, blockCount); + result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, ®, + sizeof(reg), ®, sizeof(reg), &cb, 0); + + if (result != 0 && !(reg.reg_Flags & CARRY_FLAG)) + break; // success! + + /* if it's an invalid sector request, bail out immediately */ + if (HIBYTE(reg.reg_EAX) == kInt13StatusSectorNotFound || + HIBYTE(reg.reg_EAX) == kInt13StatusMissingAddrMark) + { + break; + } + LOGI(" DIOC soft read failure, ax=0x%08lx", reg.reg_EAX); + } + if (!result || (reg.reg_Flags & CARRY_FLAG)) { + int ah = HIBYTE(reg.reg_EAX); + LOGI(" DIOC read failed, result=%d ah=%ld", result, ah); + if (ah != 0) + return ah; + else + return -1; + } + + return 0; +} + +/* + * Read one or more blocks using Int13h services. This only works on + * floppy drives due to Win9x limitations. + * + * It's important to be able to read multiple blocks for performance + * reasons. Because this is fairly "raw", we have to retry it 3x before + * giving up. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::ReadBlocksInt13h(HANDLE handle, + int unitNum, const DiskGeometry* pGeometry, long startBlock, short blockCount, + void* buf) +{ + int cylinder, head, sector; + int status, runCount; + long lastBlockOnTrack; + + if (unitNum < 0 || unitNum >= 4) { + assert(false); + return kDIErrInternal; + } + if (startBlock < 0 || blockCount <= 0) + return kDIErrInvalidArg; + if (startBlock + blockCount > pGeometry->blockCount) { + LOGI(" ReadInt13h: invalid request for start=%ld count=%d", + startBlock, blockCount); + return kDIErrReadFailed; + } + + while (blockCount) { + int result; + result = BlockToCylinderHeadSector(startBlock, pGeometry, + &cylinder, &head, §or, &lastBlockOnTrack); + assert(result); + + /* + * According to "The Undocumented PC", the average BIOS will read + * at most one or two tracks reliably. It's really geared toward + * writing a single track or cylinder with one call. We want to + * be sure that our read doesn't straddle tracks, so we break it + * down as needed. + */ + runCount = lastBlockOnTrack - startBlock +1; + if (runCount > blockCount) + runCount = blockCount; + //LOGI("R runCount=%d lastBlkOnT=%d start=%d", + // runCount, lastBlockOnTrack, startBlock); + assert(runCount > 0); + + status = ReadBlocksInt13h(handle, unitNum, cylinder, head, sector, + runCount, buf); + if (status != 0) { + LOGI(" DIOC read failed (status=%d)", status); + return kDIErrReadFailed; + } + + startBlock += runCount; + blockCount -= runCount; + } + + return kDIErrNone; +} + +/* + * Write one or more blocks using Int13h services. This only works on + * floppy drives due to Win9x limitations. + * + * "cylinder" and "head" are 0-based, "sector" is 1-based. + * + * It's important to be able to write multiple blocks for performance + * reasons. Because this is fairly "raw", we have to retry it 3x before + * giving up. + */ +/*static*/ int Win32VolumeAccess::BlockAccess::WriteBlocksInt13h(HANDLE handle, + int unitNum, int cylinder, int head, int sector, short blockCount, + const void* buf) +{ + DIOC_REGISTERS reg = {0}; + DWORD cb; + BOOL result; + + for (int retry = 0; retry < kMaxFloppyRetries; retry++) { + reg.reg_EAX = MAKEWORD(blockCount, 0x03); // write N sectors + reg.reg_EBX = (DWORD) buf; + reg.reg_ECX = MAKEWORD(sector, cylinder); + reg.reg_EDX = MAKEWORD(unitNum, head); + + LOGI(" DIOC Int13h write c=%d h=%d s=%d rc=%d", + cylinder, head, sector, blockCount); + result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, ®, + sizeof(reg), ®, sizeof(reg), &cb, 0); + + if (result != 0 && !(reg.reg_Flags & CARRY_FLAG)) + break; // success! + + if (HIBYTE(reg.reg_EAX) == kInt13StatusWriteProtected) + break; // no point retrying this + LOGI(" DIOC soft write failure, ax=0x%08lx", reg.reg_EAX); + } + if (!result || (reg.reg_Flags & CARRY_FLAG)) { + int ah = HIBYTE(reg.reg_EAX); + LOGI(" DIOC write failed, result=%d ah=%ld", result, ah); + if (ah != 0) + return ah; + else + return -1; + } + + return 0; +} + +/* + * Write one or more blocks using Int13h services. This only works on + * floppy drives. + * + * It's important to be able to write multiple blocks for performance + * reasons. Because this is fairly "raw", we have to retry it 3x before + * giving up. + * + * Returns "true" on success, "false" on failure. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::WriteBlocksInt13h(HANDLE handle, + int unitNum, const DiskGeometry* pGeometry, long startBlock, short blockCount, + const void* buf) +{ + int cylinder, head, sector; + int status, runCount; + long lastBlockOnTrack; + + // make sure this is a floppy drive and we know which unit it is + if (unitNum < 0 || unitNum >= 4) { + assert(false); + return kDIErrInternal; + } + if (startBlock < 0 || blockCount <= 0) + return kDIErrInvalidArg; + if (startBlock + blockCount > pGeometry->blockCount) { + LOGI(" WriteInt13h: invalid request for start=%ld count=%d", + startBlock, blockCount); + return kDIErrWriteFailed; + } + + while (blockCount) { + int result; + result = BlockToCylinderHeadSector(startBlock, pGeometry, + &cylinder, &head, §or, &lastBlockOnTrack); + assert(result); + + runCount = lastBlockOnTrack - startBlock +1; + if (runCount > blockCount) + runCount = blockCount; + //LOGI("W runCount=%d lastBlkOnT=%d start=%d", + // runCount, lastBlockOnTrack, startBlock); + assert(runCount > 0); + + status = WriteBlocksInt13h(handle, unitNum, cylinder, head, sector, + runCount, buf); + if (status != 0) { + LOGI(" DIOC write failed (status=%d)", status); + if (status == kInt13StatusWriteProtected) + return kDIErrWriteProtected; + else + return kDIErrWriteFailed; + } + + startBlock += runCount; + blockCount -= runCount; + } + + return kDIErrNone; +} + +/* + * Read blocks from a Win9x logical volume, using Int21h func 7305h. Pass in + * a handle to vwin32 and the logical drive number (A=1, B=2, etc). + * + * Works on Win95 OSR2 and later. Earlier versions require Int25 or Int13. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::ReadBlocksInt21h(HANDLE handle, + int driveNum, long startBlock, short blockCount, void* buf) +{ +#if 0 + assert(false); // discouraged + BOOL result; + DWORD cb; + DIOC_REGISTERS reg = {0}; + DISKIO dio = {0}; + + dio.dwStartSector = startBlock; + dio.wSectors = (WORD) blockCount; + dio.dwBuffer = (DWORD) buf; + + reg.reg_EAX = fDriveNum - 1; // Int 25h drive numbers are 0-based. + reg.reg_EBX = (DWORD)&dio; + reg.reg_ECX = 0xFFFF; // use DISKIO struct + + LOGI(" Int25 read start=%d count=%d", + startBlock, blockCount); + result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_INT25, + ®, sizeof(reg), + ®, sizeof(reg), &cb, 0); + + // Determine if the DeviceIoControl call and the read succeeded. + result = result && !(reg.reg_Flags & CARRY_FLAG); + + LOGI(" +++ read from block %ld (result=%d)", startBlock, result); + if (!result) + return kDIErrReadFailed; +#else + BOOL result; + DWORD cb; + DIOC_REGISTERS reg = {0}; + DISKIO dio = {0}; + + dio.dwStartSector = startBlock; + dio.wSectors = (WORD) blockCount; + dio.dwBuffer = (DWORD) buf; + + reg.reg_EAX = 0x7305; // Ext_ABSDiskReadWrite + reg.reg_EBX = (DWORD)&dio; + reg.reg_ECX = -1; + reg.reg_EDX = driveNum; // Int 21h, fn 7305h drive numbers are 1-based + assert(reg.reg_ESI == 0); // read/write flag + + LOGI(" Int21/7305h read start=%d count=%d", + startBlock, blockCount); + result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_DRIVEINFO, + ®, sizeof(reg), + ®, sizeof(reg), &cb, 0); + + // Determine if the DeviceIoControl call and the read succeeded. + result = result && !(reg.reg_Flags & CARRY_FLAG); + + LOGI(" +++ RB21h %ld %d (result=%d lastError=%ld)", + startBlock, blockCount, result, GetLastError()); + if (!result) + return kDIErrReadFailed; +#endif + + return kDIErrNone; +} + +/* + * Write blocks to a Win9x logical volume. Pass in a handle to vwin32 and + * the logical drive number (A=1, B=2, etc). + * + * Works on Win95 OSR2 and later. Earlier versions require Int26 or Int13. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::WriteBlocksInt21h(HANDLE handle, + int driveNum, long startBlock, short blockCount, const void* buf) +{ + BOOL result; + DWORD cb; + DIOC_REGISTERS reg = {0}; + DISKIO dio = {0}; + + dio.dwStartSector = startBlock; + dio.wSectors = (WORD) blockCount; + dio.dwBuffer = (DWORD) buf; + + reg.reg_EAX = 0x7305; // Ext_ABSDiskReadWrite + reg.reg_EBX = (DWORD)&dio; + reg.reg_ECX = -1; + reg.reg_EDX = driveNum; // Int 21h, fn 7305h drive numbers are 1-based + reg.reg_ESI = 0x6001; // write normal data (bit flags) + + //LOGI(" Int21/7305h write start=%d count=%d", + // startBlock, blockCount); + result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_DRIVEINFO, + ®, sizeof(reg), + ®, sizeof(reg), &cb, 0); + + // Determine if the DeviceIoControl call and the read succeeded. + result = result && !(reg.reg_Flags & CARRY_FLAG); + + LOGI(" +++ WB21h %ld %d (result=%d lastError=%ld)", + startBlock, blockCount, result, GetLastError()); + if (!result) + return kDIErrWriteFailed; + + return kDIErrNone; +} + +/* + * Read blocks from a Win2K logical or physical volume. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::ReadBlocksWin2K(HANDLE handle, + long startBlock, short blockCount, void* buf) +{ + /* + * Try to read the blocks. Under Win2K the seek and read calls appear + * to succeed, but the value in "actual" is set to zero to indicate + * that we're trying to read past EOF. + */ + DWORD posn, actual; + + /* + * Win2K: the 3rd argument holds the high 32 bits of the distance to + * move. This isn't supported in Win9x, which means Win9x is limited + * to 2GB files. + */ + LARGE_INTEGER li; + li.QuadPart = (LONGLONG) startBlock * (LONGLONG) kBlockSize; + posn = ::SetFilePointer(handle, li.LowPart, &li.HighPart, + FILE_BEGIN); + if (posn == INVALID_SET_FILE_POINTER) { + DWORD lerr = GetLastError(); + LOGI(" GFDWinVolume ReadBlock: SFP failed (err=%ld)", lerr); + return LastErrorToDIError(); + } + + //LOGI(" ReadFile block start=%d count=%d", startBlock, blockCount); + + BOOL result; + result = ::ReadFile(handle, buf, blockCount * kBlockSize, &actual, NULL); + if (!result) { + DWORD lerr = GetLastError(); + LOGI(" ReadBlocksWin2K: ReadFile failed (start=%ld count=%d err=%ld)", + startBlock, blockCount, lerr); + return LastErrorToDIError(); + } + if ((long) actual != blockCount * kBlockSize) + return kDIErrEOF; + + return kDIErrNone; +} + +/* + * Write blocks to a Win2K logical or physical volume. + */ +/*static*/ DIError Win32VolumeAccess::BlockAccess::WriteBlocksWin2K(HANDLE handle, + long startBlock, short blockCount, const void* buf) +{ + DWORD posn, actual; + + posn = ::SetFilePointer(handle, startBlock * kBlockSize, NULL, + FILE_BEGIN); + if (posn == INVALID_SET_FILE_POINTER) { + DWORD lerr = GetLastError(); + LOGI(" GFDWinVolume ReadBlocks: SFP %ld failed (err=%ld)", + startBlock * kBlockSize, lerr); + return LastErrorToDIError(); + } + + BOOL result; + result = ::WriteFile(handle, buf, blockCount * kBlockSize, &actual, NULL); + if (!result) { + DWORD lerr = GetLastError(); + LOGI(" GFDWinVolume WriteBlocks: WriteFile failed (err=%ld)", + lerr); + return LastErrorToDIError(); + } + if ((long) actual != blockCount * kBlockSize) + return kDIErrEOF; // unexpected on a write call? + + return kDIErrNone; +} + + +/* + * =========================================================================== + * LogicalBlockAccess + * =========================================================================== + */ + +/* + * Open a logical device. The device name should be of the form "A:\". + */ +DIError Win32VolumeAccess::LogicalBlockAccess::Open(const WCHAR* deviceName, + bool readOnly) +{ + DIError dierr = kDIErrNone; + const bool kPreferASPI = true; + + assert(fHandle == NULL); + fIsCDROM = false; + fDriveNum = -1; + + if (deviceName[0] < 'A' || deviceName[0] > 'Z' || + deviceName[1] != ':' || deviceName[2] != '\\' || + deviceName[3] != '\0') + { + LOGI(" LogicalBlockAccess: invalid device name '%s'", deviceName); + assert(false); + dierr = kDIErrInvalidArg; + goto bail; + } + if (deviceName[0] == 'C') { + if (readOnly == false) { + LOGI(" REFUSING WRITE ACCESS TO C:\\ "); + return kDIErrVWAccessForbidden; + } + } + + DWORD access; + if (readOnly) + access = GENERIC_READ; + else + access = GENERIC_READ | GENERIC_WRITE; + + UINT driveType; + driveType = GetDriveType(deviceName); + if (driveType == DRIVE_CDROM) { + if (!Global::GetHasSPTI() && !Global::GetHasASPI()) + return kDIErrCDROMNotSupported; + + fIsCDROM = true; + // SPTI needs this -- maybe to enforce exclusive access? + access |= GENERIC_WRITE; + } + + if (fIsWin9x) { + if (fIsCDROM) + return kDIErrCDROMNotSupported; + + fHandle = CreateFile(_T("\\\\.\\vwin32"), 0, 0, NULL, + OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (fHandle == INVALID_HANDLE_VALUE) { + DWORD lastError = GetLastError(); + LOGI(" Win32LVOpen: CreateFile(vwin32) failed (err=%ld)", + lastError); + dierr = LastErrorToDIError(); + goto bail; + } + fDriveNum = deviceName[0] - 'A' +1; + assert(fDriveNum > 0 && fDriveNum <= kNumLogicalVolumes); + +#if 0 + int int13Unit; + dierr = GetInt13Unit(fHandle, fDriveNum, &int13Unit); + if (dierr != kDIErrNone) + goto bail; + if (int13Unit < 4) { + fFloppyUnit = int13Unit; + LOGI(" Logical volume #%d looks like floppy unit %d", + fDriveNum, fFloppyUnit); + } +#endif + } else { + WCHAR device[7] = _T("\\\\.\\_:"); + device[4] = deviceName[0]; + LOGI("Opening '%s'", device); + + // If we're reading, allow others to write. If we're writing, insist + // upon exclusive access to the volume. + DWORD shareMode = FILE_SHARE_READ; + if (access == GENERIC_READ) + shareMode |= FILE_SHARE_WRITE; + + fHandle = CreateFile(device, access, shareMode, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fHandle == INVALID_HANDLE_VALUE) { + DWORD lastError = GetLastError(); + dierr = LastErrorToDIError(); + if (lastError == ERROR_INVALID_PARAMETER && shareMode == FILE_SHARE_READ) + { + // Win2K spits this back if the volume is open and we're + // not specifying FILE_SHARE_WRITE. Give it a try, just to + // see if it works, so we can tell the difference between + // an internal library error and an active filesystem. + HANDLE tmpHandle; + tmpHandle = CreateFile(device, access, FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpHandle != INVALID_HANDLE_VALUE) { + dierr = kDIErrNoExclusiveAccess; + ::CloseHandle(tmpHandle); + } + } + LOGI(" LBAccess Open: CreateFile failed (err=%ld dierr=%d)", + lastError, dierr); + goto bail; + } + } + + assert(fHandle != NULL && fHandle != INVALID_HANDLE_VALUE); + +#if 0 + if (fIsCDROM) { + assert(Global::GetHasSPTI() || Global::GetHasASPI()); + if (Global::GetHasASPI() && (!Global::GetHasSPTI() || kPreferASPI)) { + LOGI(" LBAccess using ASPI"); + fCDBaggage.fUseASPI = true; + } else { + LOGI(" LBAccess using SPTI"); + fCDBaggage.fUseASPI = false; + } + + if (fCDBaggage.fUseASPI) { + dierr = FindASPIDriveMapping(deviceName[0]); + if (dierr != kDIErrNone) { + LOGI("ERROR: couldn't find ASPI drive mapping"); + dierr = kDIErrNoASPIMapping; + goto bail; + } + } + } +#endif + +bail: + if (dierr != kDIErrNone) { + if (fHandle != NULL && fHandle != INVALID_HANDLE_VALUE) + ::CloseHandle(fHandle); + fHandle = NULL; + } + return dierr; +} + +/* + * Close the device handle. + */ +DIError Win32VolumeAccess::LogicalBlockAccess::Close(void) +{ + if (fHandle != NULL) { + ::CloseHandle(fHandle); + fHandle = NULL; + } + return kDIErrNone; +} + +/* + * Read 512-byte blocks from CD-ROM media using SPTI calls. + */ +DIError Win32VolumeAccess::LogicalBlockAccess::ReadBlocksCDROM(HANDLE handle, + long startBlock, short blockCount, void* buf) +{ +#ifdef HAVE_WINDOWS_CDROM + DIError dierr; + + assert(handle != NULL); + assert(startBlock >= 0); + assert(blockCount > 0); + assert(buf != NULL); + + //LOGI(" CDROM read block %ld (%ld)", startBlock, block); + + /* alloc sector buffer on first use */ + if (fLastSectorCache == NULL) { + fLastSectorCache = new uint8_t[kCDROMSectorSize]; + if (fLastSectorCache == NULL) + return kDIErrMalloc; + assert(fLastSectorNum == -1); + } + + /* + * Map a range of 512-byte blocks to a range of 2048-byte blocks. + */ + assert(kCDROMSectorSize % kBlockSize == 0); + const int kFactor = kCDROMSectorSize / kBlockSize; + long sectorIndex = startBlock / kFactor; + int sectorOffset = (int) (startBlock % kFactor); // 0-3 + + /* + * When possible, do multi-block reads directly into "buf". The first + * and last block may require special handling. + */ + while (blockCount) { + assert(blockCount > 0); + + if (sectorOffset != 0 || blockCount < kFactor) { + assert(sectorOffset >= 0 && sectorOffset < kFactor); + + /* get from single-block cache or from disc */ + if (sectorIndex != fLastSectorNum) { + fLastSectorNum = -1; // invalidate, in case of error + + dierr = SPTI::ReadBlocks(handle, sectorIndex, 1, + kCDROMSectorSize, fLastSectorCache); + if (dierr != kDIErrNone) + return dierr; + + fLastSectorNum = sectorIndex; + } + + int thisNumBlocks; + thisNumBlocks = kFactor - sectorOffset; + if (thisNumBlocks > blockCount) + thisNumBlocks = blockCount; + + //LOGI(" Small copy (sectIdx=%ld off=%d*512 size=%d*512)", + // sectorIndex, sectorOffset, thisNumBlocks); + memcpy(buf, fLastSectorCache + sectorOffset * kBlockSize, + thisNumBlocks * kBlockSize); + + blockCount -= thisNumBlocks; + buf = (uint8_t*) buf + (thisNumBlocks * kBlockSize); + + sectorOffset = 0; + sectorIndex++; + } else { + fLastSectorNum = -1; // invalidate single-block cache + + int numSectors; + numSectors = blockCount / kFactor; // rounds down + + //LOGI(" Big read (sectIdx=%ld numSect=%d)", + // sectorIndex, numSectors); + dierr = SPTI::ReadBlocks(handle, sectorIndex, numSectors, + kCDROMSectorSize, buf); + if (dierr != kDIErrNone) + return dierr; + + blockCount -= numSectors * kFactor; + buf = (uint8_t*) buf + (numSectors * kCDROMSectorSize); + + sectorIndex += numSectors; + } + } + + return kDIErrNone; + +#else + return kDIErrCDROMNotSupported; +#endif +} + + +/* + * =========================================================================== + * PhysicalBlockAccess + * =========================================================================== + */ + +/* + * Open a physical device. The device name should be of the form "80:\". + */ +DIError Win32VolumeAccess::PhysicalBlockAccess::Open(const WCHAR* deviceName, + bool readOnly) +{ + DIError dierr = kDIErrNone; + + // initialize all local state + assert(fHandle == NULL); + fInt13Unit = -1; + fFloppyKind = kFloppyUnknown; + memset(&fGeometry, 0, sizeof(fGeometry)); + + // sanity-check name; not this only works for first 10 devices + if (deviceName[0] < '0' || deviceName[0] > '9' || + deviceName[1] < '0' || deviceName[1] > '9' || + deviceName[2] != ':' || deviceName[3] != '\\' || + deviceName[4] != '\0') + { + LOGI(" PhysicalBlockAccess: invalid device name '%s'", deviceName); + assert(false); + dierr = kDIErrInvalidArg; + goto bail; + } + + if (deviceName[0] == '8' && deviceName[1] == '0') { + if (!gAllowWritePhys0 && readOnly == false) { + LOGI(" REFUSING WRITE ACCESS TO 80:\\ "); + return kDIErrVWAccessForbidden; + } + } + + fInt13Unit = (deviceName[0] - '0') * 16 + deviceName[1] - '0'; + if (!fIsWin9x && fInt13Unit < 0x80) { + LOGI("GLITCH: can't open floppy as physical unit in Win2K"); + dierr = kDIErrInvalidArg; + goto bail; + } + if (fIsWin9x && fInt13Unit >= 0x80) { + LOGI("GLITCH: can't access physical HD in Win9x"); + dierr = kDIErrInvalidArg; + goto bail; + } + if ((fInt13Unit >= 0x00 && fInt13Unit < 0x04) || + (fInt13Unit >= 0x80 && fInt13Unit < 0x88)) + { + LOGI(" Win32VA/P: opening unit %02xh", fInt13Unit); + } else { + LOGI("GLITCH: converted '%s' to %02xh", deviceName, fInt13Unit); + dierr = kDIErrInternal; + goto bail; + } + + DWORD access; + if (readOnly) + access = GENERIC_READ; + else + access = GENERIC_READ | GENERIC_WRITE; + + if (fIsWin9x) { + fHandle = CreateFile(_T("\\\\.\\vwin32"), 0, 0, NULL, + OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (fHandle == INVALID_HANDLE_VALUE) { + DWORD lastError = GetLastError(); + LOGI(" Win32VA/PBOpen: CreateFile(vwin32) failed (err=%ld)", + lastError); + dierr = LastErrorToDIError(); + goto bail; + } + + /* figure out the geometry */ + dierr = DetectFloppyGeometry(); + if (dierr != kDIErrNone) + goto bail; + } else { + WCHAR device[19] = _T("\\\\.\\PhysicalDrive_"); + assert(fInt13Unit >= 0x80 && fInt13Unit <= 0x89); + device[17] = fInt13Unit - 0x80 + '0'; + LOGI("Opening '%s' (access=0x%02x)", device, access); + + // If we're reading, allow others to write. If we're writing, insist + // upon exclusive access to the volume. + DWORD shareMode = FILE_SHARE_READ; + if (access == GENERIC_READ) + shareMode |= FILE_SHARE_WRITE; + + fHandle = CreateFile(device, access, shareMode, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fHandle == INVALID_HANDLE_VALUE) { + DWORD lastError = GetLastError(); + dierr = LastErrorToDIError(); + LOGI(" PBAccess Open: CreateFile failed (err=%ld dierr=%d)", + lastError, dierr); + goto bail; + } + } + + assert(fHandle != NULL && fHandle != INVALID_HANDLE_VALUE); + +bail: + if (dierr != kDIErrNone) { + if (fHandle != NULL && fHandle != INVALID_HANDLE_VALUE) + ::CloseHandle(fHandle); + fHandle = NULL; + } + + return dierr; +} + +/* + * Auto-detect the geometry of a floppy drive. + * + * Sets "fFloppyKind" and "fGeometry". + */ +DIError Win32VolumeAccess::PhysicalBlockAccess::DetectFloppyGeometry(void) +{ + DIError dierr = kDIErrNone; + static const struct { + FloppyKind kind; + DiskGeometry geom; + } floppyGeometry[] = { + { kFloppyUnknown, { -1, -1, -1, -1 } }, + { kFloppy525_360, { 40, 2, 9, 360*2 } }, + { kFloppy525_1200, { 80, 2, 15, 1200*2 } }, + { kFloppy35_720, { 80, 2, 9, 720*2 } }, + { kFloppy35_1440, { 80, 2, 18, 1440*2 } }, + { kFloppy35_2880, { 80, 2, 36, 2880*2 } } + }; + uint8_t buf[kBlockSize]; + FloppyKind driveKind; + int status; + + /* verify that we can directly index the table with the enum */ + int chk; + for (chk = 0; chk < NELEM(floppyGeometry); chk++) { + assert(floppyGeometry[chk].kind == chk); + } + assert(chk == kFloppyMax); + + + /* + * Issue a BIOS call to determine the kind of drive we're looking at. + */ + dierr = GetFloppyDriveKind(fHandle, fInt13Unit, &driveKind); + if (dierr != kDIErrNone) + goto bail; + + switch (driveKind) { + case kFloppy35_2880: + status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 36, 1, buf); + if (status == 0) { + fFloppyKind = kFloppy35_2880; + break; + } + // else, fall through + case kFloppy35_1440: + status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 18, 1, buf); + if (status == 0) { + fFloppyKind = kFloppy35_1440; + break; + } + // else, fall through + case kFloppy35_720: + status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 9, 1, buf); + if (status == 0) { + fFloppyKind = kFloppy35_720; + break; + } + // else, fail + dierr = kDIErrReadFailed; + goto bail; + + case kFloppy525_1200: + status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 15, 1, buf); + if (status == 0) { + fFloppyKind = kFloppy525_1200; + break; + } + // else, fall through + case kFloppy525_360: + status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 9, 1, buf); + if (status == 0) { + fFloppyKind = kFloppy525_360; + break; + } + // else, fail + dierr = kDIErrReadFailed; + goto bail; + + default: + LOGI(" Unknown driveKind %d", driveKind); + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + LOGI("PBA: floppy disk appears to be type=%d", fFloppyKind); + fGeometry = floppyGeometry[fFloppyKind].geom; + +bail: + return dierr; +} + +#if 0 +/* + * Flush the system disk cache. + */ +DIError Win32VolumeAccess::PhysicalBlockAccess::FlushBlockDevice(void) +{ + DIError dierr = kDIErrNone; + + if (::FlushFileBuffers(fHandle) == FALSE) { + DWORD lastError = GetLastError(); + LOGI(" Win32VA/PBAFlush: FlushFileBuffers failed (err=%ld)", + lastError); + dierr = LastErrorToDIError(); + } + return dierr; +} +#endif + +/* + * Close the device handle. + */ +DIError Win32VolumeAccess::PhysicalBlockAccess::Close(void) +{ + if (fHandle != NULL) { + ::CloseHandle(fHandle); + fHandle = NULL; + } + return kDIErrNone; +} + + +/* + * =========================================================================== + * ASPIBlockAccess + * =========================================================================== + */ +#ifdef WANT_ASPI + +/* + * Unpack device name and verify that the device is a CD-ROM drive or + * direct-access storage device. + */ +DIError Win32VolumeAccess::ASPIBlockAccess::Open(const char* deviceName, + bool readOnly) +{ + DIError dierr = kDIErrNone; + + if (fpASPI != NULL) + return kDIErrAlreadyOpen; + + fpASPI = Global::GetASPI(); + if (fpASPI == NULL) + return kDIErrASPIFailure; + + if (strncmp(deviceName, kASPIDev, strlen(kASPIDev)) != 0) { + assert(false); + dierr = kDIErrInternal; + goto bail; + } + + const char* cp; + int adapter, target, lun; + int result; + + cp = deviceName + strlen(kASPIDev); + result = 0; + result |= ExtractInt(&cp, &adapter); + result |= ExtractInt(&cp, &target); + result |= ExtractInt(&cp, &lun); + if (result != 0) { + LOGI(" Win32VA couldn't parse '%s'", deviceName); + dierr = kDIErrInternal; + goto bail; + } + + fAdapter = adapter; + fTarget = target; + fLun = lun; + + uint8_t deviceType; + dierr = fpASPI->GetDeviceType(fAdapter, fTarget, fLun, &deviceType); + if (dierr != kDIErrNone || + (deviceType != kScsiDevTypeCDROM && deviceType != kScsiDevTypeDASD)) + { + LOGI(" Win32VA bad GetDeviceType err=%d type=%d", + dierr, deviceType); + dierr = kDIErrInternal; // should not be here at all + goto bail; + } + if (deviceType == kScsiDevTypeCDROM) + fReadOnly = true; + else + fReadOnly = readOnly; + + LOGI(" Win32VA successful 'open' of '%s' on %d:%d:%d", + deviceName, fAdapter, fTarget, fLun); + +bail: + if (dierr != kDIErrNone) + fpASPI = NULL; + return dierr; +} + +/* + * Extract an integer from a string, advancing to the next integer after + * doing so. + * + * Returns 0 on success, -1 on failure. + */ +int Win32VolumeAccess::ASPIBlockAccess::ExtractInt(const char** pStr, int* pResult) +{ + char* end = NULL; + + if (*pStr == NULL) { + assert(false); + return -1; + } + + *pResult = (int) strtol(*pStr, &end, 10); + + if (end == NULL) + *pStr = NULL; + else + *pStr = end+1; + + return 0; +} + +/* + * Return the device capacity in 512-byte blocks. + * + * Sets fChunkSize as a side-effect. + */ +DIError Win32VolumeAccess::ASPIBlockAccess::DetectCapacity(long* pNumBlocks) +{ + DIError dierr = kDIErrNone; + unsigned long lba, blockLen; + + dierr = fpASPI->GetDeviceCapacity(fAdapter, fTarget, fLun, &lba, + &blockLen); + if (dierr != kDIErrNone) + goto bail; + + LOGI("READ CAPACITY reports lba=%lu blockLen=%lu (total=%lu)", + lba, blockLen, lba*blockLen); + + fChunkSize = blockLen; + + if ((blockLen % 512) != 0) { + LOGI("Unacceptable CD-ROM blockLen=%ld, bailing", blockLen); + dierr = kDIErrReadFailed; + goto bail; + } + + // The LBA is the last valid block on the disk. To get the disk size, + // we need to add one. + + *pNumBlocks = (blockLen/512) * (lba+1); + LOGI(" ASPI returning 512-byte block count %ld", *pNumBlocks); + +bail: + return dierr; +} + +/* + * Read one or more 512-byte blocks from the device. + * + * SCSI doesn't promise it'll be in a chunk size we like, but it's pretty + * safe to assume that it'll be at least 512 bytes, and divisible by 512. + */ +DIError Win32VolumeAccess::ASPIBlockAccess::ReadBlocks(long startBlock, + short blockCount, void* buf) +{ + DIError dierr; + + // we're expecting fBlockSize to be 512 or 2048 + assert(fChunkSize >= kBlockSize && fChunkSize <= 65536); + assert((fChunkSize % kBlockSize) == 0); + + /* alloc chunk buffer on first use */ + if (fLastChunkCache == NULL) { + fLastChunkCache = new uint8_t[fChunkSize]; + if (fLastChunkCache == NULL) + return kDIErrMalloc; + assert(fLastChunkNum == -1); + } + + /* + * Map a range of N-byte blocks to a range of 2048-byte blocks. + */ + const int kFactor = fChunkSize / kBlockSize; + long chunkIndex = startBlock / kFactor; + int chunkOffset = (int) (startBlock % kFactor); // small integer + + /* + * When possible, do multi-block reads directly into "buf". The first + * and last block may require special handling. + */ + while (blockCount) { + assert(blockCount > 0); + + if (chunkOffset != 0 || blockCount < kFactor) { + assert(chunkOffset >= 0 && chunkOffset < kFactor); + + /* get from single-block cache or from disc */ + if (chunkIndex != fLastChunkNum) { + fLastChunkNum = -1; // invalidate, in case of error + + dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex, + 1, fChunkSize, fLastChunkCache); + if (dierr != kDIErrNone) + return dierr; + + fLastChunkNum = chunkIndex; + } + + int thisNumBlocks; + thisNumBlocks = kFactor - chunkOffset; + if (thisNumBlocks > blockCount) + thisNumBlocks = blockCount; + + //LOGI(" Small copy (chIdx=%ld off=%d*512 size=%d*512)", + // chunkIndex, chunkOffset, thisNumBlocks); + memcpy(buf, fLastChunkCache + chunkOffset * kBlockSize, + thisNumBlocks * kBlockSize); + + blockCount -= thisNumBlocks; + buf = (uint8_t*) buf + (thisNumBlocks * kBlockSize); + + chunkOffset = 0; + chunkIndex++; + } else { + fLastChunkNum = -1; // invalidate single-block cache + + int numChunks; + numChunks = blockCount / kFactor; // rounds down + + //LOGI(" Big read (chIdx=%ld numCh=%d)", + // chunkIndex, numChunks); + dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex, + numChunks, fChunkSize, buf); + if (dierr != kDIErrNone) + return dierr; + + blockCount -= numChunks * kFactor; + buf = (uint8_t*) buf + (numChunks * fChunkSize); + + chunkIndex += numChunks; + } + } + + return kDIErrNone; +} + +/* + * Write one or more 512-byte blocks to the device. + * + * SCSI doesn't promise it'll be in a chunk size we like, but it's pretty + * safe to assume that it'll be at least 512 bytes, and divisible by 512. + */ +DIError Win32VolumeAccess::ASPIBlockAccess::WriteBlocks(long startBlock, + short blockCount, const void* buf) +{ + DIError dierr; + + if (fReadOnly) + return kDIErrAccessDenied; + + // we're expecting fBlockSize to be 512 or 2048 + assert(fChunkSize >= kBlockSize && fChunkSize <= 65536); + assert((fChunkSize % kBlockSize) == 0); + + /* throw out the cache */ + fLastChunkNum = -1; + + /* + * Map a range of N-byte blocks to a range of 2048-byte blocks. + */ + const int kFactor = fChunkSize / kBlockSize; + long chunkIndex = startBlock / kFactor; + int chunkOffset = (int) (startBlock % kFactor); // small integer + + /* + * When possible, do multi-block writes directly from "buf". The first + * and last block may require special handling. + */ + while (blockCount) { + assert(blockCount > 0); + + if (chunkOffset != 0 || blockCount < kFactor) { + assert(chunkOffset >= 0 && chunkOffset < kFactor); + + /* read the chunk we're writing a part of */ + dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex, + 1, fChunkSize, fLastChunkCache); + if (dierr != kDIErrNone) + return dierr; + + int thisNumBlocks; + thisNumBlocks = kFactor - chunkOffset; + if (thisNumBlocks > blockCount) + thisNumBlocks = blockCount; + + LOGI(" Small copy out (chIdx=%ld off=%d*512 size=%d*512)", + chunkIndex, chunkOffset, thisNumBlocks); + memcpy(fLastChunkCache + chunkOffset * kBlockSize, buf, + thisNumBlocks * kBlockSize); + + blockCount -= thisNumBlocks; + buf = (const uint8_t*) buf + (thisNumBlocks * kBlockSize); + + chunkOffset = 0; + chunkIndex++; + } else { + int numChunks; + numChunks = blockCount / kFactor; // rounds down + + LOGI(" Big write (chIdx=%ld numCh=%d)", + chunkIndex, numChunks); + dierr = fpASPI->WriteBlocks(fAdapter, fTarget, fLun, chunkIndex, + numChunks, fChunkSize, buf); + if (dierr != kDIErrNone) + return dierr; + + blockCount -= numChunks * kFactor; + buf = (const uint8_t*) buf + (numChunks * fChunkSize); + + chunkIndex += numChunks; + } + } + + return kDIErrNone; +} + +/* + * Not much to do, really, since we're not holding onto any OS structures. + */ +DIError +Win32VolumeAccess::ASPIBlockAccess::Close(void) +{ + fpASPI = NULL; + return kDIErrNone; +} +#endif + + +/* + * =========================================================================== + * CBCache + * =========================================================================== + */ + +/* + * Determine whether we're holding a block in the cache. + */ +bool CBCache::IsBlockInCache(long blockNum) const +{ + if (fFirstBlock == kEmpty) + return false; + assert(fNumBlocks > 0); + + if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks) + return true; + else + return false; +} + +/* + * Retrieve a single block from the cache. + */ +DIError CBCache::GetFromCache(long blockNum, void* buf) +{ + if (!IsBlockInCache(blockNum)) { + assert(false); + return kDIErrInternal; + } + + //LOGI(" CBCache: getting block %d from cache", blockNum); + int offset = (blockNum - fFirstBlock) * kBlockSize; + assert(offset >= 0); + + memcpy(buf, fCache + offset, kBlockSize); + return kDIErrNone; +} + +/* + * Determine whether a block will "fit" in the cache. There are two + * criteria: (1) there must actually be room at the end, and (2) the + * block in question must be the next consecutive block. + */ +bool CBCache::IsRoomInCache(long blockNum) const +{ + if (fFirstBlock == kEmpty) + return true; + + // already in cache? + if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks) + return true; + + // running off the end? + if (fNumBlocks == kMaxCachedBlocks) + return false; + + // is it the exact next one? + if (fFirstBlock + fNumBlocks != blockNum) + return false; + + return true; +} + +/* + * Add a block to the cache. + * + * We might be adding it after a read or a write. The "isDirty" flag + * tells us what the deal is. If somebody tries to overwrite a dirty + * block with a new one and doesn't have "isDirty" set, it probably means + * they're trying to overwrite dirty cached data with the result of a new + * read, which is a bug. Trap it here. + */ +DIError CBCache::PutInCache(long blockNum, const void* buf, bool isDirty) +{ + int blockOffset = -1; + if (!IsRoomInCache(blockNum)) { + assert(false); + return kDIErrInternal; + } + + if (fFirstBlock == kEmpty) { + //LOGI(" CBCache: starting anew with block %ld", blockNum); + fFirstBlock = blockNum; + fNumBlocks = 1; + blockOffset = 0; + } else if (blockNum == fFirstBlock + fNumBlocks) { + //LOGI(" CBCache: appending block %ld", blockNum); + blockOffset = fNumBlocks; + fNumBlocks++; + } else if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks) { + blockOffset = blockNum - fFirstBlock; + } else { + assert(false); + return kDIErrInternal; + } + assert(blockOffset != -1); + assert(blockOffset < kMaxCachedBlocks); + + if (fDirty[blockOffset] && !isDirty) { + LOGI("BUG: CBCache trying to clear dirty flag for block %ld", + blockNum); + assert(false); + return kDIErrInternal; + } + fDirty[blockOffset] = isDirty; + + //LOGI(" CBCache: adding block %d to cache at %d", blockNum, blockOffset); + int offset = blockOffset * kBlockSize; + assert(offset >= 0); + + memcpy(fCache + offset, buf, kBlockSize); + return kDIErrNone; +} + +/* + * Determine whether there are any dirty blocks in the cache. + */ +bool CBCache::IsDirty(void) const +{ + if (fFirstBlock == kEmpty) + return false; + + assert(fNumBlocks > 0); + for (int i = 0; i < fNumBlocks; i++) { + if (fDirty[i]) { + //LOGI(" CBCache: dirty blocks found"); + return true; + } + } + + //LOGI(" CBCache: no dirty blocks found"); + return false; +} + +/* + * Return a pointer to the cache goodies, so that the object sitting + * on the disk hardware can write our stuff. + */ +void CBCache::GetCachePointer(long* pFirstBlock, int* pNumBlocks, void** pBuf) const +{ + assert(fFirstBlock != kEmpty); // not essential, but why call here if not? + + *pFirstBlock = fFirstBlock; + *pNumBlocks = fNumBlocks; + *pBuf = (void*) fCache; +} + +/* + * Clear all the dirty flags. + */ +void CBCache::Scrub(void) +{ + if (fFirstBlock == kEmpty) + return; + + for (int i = 0; i < fNumBlocks; i++) + fDirty[i] = false; +} + +/* + * Trash all of our entries. If any are dirty, scream bloody murder. + */ +void CBCache::Purge(void) +{ + if (fFirstBlock == kEmpty) + return; + + if (IsDirty()) { + // Should only happen after a write failure causes us to clean up. + LOGE("HEY: CBCache purging dirty blocks!"); + //assert(false); + } + Scrub(); + + fFirstBlock = kEmpty; + fNumBlocks = 0; +} + +#endif /*_WIN32*/ diff --git a/diskimg/Win32BlockIO.h b/diskimg/Win32BlockIO.h new file mode 100644 index 0000000..7d5d3de --- /dev/null +++ b/diskimg/Win32BlockIO.h @@ -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*/ diff --git a/diskimg/Win32Extra.h b/diskimg/Win32Extra.h new file mode 100644 index 0000000..fa77ea3 --- /dev/null +++ b/diskimg/Win32Extra.h @@ -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 // 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*/ diff --git a/diskimg/diskimg.vcxproj b/diskimg/diskimg.vcxproj new file mode 100644 index 0000000..8196efb --- /dev/null +++ b/diskimg/diskimg.vcxproj @@ -0,0 +1,216 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + {0CFE6FAD-0126-4E99-8625-C807D1D2AAF4} + + + + DynamicLibrary + v143 + Unicode + Dynamic + + + DynamicLibrary + v143 + false + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + diskimg5 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + diskimg5 + + + + MaxSpeed + OnlyExplicitInline + WIN32;NDEBUGX;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + Use + stdafx.h + $(IntDir)$(TargetName).pch + $(IntDir) + $(IntDir) + $(IntDir)vc$(PlatformToolsetVersion).pdb + Level3 + true + true + + + %(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + false + $(OutDir)$(TargetName).pdb + $(OutDir)$(TargetName).lib + MachineX86 + + Windows + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/diskimg.tlb + + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Use + stdafx.h + $(IntDir)$(TargetName).pch + $(IntDir) + $(IntDir) + $(IntDir)vc$(PlatformToolsetVersion).pdb + Level3 + true + EditAndContinue + true + + + $(OutDir)$(TargetName)$(TargetExt) + false + true + $(OutDir)$(TargetName).pdb + $(OutDir)$(TargetName).lib + MachineX86 + + Windows + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/diskimg.tlb + + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + + + + + + + + + + + + + + + {c48ae53b-3dcb-43b1-9207-b7c5b6bb78af} + + + {b66109f4-217b-43c0-86aa-eb55657e5ac0} + + + {0fa742e9-8c07-43dd-aff8-ce31faf70821} + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + \ No newline at end of file diff --git a/diskimg/diskimg.vcxproj.filters b/diskimg/diskimg.vcxproj.filters new file mode 100644 index 0000000..18d2b7f --- /dev/null +++ b/diskimg/diskimg.vcxproj.filters @@ -0,0 +1,156 @@ + + + + + {b6ac9831-b535-4474-a308-d3bf6fdf4488} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {def4def1-c9b0-48b3-a80b-c137f7ebf9bd} + h;hpp;hxx;hm;inl + + + {ed4624b4-6280-4672-8e7f-e8aac6e178ef} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/diskimg/diskimg.vcxproj.user b/diskimg/diskimg.vcxproj.user new file mode 100644 index 0000000..ef5ff2a --- /dev/null +++ b/diskimg/diskimg.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/libhfs/CMakeLists.txt b/libhfs/CMakeLists.txt new file mode 100644 index 0000000..9d21b17 --- /dev/null +++ b/libhfs/CMakeLists.txt @@ -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} +) + + + diff --git a/libhfs/COPYRIGHT b/libhfs/COPYRIGHT new file mode 100644 index 0000000..0960cef --- /dev/null +++ b/libhfs/COPYRIGHT @@ -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 + diff --git a/libhfs/Makefile b/libhfs/Makefile new file mode 100644 index 0000000..889981d --- /dev/null +++ b/libhfs/Makefile @@ -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. + diff --git a/libhfs/README b/libhfs/README new file mode 100644 index 0000000..5f7dec5 --- /dev/null +++ b/libhfs/README @@ -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. diff --git a/libhfs/apple.h b/libhfs/apple.h new file mode 100644 index 0000000..d860874 --- /dev/null +++ b/libhfs/apple.h @@ -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; diff --git a/libhfs/block.c b/libhfs/block.c new file mode 100644 index 0000000..de6317c --- /dev/null +++ b/libhfs/block.c @@ -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 +# include +# include + +# 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; +} diff --git a/libhfs/block.h b/libhfs/block.h new file mode 100644 index 0000000..a18f007 --- /dev/null +++ b/libhfs/block.h @@ -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 diff --git a/libhfs/btree.c b/libhfs/btree.c new file mode 100644 index 0000000..9d4331f --- /dev/null +++ b/libhfs/btree.c @@ -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 +# include +# include + +# 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; +} diff --git a/libhfs/btree.h b/libhfs/btree.h new file mode 100644 index 0000000..cdf9427 --- /dev/null +++ b/libhfs/btree.h @@ -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 *); diff --git a/libhfs/config.h b/libhfs/config.h new file mode 100644 index 0000000..fd76a54 --- /dev/null +++ b/libhfs/config.h @@ -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 doesn't define. */ +/* #undef size_t */ + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if your 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 header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#ifndef _WIN32 +# define HAVE_UNISTD_H 1 +#endif + +/***************************************************************************** + * End of automatically configured definitions * + *****************************************************************************/ + +# ifdef DEBUG +# include +# endif diff --git a/libhfs/data.c b/libhfs/data.c new file mode 100644 index 0000000..5cf763c --- /dev/null +++ b/libhfs/data.c @@ -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 +# include + +# ifdef TM_IN_SYS_TIME +# include +# 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; +} diff --git a/libhfs/data.h b/libhfs/data.h new file mode 100644 index 0000000..8cddf00 --- /dev/null +++ b/libhfs/data.h @@ -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); diff --git a/libhfs/file.c b/libhfs/file.c new file mode 100644 index 0000000..551a399 --- /dev/null +++ b/libhfs/file.c @@ -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 +# include + +# 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; +} diff --git a/libhfs/file.h b/libhfs/file.h new file mode 100644 index 0000000..b4a9501 --- /dev/null +++ b/libhfs/file.h @@ -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 *); diff --git a/libhfs/hfs.c b/libhfs/hfs.c new file mode 100644 index 0000000..847e0e6 --- /dev/null +++ b/libhfs/hfs.c @@ -0,0 +1,1993 @@ +/* + * 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 +# include +# include +# include +# include +# include /* debug */ + +# include "libhfs.h" +# include "data.h" +# include "block.h" +# include "medium.h" +# include "file.h" +# include "btree.h" +# include "node.h" +# include "record.h" +# include "volume.h" + +/* should be CP_NO_STATIC, but it's harmless (albeit useless with MT) */ +const char *hfs_error = "no error"; /* static error string */ + +#ifdef CP_NO_STATIC +hfsvol *hfs_mounts; /* linked list of mounted volumes */ + +static +hfsvol *curvol; /* current volume */ +#endif + +/* + * NAME: validvname() + * DESCRIPTION: return true if parameter is a valid volume name + */ +static +int validvname(const char *name) +{ + int len; + + len = strlen(name); + if (len < 1) + ERROR(EINVAL, "volume name cannot be empty"); + else if (len > HFS_MAX_VLEN) + ERROR(ENAMETOOLONG, + "volume name can be at most " STR(HFS_MAX_VLEN) " chars"); + + if (strchr(name, ':')) + ERROR(EINVAL, "volume name may not contain colons"); + + return 1; + +fail: + return 0; +} + +/* + * NAME: getvol() + * DESCRIPTION: validate a volume reference + */ +static +int getvol(hfsvol **vol) +{ +#ifdef CP_NO_STATIC + if (*vol == 0) + { + if (curvol == 0) + ERROR(EINVAL, "no volume is current"); + + *vol = curvol; + } +#else + if (*vol == 0) + ERROR(EINVAL, "no volume is current"); +#endif + + return 0; + +fail: + return -1; +} + +/* High-Level Volume Routines ============================================== */ + +#ifdef CP_NO_STATIC +/* + * NAME: hfs->mount() + * DESCRIPTION: open an HFS volume; return volume descriptor or 0 (error) + */ +hfsvol *hfs_mount(const char *path, int pnum, int mode) +{ + hfsvol *vol, *check; + + /* see if the volume is already mounted */ + + for (check = hfs_mounts; check; check = check->next) + { + if (check->pnum == pnum && v_same(check, path) == 1) + { + /* verify compatible read/write mode */ + + if (((check->flags & HFS_VOL_READONLY) && + ! (mode & HFS_MODE_RDWR)) || + (! (check->flags & HFS_VOL_READONLY) && + (mode & (HFS_MODE_RDWR | HFS_MODE_ANY)))) + { + vol = check; + goto done; + } + } + } + + vol = ALLOC(hfsvol, 1); + if (vol == 0) + ERROR(ENOMEM, 0); + + v_init(vol, mode); + + /* open the medium */ + + switch (mode & HFS_MODE_MASK) + { + case HFS_MODE_RDWR: + case HFS_MODE_ANY: + if (v_open(vol, path, HFS_MODE_RDWR) != -1) + break; + + if ((mode & HFS_MODE_MASK) == HFS_MODE_RDWR) + goto fail; + + case HFS_MODE_RDONLY: + default: + vol->flags |= HFS_VOL_READONLY; + + if (v_open(vol, path, HFS_MODE_RDONLY) == -1) + goto fail; + } + + /* mount the volume */ + + if (v_geometry(vol, pnum) == -1 || + v_mount(vol) == -1) + goto fail; + + /* add to linked list of volumes */ + + vol->prev = 0; + vol->next = hfs_mounts; + + if (hfs_mounts) + hfs_mounts->prev = vol; + + hfs_mounts = vol; + +done: + ++vol->refs; + curvol = vol; + + return vol; + +fail: + if (vol) + { + v_close(vol); + FREE(vol); + } + + return 0; +} +#endif + +/* + * NAME: hfs_callback_open() + * DESCRIPTION: open an HFS volume; return volume descriptor or 0 (error) + */ +hfsvol* hfs_callback_open(oscallback func, void* cookie, int mode) +{ + hfsvol *vol; + + vol = ALLOC(hfsvol, 1); + if (vol == 0) + ERROR(ENOMEM, 0); + + v_init(vol, mode); + + /* open the medium */ + + switch (mode & HFS_MODE_MASK) + { + case HFS_MODE_RDWR: + case HFS_MODE_ANY: + break; + + case HFS_MODE_RDONLY: + default: + vol->flags |= HFS_VOL_READONLY; + } + + /* set up vol->priv */ + v_callback_open(vol, func, cookie); + + + /* mount the volume */ + + if (v_geometry(vol, 0 /*we don't see partition map*/) == -1 || + v_mount(vol) == -1) + goto fail; + + assert(func != 0); + assert(cookie != 0); + +/*done*/ + ++vol->refs; + + return vol; + +fail: + if (vol) + { + v_close(vol); + FREE(vol); + } + + return 0; +} + +/* + * NAME: hfs->callback_close() + * DESCRIPTION: close an HFS volume + */ +int hfs_callback_close(hfsvol *vol) +{ + int result = 0; + + if (getvol(&vol) == -1) + goto fail; + + if (--vol->refs) + { + result = v_flush(vol); + goto done; + } + + /* close all open files and directories */ + + while (vol->files) + { + if (hfs_close(vol->files) == -1) + result = -1; + } + + while (vol->dirs) + { + if (hfs_closedir(vol->dirs) == -1) + result = -1; + } + + /* close medium */ + + if (v_close(vol) == -1) + result = -1; + + FREE(vol); + +done: + return result; + +fail: + return -1; +} + + +/* + * NAME: hfs->flush() + * DESCRIPTION: flush all pending changes to an HFS volume + */ +int hfs_flush(hfsvol *vol) +{ + hfsfile *file; + + if (getvol(&vol) == -1) + goto fail; + + for (file = vol->files; file; file = file->next) + { + if (f_flush(file) == -1) + goto fail; + } + + if (v_flush(vol) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +#ifdef CP_NO_STATIC +/* + * NAME: hfs->flushall() + * DESCRIPTION: flush all pending changes to all mounted HFS volumes + */ +void hfs_flushall(void) +{ + hfsvol *vol; + + for (vol = hfs_mounts; vol; vol = vol->next) + hfs_flush(vol); +} + +/* + * NAME: hfs->umount() + * DESCRIPTION: close an HFS volume + */ +int hfs_umount(hfsvol *vol) +{ + int result = 0; + + if (getvol(&vol) == -1) + goto fail; + + if (--vol->refs) + { + result = v_flush(vol); + goto done; + } + + /* close all open files and directories */ + + while (vol->files) + { + if (hfs_close(vol->files) == -1) + result = -1; + } + + while (vol->dirs) + { + if (hfs_closedir(vol->dirs) == -1) + result = -1; + } + + /* close medium */ + + if (v_close(vol) == -1) + result = -1; + + /* remove from linked list of volumes */ + + if (vol->prev) + vol->prev->next = vol->next; + if (vol->next) + vol->next->prev = vol->prev; + + if (vol == hfs_mounts) + hfs_mounts = vol->next; + if (vol == curvol) + curvol = 0; + + FREE(vol); + +done: + return result; + +fail: + return -1; +} + +/* + * NAME: hfs->umountall() + * DESCRIPTION: unmount all mounted volumes + */ +void hfs_umountall(void) +{ + while (hfs_mounts) + hfs_umount(hfs_mounts); +} + +/* + * NAME: hfs->getvol() + * DESCRIPTION: return a pointer to a mounted volume + */ +hfsvol *hfs_getvol(const char *name) +{ + hfsvol *vol; + + if (name == 0) + return curvol; + + for (vol = hfs_mounts; vol; vol = vol->next) + { + if (d_relstring(name, vol->mdb.drVN) == 0) + return vol; + } + + return 0; +} + +/* + * NAME: hfs->setvol() + * DESCRIPTION: change the current volume + */ +void hfs_setvol(hfsvol *vol) +{ + curvol = vol; +} +#endif + +/* + * NAME: hfs->vstat() + * DESCRIPTION: return volume statistics + */ +int hfs_vstat(hfsvol *vol, hfsvolent *ent) +{ + if (getvol(&vol) == -1) + goto fail; + + strcpy(ent->name, vol->mdb.drVN); + + ent->flags = (vol->flags & HFS_VOL_READONLY) ? HFS_ISLOCKED : 0; + + ent->totbytes = vol->mdb.drNmAlBlks * vol->mdb.drAlBlkSiz; + ent->freebytes = vol->mdb.drFreeBks * vol->mdb.drAlBlkSiz; + + ent->alblocksz = vol->mdb.drAlBlkSiz; + ent->clumpsz = vol->mdb.drClpSiz; + + ent->numfiles = vol->mdb.drFilCnt; + ent->numdirs = vol->mdb.drDirCnt; + + ent->crdate = d_ltime(vol->mdb.drCrDate); + ent->mddate = d_ltime(vol->mdb.drLsMod); + ent->bkdate = d_ltime(vol->mdb.drVolBkUp); + + ent->blessed = vol->mdb.drFndrInfo[0]; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->vsetattr() + * DESCRIPTION: change volume attributes + */ +int hfs_vsetattr(hfsvol *vol, hfsvolent *ent) +{ + if (getvol(&vol) == -1) + goto fail; + + if (ent->clumpsz % vol->mdb.drAlBlkSiz != 0) + ERROR(EINVAL, "illegal clump size"); + + /* make sure "blessed" folder exists */ + + if (ent->blessed && + v_getdthread(vol, ent->blessed, 0, 0) <= 0) + ERROR(EINVAL, "illegal blessed folder"); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + vol->mdb.drClpSiz = ent->clumpsz; + + vol->mdb.drCrDate = d_mtime(ent->crdate); + vol->mdb.drLsMod = d_mtime(ent->mddate); + vol->mdb.drVolBkUp = d_mtime(ent->bkdate); + + vol->mdb.drFndrInfo[0] = ent->blessed; + + vol->flags |= HFS_VOL_UPDATE_MDB; + + return 0; + +fail: + return -1; +} + +/* High-Level Directory Routines =========================================== */ + +/* + * NAME: hfs->chdir() + * DESCRIPTION: change current HFS directory + */ +int hfs_chdir(hfsvol *vol, const char *path) +{ + CatDataRec data; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, 0, 0, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + vol->cwd = data.u.dir.dirDirID; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->getcwd() + * DESCRIPTION: return the current working directory ID + */ +unsigned long hfs_getcwd(hfsvol *vol) +{ + if (getvol(&vol) == -1) + return 0; + + return vol->cwd; +} + +/* + * NAME: hfs->setcwd() + * DESCRIPTION: set the current working directory ID + */ +int hfs_setcwd(hfsvol *vol, unsigned long id) +{ + if (getvol(&vol) == -1) + goto fail; + + if (id == vol->cwd) + goto done; + + /* make sure the directory exists */ + + if (v_getdthread(vol, id, 0, 0) <= 0) + goto fail; + + vol->cwd = id; + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->dirinfo() + * DESCRIPTION: given a directory ID, return its (name and) parent ID + */ +int hfs_dirinfo(hfsvol *vol, unsigned long *id, char *name) +{ + CatDataRec thread; + + if (getvol(&vol) == -1 || + v_getdthread(vol, *id, &thread, 0) <= 0) + goto fail; + + *id = thread.u.dthd.thdParID; + + if (name) + strcpy(name, thread.u.dthd.thdCName); + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->opendir() + * DESCRIPTION: prepare to read the contents of a directory + */ +hfsdir *hfs_opendir(hfsvol *vol, const char *path) +{ + hfsdir *dir = 0; + CatKeyRec key; + CatDataRec data; + byte pkey[HFS_CATKEYLEN]; + + if (getvol(&vol) == -1) + goto fail; + + dir = ALLOC(hfsdir, 1); + if (dir == 0) + ERROR(ENOMEM, 0); + + dir->vol = vol; + + if (*path == 0) + { +#ifdef CP_NO_STATIC + /* meta-directory containing root dirs from all mounted volumes */ + + dir->dirid = 0; + dir->vptr = hfs_mounts; +#else + assert(0); +#endif + } + else + { + if (v_resolve(&vol, path, &data, 0, 0, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + dir->dirid = data.u.dir.dirDirID; + dir->vptr = 0; + + r_makecatkey(&key, dir->dirid, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_search(&vol->cat, pkey, &dir->n) <= 0) + goto fail; + } + + dir->prev = 0; + dir->next = vol->dirs; + + if (vol->dirs) + vol->dirs->prev = dir; + + vol->dirs = dir; + + return dir; + +fail: + FREE(dir); + return 0; +} + +/* + * NAME: hfs->readdir() + * DESCRIPTION: return the next entry in the directory + */ +int hfs_readdir(hfsdir *dir, hfsdirent *ent) +{ + CatKeyRec key; + CatDataRec data; + const byte *ptr; + + if (dir->dirid == 0) + { +#ifdef CP_NO_STATIC + hfsvol *vol; + char cname[HFS_MAX_FLEN + 1]; + + for (vol = hfs_mounts; vol; vol = vol->next) + { + if (vol == dir->vptr) + break; + } + + if (vol == 0) + ERROR(ENOENT, "no more entries"); + + if (v_getdthread(vol, HFS_CNID_ROOTDIR, &data, 0) <= 0 || + v_catsearch(vol, HFS_CNID_ROOTPAR, data.u.dthd.thdCName, + &data, cname, 0) <= 0) + goto fail; + + r_unpackdirent(HFS_CNID_ROOTPAR, cname, &data, ent); + + dir->vptr = vol->next; + + goto done; +#else + assert(0); +#endif + } + + if (dir->n.rnum == -1) + ERROR(ENOENT, "no more entries"); + + while (1) + { + ++dir->n.rnum; + + while (dir->n.rnum >= dir->n.nd.ndNRecs) + { + if (dir->n.nd.ndFLink == 0) + { + dir->n.rnum = -1; + ERROR(ENOENT, "no more entries"); + } + + if (bt_getnode(&dir->n, dir->n.bt, dir->n.nd.ndFLink) == -1) + { + dir->n.rnum = -1; + goto fail; + } + + dir->n.rnum = 0; + } + + ptr = HFS_NODEREC(dir->n, dir->n.rnum); + + r_unpackcatkey(ptr, &key); + + if (key.ckrParID != dir->dirid) + { + dir->n.rnum = -1; + ERROR(ENOENT, "no more entries"); + } + + r_unpackcatdata(HFS_RECDATA(ptr), &data); + + switch (data.cdrType) + { + case cdrDirRec: + case cdrFilRec: + r_unpackdirent(key.ckrParID, key.ckrCName, &data, ent); + goto done; + + case cdrThdRec: + case cdrFThdRec: + break; + + default: + dir->n.rnum = -1; + ERROR(EIO, "unexpected directory entry found"); + } + } + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->closedir() + * DESCRIPTION: stop reading a directory + */ +int hfs_closedir(hfsdir *dir) +{ + hfsvol *vol = dir->vol; + + if (dir->prev) + dir->prev->next = dir->next; + if (dir->next) + dir->next->prev = dir->prev; + if (dir == vol->dirs) + vol->dirs = dir->next; + + FREE(dir); + + return 0; +} + +/* High-Level File Routines ================================================ */ + +/* + * NAME: hfs->create() + * DESCRIPTION: create and open a new file + */ +hfsfile *hfs_create(hfsvol *vol, const char *path, + const char *type, const char *creator) +{ + hfsfile *file = 0; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + CatKeyRec key; + byte record[HFS_MAX_CATRECLEN]; + unsigned reclen; + int found; + + if (getvol(&vol) == -1) + goto fail; + + file = ALLOC(hfsfile, 1); + if (file == 0) + ERROR(ENOMEM, 0); + + found = v_resolve(&vol, path, &file->cat, &parid, name, 0); + if (found == -1 || parid == 0) + goto fail; + + if (found) + ERROR(EEXIST, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* create file `name' in parent `parid' */ + + if (bt_space(&vol->cat, 1) == -1) + goto fail; + + f_init(file, vol, vol->mdb.drNxtCNID++, name); + vol->flags |= HFS_VOL_UPDATE_MDB; + + file->parid = parid; + + /* create catalog record */ + + file->cat.u.fil.filUsrWds.fdType = + d_getsl((const unsigned char *) type); + file->cat.u.fil.filUsrWds.fdCreator = + d_getsl((const unsigned char *) creator); + + file->cat.u.fil.filCrDat = d_mtime(time(0)); + file->cat.u.fil.filMdDat = file->cat.u.fil.filCrDat; + + r_makecatkey(&key, file->parid, file->name); + r_packcatrec(&key, &file->cat, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1 || + v_adjvalence(vol, file->parid, 0, 1) == -1) + goto fail; + + /* package file handle for user */ + + file->next = vol->files; + + if (vol->files) + vol->files->prev = file; + + vol->files = file; + + return file; + +fail: + FREE(file); + return 0; +} + +/* + * NAME: hfs->open() + * DESCRIPTION: prepare a file for I/O + */ +hfsfile *hfs_open(hfsvol *vol, const char *path) +{ + hfsfile *file = 0; + + if (getvol(&vol) == -1) + goto fail; + + file = ALLOC(hfsfile, 1); + if (file == 0) + ERROR(ENOMEM, 0); + + if (v_resolve(&vol, path, &file->cat, &file->parid, file->name, 0) <= 0) + goto fail; + + if (file->cat.cdrType != cdrFilRec) + ERROR(EISDIR, 0); + + /* package file handle for user */ + + file->vol = vol; + file->flags = 0; + + f_selectfork(file, fkData); + + file->prev = 0; + file->next = vol->files; + + if (vol->files) + vol->files->prev = file; + + vol->files = file; + + return file; + +fail: + FREE(file); + return 0; +} + +/* + * NAME: hfs->setfork() + * DESCRIPTION: select file fork for I/O operations + */ +int hfs_setfork(hfsfile *file, int fork) +{ + int result = 0; + + if (f_trunc(file) == -1) + result = -1; + + f_selectfork(file, fork ? fkRsrc : fkData); + + return result; +} + +/* + * NAME: hfs->getfork() + * DESCRIPTION: return the current fork for I/O operations + */ +int hfs_getfork(hfsfile *file) +{ + return file->fork != fkData; +} + +/* + * NAME: hfs->read() + * DESCRIPTION: read from an open file + */ +unsigned long hfs_read(hfsfile *file, void *buf, unsigned long len) +{ + unsigned long *lglen, count; + byte *ptr = buf; + + f_getptrs(file, 0, &lglen, 0); + + if (file->pos + len > *lglen) + len = *lglen - file->pos; + + count = len; + while (count) + { + unsigned long bnum, offs, chunk; + + bnum = file->pos >> HFS_BLOCKSZ_BITS; + offs = file->pos & (HFS_BLOCKSZ - 1); + + chunk = HFS_BLOCKSZ - offs; + if (chunk > count) + chunk = count; + + if (offs == 0 && chunk == HFS_BLOCKSZ) + { + if (f_getblock(file, bnum, (block *) ptr) == -1) + goto fail; + } + else + { + block b; + + if (f_getblock(file, bnum, &b) == -1) + goto fail; + + memcpy(ptr, b + offs, chunk); + } + + ptr += chunk; + + file->pos += chunk; + count -= chunk; + } + + return len; + +fail: + return -1; +} + +/* + * NAME: hfs->write() + * DESCRIPTION: write to an open file + */ +unsigned long hfs_write(hfsfile *file, const void *buf, unsigned long len) +{ + unsigned long *lglen, *pylen, count; + const byte *ptr = buf; + + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + f_getptrs(file, 0, &lglen, &pylen); + + count = len; + + /* set flag to update (at least) the modification time */ + + if (count) + { + file->cat.u.fil.filMdDat = d_mtime(time(0)); + file->flags |= HFS_FILE_UPDATE_CATREC; + } + + while (count) + { + unsigned long bnum, offs, chunk; + + bnum = file->pos >> HFS_BLOCKSZ_BITS; + offs = file->pos & (HFS_BLOCKSZ - 1); + + chunk = HFS_BLOCKSZ - offs; + if (chunk > count) + chunk = count; + + if (file->pos + chunk > *pylen) + { + if (bt_space(&file->vol->ext, 1) == -1 || + f_alloc(file) == -1) + goto fail; + } + + if (offs == 0 && chunk == HFS_BLOCKSZ) + { + if (f_putblock(file, bnum, (block *) ptr) == -1) + goto fail; + } + else + { + block b; + + if (f_getblock(file, bnum, &b) == -1) + goto fail; + + memcpy(b + offs, ptr, chunk); + + if (f_putblock(file, bnum, &b) == -1) + goto fail; + } + + ptr += chunk; + + file->pos += chunk; + count -= chunk; + + if (file->pos > *lglen) + *lglen = file->pos; + } + + return len; + +fail: + return -1; +} + +/* + * NAME: hfs->truncate() + * DESCRIPTION: truncate an open file + */ +int hfs_truncate(hfsfile *file, unsigned long len) +{ + unsigned long *lglen; + + f_getptrs(file, 0, &lglen, 0); + + if (*lglen > len) + { + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + *lglen = len; + + file->cat.u.fil.filMdDat = d_mtime(time(0)); + file->flags |= HFS_FILE_UPDATE_CATREC; + + if (file->pos > len) + file->pos = len; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->seek() + * DESCRIPTION: change file seek pointer + */ +unsigned long hfs_seek(hfsfile *file, long offset, int from) +{ + unsigned long *lglen, newpos; + + f_getptrs(file, 0, &lglen, 0); + + switch (from) + { + case HFS_SEEK_SET: + newpos = (offset < 0) ? 0 : offset; + break; + + case HFS_SEEK_CUR: + if (offset < 0 && (unsigned long) -offset > file->pos) + newpos = 0; + else + newpos = file->pos + offset; + break; + + case HFS_SEEK_END: + if (offset < 0 && (unsigned long) -offset > *lglen) + newpos = 0; + else + newpos = *lglen + offset; + break; + + default: + ERROR(EINVAL, 0); + } + + if (newpos > *lglen) + newpos = *lglen; + + file->pos = newpos; + + return newpos; + +fail: + return -1; +} + +/* + * NAME: hfs->close() + * DESCRIPTION: close a file + */ +int hfs_close(hfsfile *file) +{ + hfsvol *vol = file->vol; + int result = 0; + + if (f_trunc(file) == -1 || + f_flush(file) == -1) + result = -1; + + if (file->prev) + file->prev->next = file->next; + if (file->next) + file->next->prev = file->prev; + if (file == vol->files) + vol->files = file->next; + + FREE(file); + + return result; +} + +/* High-Level Catalog Routines ============================================= */ + +/* + * NAME: hfs->stat() + * DESCRIPTION: return catalog information for an arbitrary path + */ +int hfs_stat(hfsvol *vol, const char *path, hfsdirent *ent) +{ + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, &parid, name, 0) <= 0) + goto fail; + + r_unpackdirent(parid, name, &data, ent); + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->fstat() + * DESCRIPTION: return catalog information for an open file + */ +int hfs_fstat(hfsfile *file, hfsdirent *ent) +{ + r_unpackdirent(file->parid, file->name, &file->cat, ent); + + return 0; +} + +/* + * NAME: hfs->setattr() + * DESCRIPTION: change a file's attributes + */ +int hfs_setattr(hfsvol *vol, const char *path, const hfsdirent *ent) +{ + CatDataRec data; + node n; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, 0, 0, &n) <= 0) + goto fail; + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + r_packdirent(&data, ent); + + return v_putcatrec(&data, &n); + +fail: + return -1; +} + +/* + * NAME: hfs->fsetattr() + * DESCRIPTION: change an open file's attributes + */ +int hfs_fsetattr(hfsfile *file, const hfsdirent *ent) +{ + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + r_packdirent(&file->cat, ent); + + file->flags |= HFS_FILE_UPDATE_CATREC; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->mkdir() + * DESCRIPTION: create a new directory + */ +int hfs_mkdir(hfsvol *vol, const char *path) +{ + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + int found; + + if (getvol(&vol) == -1) + goto fail; + + found = v_resolve(&vol, path, &data, &parid, name, 0); + if (found == -1 || parid == 0) + goto fail; + + if (found) + ERROR(EEXIST, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + return v_mkdir(vol, parid, name); + +fail: + return -1; +} + +/* + * NAME: hfs->rmdir() + * DESCRIPTION: delete an empty directory + */ +int hfs_rmdir(hfsvol *vol, const char *path) +{ + CatKeyRec key; + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + byte pkey[HFS_CATKEYLEN]; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, &parid, name, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + if (data.u.dir.dirVal != 0) + ERROR(ENOTEMPTY, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* delete directory record */ + + r_makecatkey(&key, parid, name); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1) + goto fail; + + /* delete thread record */ + + r_makecatkey(&key, data.u.dir.dirDirID, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1 || + v_adjvalence(vol, parid, 1, -1) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->delete() + * DESCRIPTION: remove both forks of a file + */ +int hfs_delete(hfsvol *vol, const char *path) +{ + hfsfile file; + CatKeyRec key; + byte pkey[HFS_CATKEYLEN]; + int found; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &file.cat, &file.parid, file.name, 0) <= 0) + goto fail; + + if (file.cat.cdrType != cdrFilRec) + ERROR(EISDIR, 0); + + if (file.parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* free allocation blocks */ + + file.vol = vol; + file.flags = 0; + + file.cat.u.fil.filLgLen = 0; + file.cat.u.fil.filRLgLen = 0; + + f_selectfork(&file, fkData); + if (f_trunc(&file) == -1) + goto fail; + + f_selectfork(&file, fkRsrc); + if (f_trunc(&file) == -1) + goto fail; + + /* delete file record */ + + r_makecatkey(&key, file.parid, file.name); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1 || + v_adjvalence(vol, file.parid, 0, -1) == -1) + goto fail; + + /* delete file thread, if any */ + + found = v_getfthread(vol, file.cat.u.fil.filFlNum, 0, 0); + if (found == -1) + goto fail; + + if (found) + { + r_makecatkey(&key, file.cat.u.fil.filFlNum, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->rename() + * DESCRIPTION: change the name of and/or move a file or directory + */ +int hfs_rename(hfsvol *vol, const char *srcpath, const char *dstpath) +{ + hfsvol *srcvol; + CatDataRec src, dst; + unsigned long srcid, dstid; + CatKeyRec key; + char srcname[HFS_MAX_FLEN + 1], dstname[HFS_MAX_FLEN + 1]; + byte record[HFS_MAX_CATRECLEN]; + unsigned int reclen; + int found, isdir, moving; + node n; + + if (getvol(&vol) == -1 || + v_resolve(&vol, srcpath, &src, &srcid, srcname, 0) <= 0) + goto fail; + + isdir = (src.cdrType == cdrDirRec); + srcvol = vol; + + found = v_resolve(&vol, dstpath, &dst, &dstid, dstname, 0); + if (found == -1) + goto fail; + + if (vol != srcvol) + ERROR(EINVAL, "can't move across volumes"); + + if (dstid == 0) + ERROR(ENOENT, "bad destination path"); + + if (found && + dst.cdrType == cdrDirRec && + dst.u.dir.dirDirID != src.u.dir.dirDirID) + { + dstid = dst.u.dir.dirDirID; + strcpy(dstname, srcname); + + found = v_catsearch(vol, dstid, dstname, 0, 0, 0); + if (found == -1) + goto fail; + } + + moving = (srcid != dstid); + + if (found) + { + const char *ptr; + + ptr = strrchr(dstpath, ':'); + if (ptr == 0) + ptr = dstpath; + else + ++ptr; + + if (*ptr) + strcpy(dstname, ptr); + + if (! moving && strcmp(srcname, dstname) == 0) + goto done; /* source and destination are identical */ + + if (moving || d_relstring(srcname, dstname)) + ERROR(EEXIST, "can't use destination name"); + } + + /* can't move anything into the root directory's parent */ + + if (moving && dstid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, "can't move above root directory"); + + if (moving && isdir) + { + unsigned long id; + + /* can't move root directory anywhere */ + + if (src.u.dir.dirDirID == HFS_CNID_ROOTDIR) + ERROR(EINVAL, "can't move root directory"); + + /* make sure we aren't trying to move a directory inside itself */ + + for (id = dstid; id != HFS_CNID_ROOTDIR; id = dst.u.dthd.thdParID) + { + if (id == src.u.dir.dirDirID) + ERROR(EINVAL, "can't move directory inside itself"); + + if (v_getdthread(vol, id, &dst, 0) <= 0) + goto fail; + } + } + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* change volume name */ + + if (dstid == HFS_CNID_ROOTPAR) + { + if (! validvname(dstname)) + goto fail; + + strcpy(vol->mdb.drVN, dstname); + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + /* remove source record */ + + r_makecatkey(&key, srcid, srcname); + r_packcatkey(&key, record, 0); + + if (bt_delete(&vol->cat, record) == -1) + goto fail; + + /* insert destination record */ + + r_makecatkey(&key, dstid, dstname); + r_packcatrec(&key, &src, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1) + goto fail; + + /* update thread record */ + + if (isdir) + { + if (v_getdthread(vol, src.u.dir.dirDirID, &dst, &n) <= 0) + goto fail; + + dst.u.dthd.thdParID = dstid; + strcpy(dst.u.dthd.thdCName, dstname); + + if (v_putcatrec(&dst, &n) == -1) + goto fail; + } + else + { + found = v_getfthread(vol, src.u.fil.filFlNum, &dst, &n); + if (found == -1) + goto fail; + + if (found) + { + dst.u.fthd.fthdParID = dstid; + strcpy(dst.u.fthd.fthdCName, dstname); + + if (v_putcatrec(&dst, &n) == -1) + goto fail; + } + } + + /* update directory valences */ + + if (moving) + { + if (v_adjvalence(vol, srcid, isdir, -1) == -1 || + v_adjvalence(vol, dstid, isdir, 1) == -1) + goto fail; + } + +done: + return 0; + +fail: + return -1; +} + +/* High-Level Media Routines =============================================== */ + +#ifdef CP_NOT_USED +/* + * NAME: hfs->zero() + * DESCRIPTION: initialize medium with new/empty DDR and partition map + */ +int hfs_zero(const char *path, unsigned int maxparts, unsigned long *blocks) +{ + hfsvol vol; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (maxparts < 1) + ERROR(EINVAL, "must allow at least 1 partition"); + + if (v_open(&vol, path, HFS_MODE_RDWR) == -1 || + v_geometry(&vol, 0) == -1) + goto fail; + + if (m_zeroddr(&vol) == -1 || + m_zeropm(&vol, 1 + maxparts) == -1) + goto fail; + + if (blocks) + { + Partition map; + int found; + + found = m_findpmentry(&vol, "Apple_Free", &map, 0); + if (found == -1) + goto fail; + + if (! found) + ERROR(EIO, "unable to determine free partition space"); + + *blocks = map.pmPartBlkCnt; + } + + if (v_close(&vol) == -1) + goto fail; + + return 0; + +fail: + v_close(&vol); + return -1; +} +#endif + +#ifdef CP_NOT_USED +/* + * NAME: hfs->mkpart() + * DESCRIPTION: create a new HFS partition + */ +int hfs_mkpart(const char *path, unsigned long len) +{ + hfsvol vol; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (v_open(&vol, path, HFS_MODE_RDWR) == -1) + goto fail; + + if (m_mkpart(&vol, "MacOS", "Apple_HFS", len) == -1) + goto fail; + + if (v_close(&vol) == -1) + goto fail; + + return 0; + +fail: + v_close(&vol); + return -1; +} + +/* + * NAME: hfs->nparts() + * DESCRIPTION: return the number of HFS partitions in the medium + */ +int hfs_nparts(const char *path) +{ + hfsvol vol; + int nparts, found; + Partition map; + unsigned long bnum = 0; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (v_open(&vol, path, HFS_MODE_RDONLY) == -1) + goto fail; + + nparts = 0; + while (1) + { + found = m_findpmentry(&vol, "Apple_HFS", &map, &bnum); + if (found == -1) + goto fail; + + if (! found) + break; + + ++nparts; + } + + if (v_close(&vol) == -1) + goto fail; + + return nparts; + +fail: + v_close(&vol); + return -1; +} +#endif + +/* + * NAME: compare() + * DESCRIPTION: comparison function for qsort of blocks to be spared + */ +#if 0 +static +int compare(const unsigned int *n1, const unsigned int *n2) +{ + return *n1 - *n2; +} +#endif + +/* + * NAME: hfs->format() + * DESCRIPTION: write a new filesystem + */ +#ifdef CP_NOT_USED +int hfs_format(const char *path, int pnum, int mode, const char *vname, + unsigned int nbadblocks, const unsigned long badblocks[]) +#else +int hfs_callback_format(oscallback func, void* cookie, int mode, + const char* vname) +#endif +{ + hfsvol vol; + btree *ext = &vol.ext; + btree *cat = &vol.cat; + unsigned int i, *badalloc = 0; + + v_init(&vol, mode); + + if (! validvname(vname)) + goto fail; + +#ifdef CP_NOT_USED + if (v_open(&vol, path, HFS_MODE_RDWR) == -1 || + v_geometry(&vol, pnum) == -1) + goto fail; +#else + if (v_callback_open(&vol, func, cookie) != 0 || + v_geometry(&vol, 0) != 0) + goto fail; +#endif + + /* initialize volume geometry */ + + vol.lpa = 1 + ((vol.vlen - 6) >> 16); + + if (vol.flags & HFS_OPT_2048) + vol.lpa = (vol.lpa + 3) & ~3; + + vol.vbmsz = (vol.vlen / vol.lpa + 0x0fff) >> 12; + + vol.mdb.drSigWord = HFS_SIGWORD; + vol.mdb.drCrDate = d_mtime(time(0)); + vol.mdb.drLsMod = vol.mdb.drCrDate; + vol.mdb.drAtrb = 0; + vol.mdb.drNmFls = 0; + vol.mdb.drVBMSt = 3; + vol.mdb.drAllocPtr = 0; + + vol.mdb.drAlBlkSiz = vol.lpa << HFS_BLOCKSZ_BITS; + vol.mdb.drClpSiz = vol.mdb.drAlBlkSiz << 2; + vol.mdb.drAlBlSt = vol.mdb.drVBMSt + vol.vbmsz; + + if (vol.flags & HFS_OPT_2048) + vol.mdb.drAlBlSt = ((vol.vstart & 3) + vol.mdb.drAlBlSt + 3) & ~3; + + vol.mdb.drNmAlBlks = (vol.vlen - 2 - vol.mdb.drAlBlSt) / vol.lpa; + + vol.mdb.drNxtCNID = HFS_CNID_ROOTDIR; /* modified later */ + vol.mdb.drFreeBks = vol.mdb.drNmAlBlks; + + strcpy(vol.mdb.drVN, vname); + + vol.mdb.drVolBkUp = 0; + vol.mdb.drVSeqNum = 0; + vol.mdb.drWrCnt = 0; + + vol.mdb.drXTClpSiz = vol.mdb.drNmAlBlks / 128 * vol.mdb.drAlBlkSiz; + vol.mdb.drCTClpSiz = vol.mdb.drXTClpSiz; + + vol.mdb.drNmRtDirs = 0; + vol.mdb.drFilCnt = 0; + vol.mdb.drDirCnt = -1; /* incremented when root directory is created */ + + for (i = 0; i < 8; ++i) + vol.mdb.drFndrInfo[i] = 0; + + vol.mdb.drEmbedSigWord = 0x0000; + vol.mdb.drEmbedExtent.xdrStABN = 0; + vol.mdb.drEmbedExtent.xdrNumABlks = 0; + + /* vol.mdb.drXTFlSize */ + /* vol.mdb.drCTFlSize */ + + /* vol.mdb.drXTExtRec[0..2] */ + /* vol.mdb.drCTExtRec[0..2] */ + + vol.flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB; + + /* initialize volume bitmap */ + + vol.vbm = ALLOC(block, vol.vbmsz); + if (vol.vbm == 0) + ERROR(ENOMEM, 0); + + memset(vol.vbm, 0, vol.vbmsz << HFS_BLOCKSZ_BITS); + + vol.flags |= HFS_VOL_UPDATE_VBM; + + /* perform initial bad block sparing */ + +#ifdef CP_NOT_USED + if (nbadblocks > 0) + { + if (nbadblocks * 4 > vol.vlen) + ERROR(EINVAL, "volume contains too many bad blocks"); + + badalloc = ALLOC(unsigned int, nbadblocks); + if (badalloc == 0) + ERROR(ENOMEM, 0); + + if (vol.mdb.drNmAlBlks == 1594) + vol.mdb.drFreeBks = --vol.mdb.drNmAlBlks; + + for (i = 0; i < nbadblocks; ++i) + { + unsigned long bnum; + unsigned int anum; + + bnum = badblocks[i]; + + if (bnum < vol.mdb.drAlBlSt || bnum == vol.vlen - 2) + ERROR(EINVAL, "can't spare critical bad block"); + else if (bnum >= vol.vlen) + ERROR(EINVAL, "bad block not in volume"); + + anum = (bnum - vol.mdb.drAlBlSt) / vol.lpa; + + if (anum < vol.mdb.drNmAlBlks) + BMSET(vol.vbm, anum); + + badalloc[i] = anum; + } + + vol.mdb.drAtrb |= HFS_ATRB_BBSPARED; + } +#endif + + /* create extents overflow file */ + + n_init(&ext->hdrnd, ext, ndHdrNode, 0); + + ext->hdrnd.nnum = 0; + ext->hdrnd.nd.ndNRecs = 3; + ext->hdrnd.roff[1] = 0x078; + ext->hdrnd.roff[2] = 0x0f8; + ext->hdrnd.roff[3] = 0x1f8; + + memset(HFS_NODEREC(ext->hdrnd, 1), 0, 128); + + ext->hdr.bthDepth = 0; + ext->hdr.bthRoot = 0; + ext->hdr.bthNRecs = 0; + ext->hdr.bthFNode = 0; + ext->hdr.bthLNode = 0; + ext->hdr.bthNodeSize = HFS_BLOCKSZ; + ext->hdr.bthKeyLen = 0x07; + ext->hdr.bthNNodes = 0; + ext->hdr.bthFree = 0; + for (i = 0; i < 76; ++i) + ext->hdr.bthResv[i] = 0; + + ext->map = ALLOC(byte, HFS_MAP1SZ); + if (ext->map == 0) + ERROR(ENOMEM, 0); + + memset(ext->map, 0, HFS_MAP1SZ); + BMSET(ext->map, 0); + + ext->mapsz = HFS_MAP1SZ; + ext->flags = HFS_BT_UPDATE_HDR; + + /* create catalog file */ + + n_init(&cat->hdrnd, cat, ndHdrNode, 0); + + cat->hdrnd.nnum = 0; + cat->hdrnd.nd.ndNRecs = 3; + cat->hdrnd.roff[1] = 0x078; + cat->hdrnd.roff[2] = 0x0f8; + cat->hdrnd.roff[3] = 0x1f8; + + memset(HFS_NODEREC(cat->hdrnd, 1), 0, 128); + + cat->hdr.bthDepth = 0; + cat->hdr.bthRoot = 0; + cat->hdr.bthNRecs = 0; + cat->hdr.bthFNode = 0; + cat->hdr.bthLNode = 0; + cat->hdr.bthNodeSize = HFS_BLOCKSZ; + cat->hdr.bthKeyLen = 0x25; + cat->hdr.bthNNodes = 0; + cat->hdr.bthFree = 0; + for (i = 0; i < 76; ++i) + cat->hdr.bthResv[i] = 0; + + cat->map = ALLOC(byte, HFS_MAP1SZ); + if (cat->map == 0) + ERROR(ENOMEM, 0); + + memset(cat->map, 0, HFS_MAP1SZ); + BMSET(cat->map, 0); + + cat->mapsz = HFS_MAP1SZ; + cat->flags = HFS_BT_UPDATE_HDR; + + /* allocate space for header nodes (and initial extents) */ + + if (bt_space(ext, 1) == -1 || + bt_space(cat, 1) == -1) + goto fail; + + --ext->hdr.bthFree; + --cat->hdr.bthFree; + + /* create extent records for bad blocks */ + +#ifdef CP_NOT_USED + if (nbadblocks > 0) + { + hfsfile bbfile; + ExtDescriptor extent; + ExtDataRec *extrec; + ExtKeyRec key; + byte record[HFS_MAX_EXTRECLEN]; + unsigned int reclen; + + f_init(&bbfile, &vol, HFS_CNID_BADALLOC, "bad blocks"); + + qsort(badalloc, nbadblocks, sizeof(*badalloc), + (int (*)(const void *, const void *)) compare); + + for (i = 0; i < nbadblocks; ++i) + { + if (i == 0 || badalloc[i] != extent.xdrStABN) + { + extent.xdrStABN = badalloc[i]; + extent.xdrNumABlks = 1; + + if (extent.xdrStABN < vol.mdb.drNmAlBlks && + f_addextent(&bbfile, &extent) == -1) + goto fail; + } + } + + /* flush local extents into extents overflow file */ + + f_getptrs(&bbfile, &extrec, 0, 0); + + r_makeextkey(&key, bbfile.fork, bbfile.cat.u.fil.filFlNum, 0); + r_packextrec(&key, extrec, record, &reclen); + + if (bt_insert(&vol.ext, record, reclen) == -1) + goto fail; + } +#endif + + vol.flags |= HFS_VOL_MOUNTED; + + /* create root directory */ + + if (v_mkdir(&vol, HFS_CNID_ROOTPAR, vname) == -1) + goto fail; + + vol.mdb.drNxtCNID = 16; /* first CNID not reserved by Apple */ + + /* write boot blocks */ + + if (m_zerobb(&vol) == -1) + goto fail; + + /* zero other unused space, if requested */ + + if (vol.flags & HFS_OPT_ZERO) + { + block b; + unsigned long bnum; + + memset(&b, 0, sizeof(b)); + + /* between MDB and VBM (never) */ + + for (bnum = 3; bnum < vol.mdb.drVBMSt; ++bnum) + b_writelb(&vol, bnum, &b); + + /* between VBM and first allocation block (sometimes if HFS_OPT_2048) */ + + for (bnum = vol.mdb.drVBMSt + vol.vbmsz; bnum < vol.mdb.drAlBlSt; ++bnum) + b_writelb(&vol, bnum, &b); + + /* between last allocation block and alternate MDB (sometimes) */ + + for (bnum = vol.mdb.drAlBlSt + vol.mdb.drNmAlBlks * vol.lpa; + bnum < vol.vlen - 2; ++bnum) + b_writelb(&vol, bnum, &b); + + /* final block (always) */ + + b_writelb(&vol, vol.vlen - 1, &b); + } + + /* flush remaining state and close volume */ + + if (v_close(&vol) == -1) + goto fail; + + FREE(badalloc); + + return 0; + +fail: + v_close(&vol); + + FREE(badalloc); + + return -1; +} + diff --git a/libhfs/hfs.h b/libhfs/hfs.h new file mode 100644 index 0000000..e0b9499 --- /dev/null +++ b/libhfs/hfs.h @@ -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 + +# 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 diff --git a/libhfs/libhfs.h b/libhfs/libhfs.h new file mode 100644 index 0000000..b9131a1 --- /dev/null +++ b/libhfs/libhfs.h @@ -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 + +# 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; diff --git a/libhfs/libhfs.vcxproj b/libhfs/libhfs.vcxproj new file mode 100644 index 0000000..e281f41 --- /dev/null +++ b/libhfs/libhfs.vcxproj @@ -0,0 +1,136 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {0FA742E9-8C07-43DD-AFF8-CE31FAF70821} + + + + + + StaticLibrary + v143 + false + Unicode + + + StaticLibrary + v143 + Unicode + Dynamic + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + + Disabled + WIN32;_DEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + $(IntDir)$(TargetName).pch + $(IntDir) + $(IntDir) + $(IntDir)vc$(PlatformToolsetVersion).pdb + true + Level2 + true + EditAndContinue + + + $(OutDir)$(TargetName)$(TargetExt) + true + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + MaxSpeed + OnlyExplicitInline + WIN32;NDEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + $(IntDir)$(TargetName).pch + $(IntDir) + $(IntDir) + $(IntDir)vc$(PlatformToolsetVersion).pdb + Level2 + true + + + $(OutDir)$(TargetName)$(TargetExt) + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libhfs/libhfs.vcxproj.filters b/libhfs/libhfs.vcxproj.filters new file mode 100644 index 0000000..3a24437 --- /dev/null +++ b/libhfs/libhfs.vcxproj.filters @@ -0,0 +1,98 @@ + + + + + {2d85999b-987f-4814-9a1a-50647f99d4a7} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {3de635b8-d8d0-4540-8bc6-f060838e8e8e} + h;hpp;hxx;hm;inl + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/libhfs/low.c b/libhfs/low.c new file mode 100644 index 0000000..a5c2727 --- /dev/null +++ b/libhfs/low.c @@ -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 +# include +# include + +# 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; +} diff --git a/libhfs/low.h b/libhfs/low.h new file mode 100644 index 0000000..0e59d83 --- /dev/null +++ b/libhfs/low.h @@ -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); diff --git a/libhfs/medium.c b/libhfs/medium.c new file mode 100644 index 0000000..bcd070d --- /dev/null +++ b/libhfs/medium.c @@ -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 +# include +# include + +# 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; +} diff --git a/libhfs/medium.h b/libhfs/medium.h new file mode 100644 index 0000000..912f40c --- /dev/null +++ b/libhfs/medium.h @@ -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 *); diff --git a/libhfs/memcmp.c b/libhfs/memcmp.c new file mode 100644 index 0000000..037b5b2 --- /dev/null +++ b/libhfs/memcmp.c @@ -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 + +/* + * 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; +} diff --git a/libhfs/node.c b/libhfs/node.c new file mode 100644 index 0000000..835bbc0 --- /dev/null +++ b/libhfs/node.c @@ -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 +# include +# include + +# 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; +} diff --git a/libhfs/node.h b/libhfs/node.h new file mode 100644 index 0000000..12272ce --- /dev/null +++ b/libhfs/node.h @@ -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 *); diff --git a/libhfs/os.c b/libhfs/os.c new file mode 100644 index 0000000..22b8d3a --- /dev/null +++ b/libhfs/os.c @@ -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 +# else +int open(const char *, int, ...); +int fcntl(int, int, ...); +# endif + +# ifdef HAVE_UNISTD_H +# include +# 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 +# include +# include +# include /* 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; +} diff --git a/libhfs/os.h b/libhfs/os.h new file mode 100644 index 0000000..a40c74f --- /dev/null +++ b/libhfs/os.h @@ -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); diff --git a/libhfs/record.c b/libhfs/record.c new file mode 100644 index 0000000..fe269a3 --- /dev/null +++ b/libhfs/record.c @@ -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 + +# 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; + } +} diff --git a/libhfs/record.h b/libhfs/record.h new file mode 100644 index 0000000..74b3067 --- /dev/null +++ b/libhfs/record.h @@ -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 *); diff --git a/libhfs/version.c b/libhfs/version.c new file mode 100644 index 0000000..e88e560 --- /dev/null +++ b/libhfs/version.c @@ -0,0 +1,29 @@ +/* + * 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 "version.h" + +const char libhfs_rcsid[] = + "$Id$"; + +const char libhfs_version[] = "libhfs version 3.2.6"; +const char libhfs_copyright[] = "Copyright (C) 1996-1998 Robert Leslie"; +const char libhfs_author[] = "Robert Leslie "; diff --git a/libhfs/version.h b/libhfs/version.h new file mode 100644 index 0000000..8717e07 --- /dev/null +++ b/libhfs/version.h @@ -0,0 +1,26 @@ +/* + * 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 char libhfs_rcsid[]; + +extern const char libhfs_version[]; +extern const char libhfs_copyright[]; +extern const char libhfs_author[]; diff --git a/libhfs/volume.c b/libhfs/volume.c new file mode 100644 index 0000000..687f383 --- /dev/null +++ b/libhfs/volume.c @@ -0,0 +1,1454 @@ +/* + * 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 +# include +# include +# include +# include +# include /* debug */ + +# include "libhfs.h" +# include "volume.h" +# include "data.h" +# include "block.h" +# include "low.h" +# include "medium.h" +# include "file.h" +# include "btree.h" +# include "record.h" +# include "os.h" + +/* + * NAME: vol->init() + * DESCRIPTION: initialize volume structure + */ +void v_init(hfsvol *vol, int flags) +{ + btree *ext = &vol->ext; + btree *cat = &vol->cat; + + vol->priv = 0; + vol->flags = flags & HFS_VOL_OPT_MASK; + + vol->pnum = -1; + vol->vstart = 0; + vol->vlen = 0; + vol->lpa = 0; + + vol->cache = 0; + + vol->vbm = 0; + vol->vbmsz = 0; + + f_init(&ext->f, vol, HFS_CNID_EXT, "extents overflow"); + + ext->map = 0; + ext->mapsz = 0; + ext->flags = 0; + + ext->keyunpack = (keyunpackfunc) r_unpackextkey; + ext->keycompare = (keycomparefunc) r_compareextkeys; + + f_init(&cat->f, vol, HFS_CNID_CAT, "catalog"); + + cat->map = 0; + cat->mapsz = 0; + cat->flags = 0; + + cat->keyunpack = (keyunpackfunc) r_unpackcatkey; + cat->keycompare = (keycomparefunc) r_comparecatkeys; + + vol->cwd = HFS_CNID_ROOTDIR; + + vol->refs = 0; + vol->files = 0; + vol->dirs = 0; + + vol->prev = 0; + vol->next = 0; +} + +#ifdef CP_NOT_USED +/* + * NAME: vol->open() + * DESCRIPTION: open volume source and lock against concurrent updates + */ +int v_open(hfsvol *vol, const char *path, int mode) +{ + if (vol->flags & HFS_VOL_OPEN) + { + ERROR(EINVAL, "volume already open"); + } + + if (os_open(&vol->priv, path, mode) == -1) + { + goto fail; + } + + vol->flags |= HFS_VOL_OPEN; + + /* initialize volume block cache (OK to fail) */ + + if (! (vol->flags & HFS_OPT_NOCACHE) && + b_init(vol) != -1) + { + vol->flags |= HFS_VOL_USINGCACHE; + } + + return 0; + +fail: + return -1; +} +#endif + +/* + * NAME: vol->opencallback() + * DESCRIPTION: open volume source and lock against concurrent updates + */ +int v_callback_open(hfsvol* vol, oscallback func, void* cookie) +{ + if (vol->flags & HFS_VOL_OPEN) + { + ERROR(EINVAL, "volume already open"); + } + + if (os_callback_open(&vol->priv, func, cookie) == -1) + { + goto fail; + } + + vol->flags |= HFS_VOL_OPEN; + + /* initialize volume block cache (OK to fail) */ + + if (! (vol->flags & HFS_OPT_NOCACHE) && + b_init(vol) != -1) + { + vol->flags |= HFS_VOL_USINGCACHE; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: flushvol() + * DESCRIPTION: flush all pending changes (B*-tree, MDB, VBM) to volume + */ +static +int flushvol(hfsvol *vol, int umount) +{ + if (vol->flags & HFS_VOL_READONLY) + { + goto done; + } + + if ((vol->ext.flags & HFS_BT_UPDATE_HDR) && + bt_writehdr(&vol->ext) == -1) + { + goto fail; + } + + if ((vol->cat.flags & HFS_BT_UPDATE_HDR) && + bt_writehdr(&vol->cat) == -1) + { + goto fail; + } + + if ((vol->flags & HFS_VOL_UPDATE_VBM) && + v_writevbm(vol) == -1) + { + goto fail; + } + + /* + * CiderPress note: this causes the MDB to be written when we are + * unmounting the volume if changes have been made at any point. + * This means we ALWAYS write something when we're closing the disk + * if we touched something. That's not great for us, since we + * might be using removable media. We can "fix" this by removing + * the part that marks the volume as mounted (earlier). + */ + if (umount && ! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED)) + { + vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + if ((vol->flags & (HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB)) && + v_writemdb(vol) == -1) + { + goto fail; + } + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->flush() + * DESCRIPTION: commit all pending changes to volume device + */ +int v_flush(hfsvol *vol) +{ + if (flushvol(vol, 0) == -1) + { + goto fail; + } + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_flush(vol) == -1) + { + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->close() + * DESCRIPTION: close access path to volume source + */ +int v_close(hfsvol *vol) +{ + int result = 0; + + if (! (vol->flags & HFS_VOL_OPEN)) + { + goto done; + } + + if ((vol->flags & HFS_VOL_MOUNTED) && + flushvol(vol, 1) == -1) + { + result = -1; + } + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_finish(vol) == -1) + { + result = -1; + } + + if (os_close(&vol->priv) == -1) + { + result = -1; + } + + vol->flags &= ~(HFS_VOL_OPEN | HFS_VOL_MOUNTED | HFS_VOL_USINGCACHE); + + /* free dynamically allocated structures */ + + FREE(vol->vbm); + + vol->vbm = 0; + vol->vbmsz = 0; + + FREE(vol->ext.map); + FREE(vol->cat.map); + + vol->ext.map = 0; + vol->cat.map = 0; + +done: + return result; +} + +#ifdef CP_NOT_USED +/* + * NAME: vol->same() + * DESCRIPTION: return 1 iff path is same as open volume + */ +int v_same(hfsvol *vol, const char *path) +{ + return os_same(&vol->priv, path); +} +#endif + +/* + * NAME: vol->geometry() + * DESCRIPTION: determine volume location and size (possibly in a partition) + */ +int v_geometry(hfsvol *vol, int pnum) +{ + Partition map; + unsigned long bnum = 0; + int found; + + vol->pnum = pnum; + + if (pnum == 0) + { + vol->vstart = 0; + vol->vlen = b_size(vol); + + if (vol->vlen == 0) + { + goto fail; + } + } + else + { + while (pnum--) + { + found = m_findpmentry(vol, "Apple_HFS", &map, &bnum); + if (found == -1 || ! found) + { + goto fail; + } + } + + vol->vstart = map.pmPyPartStart; + vol->vlen = map.pmPartBlkCnt; + + if (map.pmDataCnt) + { + if ((unsigned long) map.pmLgDataStart + + (unsigned long) map.pmDataCnt > vol->vlen) + { + ERROR(EINVAL, "partition data overflows partition"); + } + + vol->vstart += (unsigned long) map.pmLgDataStart; + vol->vlen = map.pmDataCnt; + } + + if (vol->vlen == 0) + { + ERROR(EINVAL, "volume partition is empty"); + } + } + + if (vol->vlen < 800 * (1024 >> HFS_BLOCKSZ_BITS)) + { + ERROR(EINVAL, "volume is smaller than 800K"); + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->readmdb() + * DESCRIPTION: load Master Directory Block into memory + */ +int v_readmdb(hfsvol *vol) +{ + if (l_getmdb(vol, &vol->mdb, 0) == -1) + { + goto fail; + } + + if (vol->mdb.drSigWord != HFS_SIGWORD) + { + if (vol->mdb.drSigWord == HFS_SIGWORD_MFS) + { + ERROR(EINVAL, "MFS volume format not supported"); + } + else + { + ERROR(EINVAL, "not a Macintosh HFS volume"); + } + } + + if (vol->mdb.drAlBlkSiz % HFS_BLOCKSZ != 0) + { + ERROR(EINVAL, "bad volume allocation block size"); + } + + vol->lpa = vol->mdb.drAlBlkSiz >> HFS_BLOCKSZ_BITS; + + /* extents pseudo-file structs */ + + vol->ext.f.cat.u.fil.filStBlk = vol->mdb.drXTExtRec[0].xdrStABN; + vol->ext.f.cat.u.fil.filLgLen = vol->mdb.drXTFlSize; + vol->ext.f.cat.u.fil.filPyLen = vol->mdb.drXTFlSize; + + vol->ext.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; + vol->ext.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; + + memcpy(&vol->ext.f.cat.u.fil.filExtRec, + &vol->mdb.drXTExtRec, sizeof(ExtDataRec)); + + f_selectfork(&vol->ext.f, fkData); + + /* catalog pseudo-file structs */ + + vol->cat.f.cat.u.fil.filStBlk = vol->mdb.drCTExtRec[0].xdrStABN; + vol->cat.f.cat.u.fil.filLgLen = vol->mdb.drCTFlSize; + vol->cat.f.cat.u.fil.filPyLen = vol->mdb.drCTFlSize; + + vol->cat.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; + vol->cat.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; + + memcpy(&vol->cat.f.cat.u.fil.filExtRec, + &vol->mdb.drCTExtRec, sizeof(ExtDataRec)); + + f_selectfork(&vol->cat.f, fkData); + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->writemdb() + * DESCRIPTION: flush Master Directory Block to medium + */ +int v_writemdb(hfsvol *vol) +{ + vol->mdb.drLsMod = d_mtime(time(0)); + + vol->mdb.drXTFlSize = vol->ext.f.cat.u.fil.filPyLen; + memcpy(&vol->mdb.drXTExtRec, + &vol->ext.f.cat.u.fil.filExtRec, sizeof(ExtDataRec)); + + vol->mdb.drCTFlSize = vol->cat.f.cat.u.fil.filPyLen; + memcpy(&vol->mdb.drCTExtRec, + &vol->cat.f.cat.u.fil.filExtRec, sizeof(ExtDataRec)); + + if (l_putmdb(vol, &vol->mdb, vol->flags & HFS_VOL_UPDATE_ALTMDB) == -1) + { + goto fail; + } + + vol->flags &= ~(HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB); + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->readvbm() + * DESCRIPTION: read volume bitmap into memory + */ +int v_readvbm(hfsvol *vol) +{ + unsigned int vbmst = vol->mdb.drVBMSt; + unsigned int vbmsz = (vol->mdb.drNmAlBlks + 0x0fff) >> 12; + block *bp; + + ASSERT(vol->vbm == 0); + + if (vol->mdb.drAlBlSt - vbmst < vbmsz) + { + ERROR(EIO, "volume bitmap collides with volume data"); + } + + vol->vbm = ALLOC(block, vbmsz); + if (vol->vbm == 0) + { + ERROR(ENOMEM, 0); + } + + vol->vbmsz = vbmsz; + + for (bp = vol->vbm; vbmsz--; ++bp) + { + if (b_readlb(vol, vbmst++, bp) == -1) + { + goto fail; + } + } + + return 0; + +fail: + FREE(vol->vbm); + + vol->vbm = 0; + vol->vbmsz = 0; + + return -1; +} + +/* + * NAME: vol->writevbm() + * DESCRIPTION: flush volume bitmap to medium + */ +int v_writevbm(hfsvol *vol) +{ + unsigned int vbmst = vol->mdb.drVBMSt; + unsigned int vbmsz = vol->vbmsz; + const block *bp; + + for (bp = vol->vbm; vbmsz--; ++bp) + { + if (b_writelb(vol, vbmst++, bp) == -1) + { + goto fail; + } + } + + vol->flags &= ~HFS_VOL_UPDATE_VBM; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->mount() + * DESCRIPTION: load volume information into memory + */ +int v_mount(hfsvol *vol) +{ + /* read the MDB, volume bitmap, and extents/catalog B*-tree headers */ + + if (v_readmdb(vol) == -1 || + v_readvbm(vol) == -1 || + bt_readhdr(&vol->ext) == -1 || + bt_readhdr(&vol->cat) == -1) + { + goto fail; + } + + if (! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED) && + v_scavenge(vol) == -1) + { + goto fail; + } + + if (vol->mdb.drAtrb & HFS_ATRB_SLOCKED) + { + vol->flags |= HFS_VOL_READONLY; + } + else if (vol->flags & HFS_VOL_READONLY) + { + vol->mdb.drAtrb |= HFS_ATRB_HLOCKED; + } + else + { + vol->mdb.drAtrb &= ~HFS_ATRB_HLOCKED; + } + + vol->flags |= HFS_VOL_MOUNTED; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->dirty() + * DESCRIPTION: ensure the volume is marked "in use" before we make changes + */ +int v_dirty(hfsvol *vol) +{ +#ifdef NOT_FOR_CP // see notes in flushvol() + if (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED) + { + vol->mdb.drAtrb &= ~HFS_ATRB_UMOUNTED; + ++vol->mdb.drWrCnt; + + if (v_writemdb(vol) == -1) + { + goto fail; + } + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_flush(vol) == -1) + { + goto fail; + } + } + return 0; +fail: + return -1; +#endif + + return 0; +} + +/* + * NAME: vol->catsearch() + * DESCRIPTION: search catalog tree + */ +int v_catsearch(hfsvol *vol, unsigned long parid, const char *name, + CatDataRec *data, char *cname, node *np) +{ + CatKeyRec key; + byte pkey[HFS_CATKEYLEN]; + const byte *ptr; + node n; + int found; + + if (np == 0) + { + np = &n; + } + + r_makecatkey(&key, parid, name); + r_packcatkey(&key, pkey, 0); + + found = bt_search(&vol->cat, pkey, np); + if (found <= 0) + { + return found; + } + + ptr = HFS_NODEREC(*np, np->rnum); + + if (cname) + { + r_unpackcatkey(ptr, &key); + strcpy(cname, key.ckrCName); + } + + if (data) + { + r_unpackcatdata(HFS_RECDATA(ptr), data); + } + + return 1; +} + +/* + * NAME: vol->extsearch() + * DESCRIPTION: search extents tree + */ +int v_extsearch(hfsfile *file, unsigned int fabn, + ExtDataRec *data, node *np) +{ + ExtKeyRec key; + ExtDataRec extsave; + unsigned int fabnsave; + byte pkey[HFS_EXTKEYLEN]; + const byte *ptr; + node n; + int found; + + if (np == 0) + { + np = &n; + } + + r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, fabn); + r_packextkey(&key, pkey, 0); + + /* in case bt_search() clobbers these */ + + memcpy(&extsave, &file->ext, sizeof(ExtDataRec)); + fabnsave = file->fabn; + + found = bt_search(&file->vol->ext, pkey, np); + + memcpy(&file->ext, &extsave, sizeof(ExtDataRec)); + file->fabn = fabnsave; + + if (found <= 0) + { + return found; + } + + if (data) + { + ptr = HFS_NODEREC(*np, np->rnum); + r_unpackextdata(HFS_RECDATA(ptr), data); + } + + return 1; +} + +/* + * NAME: vol->getthread() + * DESCRIPTION: retrieve catalog thread information for a file or directory + */ +int v_getthread(hfsvol *vol, unsigned long id, + CatDataRec *thread, node *np, int type) +{ + CatDataRec rec; + int found; + + if (thread == 0) + { + thread = &rec; + } + + found = v_catsearch(vol, id, "", thread, 0, np); + if (found == 1 && thread->cdrType != type) + { + ERROR(EIO, "bad thread record"); + } + + return found; + +fail: + return -1; +} + +/* + * NAME: vol->putcatrec() + * DESCRIPTION: store catalog information + */ +int v_putcatrec(const CatDataRec *data, node *np) +{ + byte pdata[HFS_CATDATALEN], *ptr; + unsigned int len = 0; + + r_packcatdata(data, pdata, &len); + + ptr = HFS_NODEREC(*np, np->rnum); + memcpy(HFS_RECDATA(ptr), pdata, len); + + return bt_putnode(np); +} + +/* + * NAME: vol->putextrec() + * DESCRIPTION: store extent information + */ +int v_putextrec(const ExtDataRec *data, node *np) +{ + byte pdata[HFS_EXTDATALEN], *ptr; + unsigned int len = 0; + + r_packextdata(data, pdata, &len); + + ptr = HFS_NODEREC(*np, np->rnum); + memcpy(HFS_RECDATA(ptr), pdata, len); + + return bt_putnode(np); +} + +/* + * NAME: vol->allocblocks() + * DESCRIPTION: allocate a contiguous range of blocks + */ +int v_allocblocks(hfsvol *vol, ExtDescriptor *blocks) +{ + unsigned int request, found, foundat, start, end; + register unsigned int pt; + block *vbm; + int wrap = 0; + + if (vol->mdb.drFreeBks == 0) + { + ERROR(ENOSPC, "volume full"); + } + + request = blocks->xdrNumABlks; + found = 0; + foundat = 0; + start = vol->mdb.drAllocPtr; + end = vol->mdb.drNmAlBlks; + vbm = vol->vbm; + + ASSERT(request > 0); + + /* backtrack the start pointer to recover unused space */ + + if (! BMTST(vbm, start)) + { + while (start > 0 && ! BMTST(vbm, start - 1)) + { + --start; + } + } + + /* find largest unused block which satisfies request */ + + pt = start; + + while (1) + { + unsigned int mark; + + /* skip blocks in use */ + + while (pt < end && BMTST(vbm, pt)) + { + ++pt; + } + + if (wrap && pt >= start) + { + break; + } + + /* count blocks not in use */ + + mark = pt; + while (pt < end && pt - mark < request && ! BMTST(vbm, pt)) + { + ++pt; + } + + if (pt - mark > found) + { + found = pt - mark; + foundat = mark; + } + + if (wrap && pt >= start) + { + break; + } + + if (pt == end) + { + pt = 0, wrap = 1; + } + + if (found == request) + { + break; + } + } + + if (found == 0 || found > vol->mdb.drFreeBks) + { + ERROR(EIO, "bad volume bitmap or free block count"); + } + + blocks->xdrStABN = foundat; + blocks->xdrNumABlks = found; + + if (v_dirty(vol) == -1) + { + goto fail; + } + + vol->mdb.drAllocPtr = pt; + vol->mdb.drFreeBks -= found; + + for (pt = foundat; pt < foundat + found; ++pt) + { + BMSET(vbm, pt); + } + + vol->flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_VBM; + + if (vol->flags & HFS_OPT_ZERO) + { + block b; + unsigned int i; + + memset(&b, 0, sizeof(b)); + + for (pt = foundat; pt < foundat + found; ++pt) + { + for (i = 0; i < vol->lpa; ++i) + { + b_writeab(vol, pt, i, &b); + } + } + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->freeblocks() + * DESCRIPTION: deallocate a contiguous range of blocks + */ +int v_freeblocks(hfsvol *vol, const ExtDescriptor *blocks) +{ + unsigned int start, len, pt; + block *vbm; + + start = blocks->xdrStABN; + len = blocks->xdrNumABlks; + vbm = vol->vbm; + + if (v_dirty(vol) == -1) + { + goto fail; + } + + vol->mdb.drFreeBks += len; + + for (pt = start; pt < start + len; ++pt) + { + BMCLR(vbm, pt); + } + + vol->flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_VBM; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->resolve() + * DESCRIPTION: translate a pathname; return catalog information + */ +int v_resolve(hfsvol **vol, const char *path, + CatDataRec *data, long *parid, char *fname, node *np) +{ + unsigned long dirid; + char name[HFS_MAX_FLEN + 1], *nptr; + int found = 0; + + if (*path == 0) + { + ERROR(ENOENT, "empty path"); + } + + if (parid) + { + *parid = 0; + } + + nptr = strchr(path, ':'); + + if (*path == ':' || nptr == 0) + { + dirid = (*vol)->cwd; /* relative path */ + + if (*path == ':') + { + ++path; + } + + if (*path == 0) + { + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + { + goto fail; + } + + if (found) + { + if (parid) + { + *parid = data->u.dthd.thdParID; + } + + found = v_catsearch(*vol, data->u.dthd.thdParID, + data->u.dthd.thdCName, data, fname, np); + if (found == -1) + { + goto fail; + } + } + + goto done; + } + } + else + { +#ifdef CP_NO_STATIC + hfsvol *check; +#endif + + dirid = HFS_CNID_ROOTPAR; /* absolute path */ + + if (nptr - path > HFS_MAX_VLEN) + { + ERROR(ENAMETOOLONG, 0); + } + + strncpy(name, path, nptr - path); + name[nptr - path] = 0; + +#ifdef CP_NO_STATIC + for (check = hfs_mounts; check; check = check->next) + { + if (d_relstring(check->mdb.drVN, name) == 0) + { + *vol = check; + break; + } + } +#else + assert(*vol != 0); +#endif + } + + while (1) + { + while (*path == ':') + { + ++path; + + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + { + goto fail; + } + else if (! found) + { + goto done; + } + + dirid = data->u.dthd.thdParID; + } + + if (*path == 0) + { + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + { + goto fail; + } + + if (found) + { + if (parid) + { + *parid = data->u.dthd.thdParID; + } + + found = v_catsearch(*vol, data->u.dthd.thdParID, + data->u.dthd.thdCName, data, fname, np); + if (found == -1) + { + goto fail; + } + } + + goto done; + } + + nptr = name; + while (nptr < name + sizeof(name) - 1 && *path && *path != ':') + { + *nptr++ = *path++; + } + + if (*path && *path != ':') + { + ERROR(ENAMETOOLONG, 0); + } + + *nptr = 0; + if (*path == ':') + { + ++path; + } + + if (parid) + { + *parid = dirid; + } + + found = v_catsearch(*vol, dirid, name, data, fname, np); + if (found == -1) + { + goto fail; + } + + if (! found) + { + if (*path && parid) + { + *parid = 0; + } + + if (*path == 0 && fname) + { + strcpy(fname, name); + } + + goto done; + } + + switch (data->cdrType) + { + case cdrDirRec: + if (*path == 0) + { + goto done; + } + + dirid = data->u.dir.dirDirID; + break; + + case cdrFilRec: + if (*path == 0) + { + goto done; + } + + ERROR(ENOTDIR, "invalid pathname"); + + default: + ERROR(EIO, "unexpected catalog record"); + } + } + +done: + return found; + +fail: + return -1; +} + +/* + * NAME: vol->adjvalence() + * DESCRIPTION: update a volume's valence counts + */ +int v_adjvalence(hfsvol *vol, unsigned long parid, int isdir, int adj) +{ + node n; + CatDataRec data; + int result = 0; + + if (isdir) + { + vol->mdb.drDirCnt += adj; + } + else + { + vol->mdb.drFilCnt += adj; + } + + vol->flags |= HFS_VOL_UPDATE_MDB; + + if (parid == HFS_CNID_ROOTDIR) + { + if (isdir) + { + vol->mdb.drNmRtDirs += adj; + } + else + { + vol->mdb.drNmFls += adj; + } + } + else if (parid == HFS_CNID_ROOTPAR) + { + goto done; + } + + if (v_getdthread(vol, parid, &data, 0) <= 0 || + v_catsearch(vol, data.u.dthd.thdParID, data.u.dthd.thdCName, + &data, 0, &n) <= 0 || + data.cdrType != cdrDirRec) + { + ERROR(EIO, "can't find parent directory"); + } + + data.u.dir.dirVal += adj; + data.u.dir.dirMdDat = d_mtime(time(0)); + + result = v_putcatrec(&data, &n); + +done: + return result; + +fail: + return -1; +} + +/* + * NAME: vol->mkdir() + * DESCRIPTION: create a new HFS directory + */ +int v_mkdir(hfsvol *vol, unsigned long parid, const char *name) +{ + CatKeyRec key; + CatDataRec data; + unsigned long id; + byte record[HFS_MAX_CATRECLEN]; + unsigned int reclen; + int i; + + if (bt_space(&vol->cat, 2) == -1) + { + goto fail; + } + + id = vol->mdb.drNxtCNID++; + vol->flags |= HFS_VOL_UPDATE_MDB; + + /* create directory record */ + + data.cdrType = cdrDirRec; + data.cdrResrv2 = 0; + + data.u.dir.dirFlags = 0; + data.u.dir.dirVal = 0; + data.u.dir.dirDirID = id; + data.u.dir.dirCrDat = d_mtime(time(0)); + data.u.dir.dirMdDat = data.u.dir.dirCrDat; + data.u.dir.dirBkDat = 0; + + memset(&data.u.dir.dirUsrInfo, 0, sizeof(data.u.dir.dirUsrInfo)); + memset(&data.u.dir.dirFndrInfo, 0, sizeof(data.u.dir.dirFndrInfo)); + for (i = 0; i < 4; ++i) + { + data.u.dir.dirResrv[i] = 0; + } + + r_makecatkey(&key, parid, name); + r_packcatrec(&key, &data, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1) + { + goto fail; + } + + /* create thread record */ + + data.cdrType = cdrThdRec; + data.cdrResrv2 = 0; + + data.u.dthd.thdResrv[0] = 0; + data.u.dthd.thdResrv[1] = 0; + data.u.dthd.thdParID = parid; + strcpy(data.u.dthd.thdCName, name); + + r_makecatkey(&key, id, ""); + r_packcatrec(&key, &data, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1 || + v_adjvalence(vol, parid, 1, 1) == -1) + { + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: markexts() + * DESCRIPTION: set bits from an extent record in the volume bitmap + */ +static +void markexts(block *vbm, const ExtDataRec *exts) +{ + int i; + unsigned int pt, len; + + for (i = 0; i < 3; ++i) + { + for ( pt = (*exts)[i].xdrStABN, + len = (*exts)[i].xdrNumABlks; len--; ++pt) + { + BMSET(vbm, pt); + } + } +} + +/* + * NAME: vol->scavenge() + * DESCRIPTION: safeguard blocks in the volume bitmap + */ +int v_scavenge(hfsvol *vol) +{ + block *vbm = vol->vbm; + node n; + unsigned int pt, blks; + unsigned long lastcnid = 15; + +# ifdef DEBUG + fprintf(stderr, "VOL: \"%s\" not cleanly unmounted\n", + vol->mdb.drVN); +# endif + + if (vol->flags & HFS_VOL_READONLY) + { + goto done; + } + +# ifdef DEBUG + fprintf(stderr, "VOL: scavenging...\n"); +# endif + + /* reset MDB by marking it dirty again */ + + vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED; + if (v_dirty(vol) == -1) + { + goto fail; + } + + /* begin by marking extents in MDB */ + + markexts(vbm, &vol->mdb.drXTExtRec); + markexts(vbm, &vol->mdb.drCTExtRec); + + vol->flags |= HFS_VOL_UPDATE_VBM; + + /* scavenge the extents overflow file */ + + if (vol->ext.hdr.bthFNode > 0) + { + if (bt_getnode(&n, &vol->ext, vol->ext.hdr.bthFNode) == -1) + { + goto fail; + } + + n.rnum = 0; + + while (1) + { + ExtDataRec data; + const byte *ptr; + + while (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink > 0) + { + if (bt_getnode(&n, &vol->ext, n.nd.ndFLink) == -1) + { + goto fail; + } + + n.rnum = 0; + } + + if (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink == 0) + { + break; + } + + ptr = HFS_NODEREC(n, n.rnum); + r_unpackextdata(HFS_RECDATA(ptr), &data); + + markexts(vbm, &data); + + ++n.rnum; + } + } + + /* scavenge the catalog file */ + + if (vol->cat.hdr.bthFNode > 0) + { + if (bt_getnode(&n, &vol->cat, vol->cat.hdr.bthFNode) == -1) + { + goto fail; + } + + n.rnum = 0; + + while (1) + { + CatDataRec data; + const byte *ptr; + + while (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink > 0) + { + if (bt_getnode(&n, &vol->cat, n.nd.ndFLink) == -1) + { + goto fail; + } + + n.rnum = 0; + } + + if (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink == 0) + { + break; + } + + ptr = HFS_NODEREC(n, n.rnum); + r_unpackcatdata(HFS_RECDATA(ptr), &data); + + switch (data.cdrType) + { + case cdrFilRec: + markexts(vbm, &data.u.fil.filExtRec); + markexts(vbm, &data.u.fil.filRExtRec); + + if (data.u.fil.filFlNum > lastcnid) + { + lastcnid = data.u.fil.filFlNum; + } + break; + + case cdrDirRec: + if (data.u.dir.dirDirID > lastcnid) + { + lastcnid = data.u.dir.dirDirID; + } + break; + } + + ++n.rnum; + } + } + + /* count free blocks */ + + for (blks = 0, pt = vol->mdb.drNmAlBlks; pt--; ) + { + if (! BMTST(vbm, pt)) + { + ++blks; + } + } + + if (vol->mdb.drFreeBks != blks) + { +# ifdef DEBUG + fprintf(stderr, "VOL: updating free blocks from %u to %u\n", + vol->mdb.drFreeBks, blks); +# endif + + vol->mdb.drFreeBks = blks; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + /* ensure next CNID is sane */ + + if ((unsigned long) vol->mdb.drNxtCNID <= lastcnid) + { +# ifdef DEBUG + fprintf(stderr, "VOL: updating next CNID from %lu to %lu\n", + vol->mdb.drNxtCNID, lastcnid + 1); +# endif + + vol->mdb.drNxtCNID = lastcnid + 1; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + +# ifdef DEBUG + fprintf(stderr, "VOL: scavenging complete\n"); +# endif + +done: + return 0; + +fail: + return -1; +} diff --git a/libhfs/volume.h b/libhfs/volume.h new file mode 100644 index 0000000..c5ded74 --- /dev/null +++ b/libhfs/volume.h @@ -0,0 +1,67 @@ +/* + * 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 v_init(hfsvol *, int); + +#ifdef CP_NOT_USED +int v_open(hfsvol *, const char *, int); +#endif +int v_callback_open(hfsvol *, oscallback, void*); +int v_flush(hfsvol *); +int v_close(hfsvol *); + +#ifdef CP_NOT_USED +int v_same(hfsvol *, const char *); +#endif +int v_geometry(hfsvol *, int); + +int v_readmdb(hfsvol *); +int v_writemdb(hfsvol *); + +int v_readvbm(hfsvol *); +int v_writevbm(hfsvol *); + +int v_mount(hfsvol *); +int v_dirty(hfsvol *); + +int v_catsearch(hfsvol *, unsigned long, const char *, + CatDataRec *, char *, node *); +int v_extsearch(hfsfile *, unsigned int, ExtDataRec *, node *); + +int v_getthread(hfsvol *, unsigned long, CatDataRec *, node *, int); + +# define v_getdthread(vol, id, thread, np) \ + v_getthread(vol, id, thread, np, cdrThdRec) +# define v_getfthread(vol, id, thread, np) \ + v_getthread(vol, id, thread, np, cdrFThdRec) + +int v_putcatrec(const CatDataRec *, node *); +int v_putextrec(const ExtDataRec *, node *); + +int v_allocblocks(hfsvol *, ExtDescriptor *); +int v_freeblocks(hfsvol *, const ExtDescriptor *); + +int v_resolve(hfsvol **, const char *, CatDataRec *, long *, char *, node *); + +int v_adjvalence(hfsvol *, unsigned long, int, int); +int v_mkdir(hfsvol *, unsigned long, const char *); + +int v_scavenge(hfsvol *); diff --git a/lup.s b/lup.s new file mode 100644 index 0000000..c91ebcc --- /dev/null +++ b/lup.s @@ -0,0 +1,22 @@ + lst on + xc off + xc + xc + org $2000 + +macro mac + lup 4 + lda #$00 + eom + +start pha + + lup 5 +]1 asl + bcs ]1 + --^ + pla + macro + + end + diff --git a/nufxlib/Archive.c b/nufxlib/Archive.c new file mode 100644 index 0000000..99f99ee --- /dev/null +++ b/nufxlib/Archive.c @@ -0,0 +1,1207 @@ +/* + * 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. + * + * Archive structure creation and manipulation. + */ +#include "NufxLibPriv.h" + +#ifdef HAVE_FCNTL_H +# include +#endif +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +/* master header identification */ +static const uint8_t kNuMasterID[kNufileIDLen] = + { 0x4e, 0xf5, 0x46, 0xe9, 0x6c, 0xe5 }; + +/* other identification; can be no longer than kNufileIDLen */ +static const uint8_t kNuBinary2ID[] = + { 0x0a, 0x47, 0x4c }; +static const uint8_t kNuSHKSEAID[] = + { 0xa2, 0x2e, 0x00 }; + +/* + * Offsets to some interesting places in the wrappers. + */ +#define kNuBNYFileSizeLo 8 /* file size in 512-byte blocks (2B) */ +#define kNuBNYFileSizeHi 114 /* ... (2B) */ +#define kNuBNYEOFLo 20 /* file size in bytes (3B) */ +#define kNuBNYEOFHi 116 /* ... (1B) */ +#define kNuBNYDiskSpace 117 /* total space req'd; equiv FileSize (4B) */ +#define kNuBNYFilesToFollow 127 /* (1B) #of files in rest of BNY file */ +#define kNuSEAFunkySize 11938 /* length of archive + 68 (4B?) */ +#define kNuSEAFunkyAdjust 68 /* ... adjustment to "FunkySize" */ +#define kNuSEALength1 11946 /* length of archive (4B?) */ +#define kNuSEALength2 12001 /* length of archive (4B?) */ + +#define kDefaultJunkSkipMax 1024 /* default junk scan size */ + +static void Nu_CloseAndFree(NuArchive* pArchive); + + +/* + * =========================================================================== + * Archive and MasterHeader utility functions + * =========================================================================== + */ + +/* + * Allocate and initialize a new NuArchive structure. + */ +static NuError Nu_NuArchiveNew(NuArchive** ppArchive) +{ + Assert(ppArchive != NULL); + + /* validate some assumptions we make throughout the code */ + Assert(sizeof(int) >= 2); + Assert(sizeof(void*) >= sizeof(NuArchive*)); + + *ppArchive = Nu_Calloc(NULL, sizeof(**ppArchive)); + if (*ppArchive == NULL) + return kNuErrMalloc; + + (*ppArchive)->structMagic = kNuArchiveStructMagic; + + (*ppArchive)->recordIdxSeed = 1000; /* could be a random number */ + (*ppArchive)->nextRecordIdx = (*ppArchive)->recordIdxSeed; + + /* + * Initialize assorted values to defaults. We don't try to do any + * system-specific values here; it's up to the application to decide + * what is most appropriate for the current system. + */ + (*ppArchive)->valIgnoreCRC = false; + #ifdef ENABLE_LZW + (*ppArchive)->valDataCompression = kNuCompressLZW2; + #else + (*ppArchive)->valDataCompression = kNuCompressNone; + #endif + (*ppArchive)->valDiscardWrapper = false; + (*ppArchive)->valEOL = kNuEOLLF; /* non-UNIX apps must override */ + (*ppArchive)->valConvertExtractedEOL = kNuConvertOff; + (*ppArchive)->valOnlyUpdateOlder = false; + (*ppArchive)->valAllowDuplicates = false; + (*ppArchive)->valHandleExisting = kNuMaybeOverwrite; + (*ppArchive)->valModifyOrig = false; + (*ppArchive)->valMimicSHK = false; + (*ppArchive)->valMaskDataless = false; + (*ppArchive)->valStripHighASCII = false; + /* bug: this can't be set by application! */ + (*ppArchive)->valJunkSkipMax = kDefaultJunkSkipMax; + (*ppArchive)->valIgnoreLZW2Len = false; + (*ppArchive)->valHandleBadMac = false; + + (*ppArchive)->messageHandlerFunc = gNuGlobalErrorMessageHandler; + + return kNuErrNone; +} + +/* + * Free up a NuArchive structure and its contents. + */ +static NuError Nu_NuArchiveFree(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + Assert(pArchive->structMagic == kNuArchiveStructMagic); + + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + pArchive->haveToc = false; + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + + Nu_Free(NULL, pArchive->archivePathnameUNI); + Nu_Free(NULL, pArchive->tmpPathnameUNI); + Nu_Free(NULL, pArchive->compBuf); + Nu_Free(NULL, pArchive->lzwCompressState); + Nu_Free(NULL, pArchive->lzwExpandState); + + /* mark it as deceased to prevent further use, then free it */ + pArchive->structMagic = kNuArchiveStructMagic ^ 0xffffffff; + Nu_Free(NULL, pArchive); + + return kNuErrNone; +} + + +/* + * Copy a NuMasterHeader struct. + */ +void Nu_MasterHeaderCopy(NuArchive* pArchive, NuMasterHeader* pDstHeader, + const NuMasterHeader* pSrcHeader) +{ + Assert(pArchive != NULL); + Assert(pDstHeader != NULL); + Assert(pSrcHeader != NULL); + + *pDstHeader = *pSrcHeader; +} + +/* + * Get a pointer to the archive master header (this is an API call). + */ +NuError Nu_GetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader) +{ + if (ppMasterHeader == NULL) + return kNuErrInvalidArg; + + *ppMasterHeader = &pArchive->masterHeader; + + return kNuErrNone; +} + + +/* + * Allocate the general-purpose compression buffer, if needed. + */ +NuError Nu_AllocCompressionBufferIFN(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + + if (pArchive->compBuf != NULL) + return kNuErrNone; + + pArchive->compBuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + if (pArchive->compBuf == NULL) + return kNuErrMalloc; + + return kNuErrNone; +} + + +/* + * Return a unique value. + */ +NuRecordIdx Nu_GetNextRecordIdx(NuArchive* pArchive) +{ + return pArchive->nextRecordIdx++; +} + +/* + * Return a unique value. + */ +NuThreadIdx Nu_GetNextThreadIdx(NuArchive* pArchive) +{ + return pArchive->nextRecordIdx++; /* just use the record counter */ +} + + +/* + * =========================================================================== + * Wrapper (SEA, BXY, BSE) functions + * =========================================================================== + */ + +/* + * Copy the wrapper from the archive file to the temp file. + */ +NuError Nu_CopyWrapperToTemp(NuArchive* pArchive) +{ + NuError err; + + Assert(pArchive->headerOffset); /* no wrapper to copy?? */ + + err = Nu_FSeek(pArchive->archiveFp, 0, SEEK_SET); + BailError(err); + err = Nu_FSeek(pArchive->tmpFp, 0, SEEK_SET); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, + pArchive->archiveFp, pArchive->headerOffset); + BailError(err); + +bail: + return err; +} + + +/* + * Fix up the wrapper. The SEA and BXY headers have some fields + * set according to file length and archive attributes. + * + * Pass in the file pointer that will be written to. Wrappers are + * assumed to start at offset 0. + * + * Wrappers must appear in this order: + * Leading junk + * Binary II + * ShrinkIt SEA (Self-Extracting Archive) + * + * If they didn't, we wouldn't be this far. + * + * I have a Binary II specification, but don't have one for SEA, so I'm + * making educated guesses based on the differences between archives. I'd + * guess some of the SEA weirdness stems from some far-sighted support + * for multiple archives within a single SEA wrapper. + */ +NuError Nu_UpdateWrapper(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + Boolean hasBinary2, hasSea; + uint8_t identBuf[kNufileIDLen]; + uint32_t archiveLen, archiveLen512; + + Assert(pArchive->newMasterHeader.isValid); /* need new crc and len */ + + hasBinary2 = hasSea = false; + + switch (pArchive->archiveType) { + case kNuArchiveNuFX: + goto bail; + case kNuArchiveNuFXInBNY: + hasBinary2 = true; + break; + case kNuArchiveNuFXSelfEx: + hasSea = true; + break; + case kNuArchiveNuFXSelfExInBNY: + hasBinary2 = hasSea = true; + break; + default: + if (pArchive->headerOffset != 0 && + pArchive->headerOffset != pArchive->junkOffset) + { + Nu_ReportError(NU_BLOB, kNuErrNone, "Can't fix the wrapper??"); + err = kNuErrInternal; + goto bail; + } else + goto bail; + } + + err = Nu_FSeek(fp, pArchive->junkOffset, SEEK_SET); + BailError(err); + + if (hasBinary2) { + /* sanity check - make sure it's Binary II */ + Nu_ReadBytes(pArchive, fp, identBuf, kNufileIDLen); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading BNY wrapper"); + goto bail; + } + if (memcmp(identBuf, kNuBinary2ID, sizeof(kNuBinary2ID)) != 0) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone,"Didn't find Binary II wrapper"); + goto bail; + } + + /* archiveLen includes the SEA wrapper, if any, but excludes junk */ + archiveLen = pArchive->newMasterHeader.mhMasterEOF + + (pArchive->headerOffset - pArchive->junkOffset) - + kNuBinary2BlockSize; + archiveLen512 = (archiveLen + 511) / 512; + + err = Nu_FSeek(fp, kNuBNYFileSizeLo - kNufileIDLen, SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (uint16_t)(archiveLen512 & 0xffff)); + + err = Nu_FSeek(fp, kNuBNYFileSizeHi - (kNuBNYFileSizeLo+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (uint16_t)(archiveLen512 >> 16)); + + err = Nu_FSeek(fp, kNuBNYEOFLo - (kNuBNYFileSizeHi+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (uint16_t)(archiveLen & 0xffff)); + Nu_WriteOne(pArchive, fp, (uint8_t)((archiveLen >> 16) & 0xff)); + + err = Nu_FSeek(fp, kNuBNYEOFHi - (kNuBNYEOFLo+3), SEEK_CUR); + BailError(err); + Nu_WriteOne(pArchive, fp, (uint8_t)(archiveLen >> 24)); + + err = Nu_FSeek(fp, kNuBNYDiskSpace - (kNuBNYEOFHi+1), SEEK_CUR); + BailError(err); + Nu_WriteFour(pArchive, fp, archiveLen512); + + /* probably ought to update "modified when" date/time field */ + + /* seek just past end of BNY wrapper */ + err = Nu_FSeek(fp, kNuBinary2BlockSize - (kNuBNYDiskSpace+4), SEEK_CUR); + BailError(err); + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating Binary II wrapper"); + goto bail; + } + } + + if (hasSea) { + /* sanity check - make sure it's SEA */ + Nu_ReadBytes(pArchive, fp, identBuf, kNufileIDLen); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading SEA wrapper"); + goto bail; + } + if (memcmp(identBuf, kNuSHKSEAID, sizeof(kNuSHKSEAID)) != 0) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone, "Didn't find SEA wrapper"); + goto bail; + } + + archiveLen = pArchive->newMasterHeader.mhMasterEOF; + + err = Nu_FSeek(fp, kNuSEAFunkySize - kNufileIDLen, SEEK_CUR); + BailError(err); + Nu_WriteFour(pArchive, fp, archiveLen + kNuSEAFunkyAdjust); + + err = Nu_FSeek(fp, kNuSEALength1 - (kNuSEAFunkySize+4), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (uint16_t)archiveLen); + + err = Nu_FSeek(fp, kNuSEALength2 - (kNuSEALength1+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (uint16_t)archiveLen); + + /* seek past end of SEA wrapper */ + err = Nu_FSeek(fp, kNuSEAOffset - (kNuSEALength2+2), SEEK_CUR); + BailError(err); + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating SEA wrapper"); + goto bail; + } + } + +bail: + return kNuErrNone; +} + + +/* + * Adjust wrapper-induced padding on the archive. + * + * GS/ShrinkIt v1.1 does some peculiar things with SEA (Self-Extracting + * Archive) files. For no apparent reason, it always adds one extra 00 + * byte to the end. When you combine SEA and BXY to make BSE, it will + * leave that extra byte inside the BXY 128-byte padding area, UNLESS + * the archive itself happens to be exactly 128 bytes, in which case + * it throws the pad byte onto the end -- resulting in an archive that + * isn't an exact multiple of 128. + * + * I've chosen to emulate the 1-byte padding "feature" of GSHK, but I'm + * not going to try to emulate the quirky behavior described above. + * + * The SEA pad byte is added first, and then the 128-byte BXY padding + * is considered. In the odd case described above, the file would be + * 127 bytes larger with nufxlib than it is with GSHK. This shouldn't + * require additional disk space to be used, assuming a filesystem block + * size of at least 128 bytes. + */ +NuError Nu_AdjustWrapperPadding(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + Boolean hasBinary2, hasSea; + + hasBinary2 = hasSea = false; + + switch (pArchive->archiveType) { + case kNuArchiveNuFX: + goto bail; + case kNuArchiveNuFXInBNY: + hasBinary2 = true; + break; + case kNuArchiveNuFXSelfEx: + hasSea = true; + break; + case kNuArchiveNuFXSelfExInBNY: + hasBinary2 = hasSea = true; + break; + default: + if (pArchive->headerOffset != 0 && + pArchive->headerOffset != pArchive->junkOffset) + { + Nu_ReportError(NU_BLOB, kNuErrNone, "Can't check the padding??"); + err = kNuErrInternal; + goto bail; + } else + goto bail; + } + + err = Nu_FSeek(fp, 0, SEEK_END); + BailError(err); + + if (hasSea && pArchive->valMimicSHK) { + /* throw on a single pad byte, for no apparent reason whatsoever */ + Nu_WriteOne(pArchive, fp, 0); + } + + if (hasBinary2) { + /* pad out to the next 128-byte boundary */ + long curOffset; + + err = Nu_FTell(fp, &curOffset); + BailError(err); + curOffset -= pArchive->junkOffset; /* don't factor junk into account */ + + DBUG(("+++ BNY needs %ld bytes of padding\n", curOffset & 0x7f)); + if (curOffset & 0x7f) { + int i; + + for (i = kNuBinary2BlockSize - (curOffset & 0x7f); i > 0; i--) + Nu_WriteOne(pArchive, fp, 0); + } + } + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating wrapper padding"); + goto bail; + } + +bail: + return err; +} + + +/* + * =========================================================================== + * Open an archive + * =========================================================================== + */ + +/* + * Read the master header from the archive file. + * + * This also handles skipping the first 128 bytes of a .BXY file and the + * front part of a self-extracting GSHK archive. + * + * We try to provide helpful messages about things that aren't archives, + * but try to stay silent about files that are other types of archives. + * That way, if the application is trying a series of libraries to find + * one that will accept the file, we don't generate spurious complaints. + * + * Since there's a fair possibility that whoever is opening this file is + * also interested in related formats, we try to return a meaningful error + * code for stuff we recognize (especially Binary II). + * + * If at first we don't succeed, we keep trying further along until we + * find something we recognize. We don't want to just scan for the + * NuFile ID, because that might prevent this from working properly with + * SEA archives which push the NuFX start out about 12K. We also wouldn't + * be able to update the BNY/SEA wrappers correctly. So, we inch our way + * along until we find something we recognize or get bored. + * + * On exit, the stream will be positioned just past the master header. + */ +static NuError Nu_ReadMasterHeader(NuArchive* pArchive) +{ + NuError err; + uint16_t crc; + FILE* fp; + NuMasterHeader* pHeader; + Boolean isBinary2 = false; + Boolean isSea = false; + + Assert(pArchive != NULL); + + fp = pArchive->archiveFp; /* saves typing */ + pHeader = &pArchive->masterHeader; + + pArchive->junkOffset = 0; + +retry: + pArchive->headerOffset = pArchive->junkOffset; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + /* may have read fewer than kNufileIDLen; that's okay */ + + if (memcmp(pHeader->mhNufileID, kNuBinary2ID, sizeof(kNuBinary2ID)) == 0) + { + int count; + + /* looks like a Binary II archive, might be BXY or BSE; seek forward */ + err = Nu_SeekArchive(pArchive, fp, kNuBNYFilesToFollow - kNufileIDLen, + SEEK_CUR); + if (err != kNuErrNone) { + err = kNuErrNotNuFX; + /* probably too short to be BNY, so go ahead and whine */ + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like a truncated Binary II archive?"); + goto bail; + } + + /* + * Check "files to follow", so we can be sure this isn't a BNY that + * just happened to have a .SHK as the first file. If it is, then + * any updates to the archive will trash the rest of the BNY files. + */ + count = Nu_ReadOne(pArchive, fp); + if (count != 0) { + err = kNuErrIsBinary2; + /*Nu_ReportError(NU_BLOB, kNuErrNone, + "This is a Binary II archive with %d files in it", count+1);*/ + DBUG(("This is a Binary II archive with %d files in it\n",count+1)); + goto bail; + } + + /* that was last item in BNY header, no need to seek */ + Assert(kNuBNYFilesToFollow == kNuBinary2BlockSize -1); + + isBinary2 = true; + pArchive->headerOffset += kNuBinary2BlockSize; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + } + if (memcmp(pHeader->mhNufileID, kNuSHKSEAID, sizeof(kNuSHKSEAID)) == 0) + { + /* might be GSHK self-extracting; seek forward */ + err = Nu_SeekArchive(pArchive, fp, kNuSEAOffset - kNufileIDLen, + SEEK_CUR); + if (err != kNuErrNone) { + err = kNuErrNotNuFX; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like GS executable, not NuFX"); + goto bail; + } + + isSea = true; + pArchive->headerOffset += kNuSEAOffset; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + } + + if (memcmp(kNuMasterID, pHeader->mhNufileID, kNufileIDLen) != 0) { + /* + * Doesn't look like a NuFX archive. Scan forward and see if we + * can find the start past some leading junk. MacBinary headers + * and chunks of HTTP seem popular on FTP sites. + */ + if ((pArchive->openMode == kNuOpenRO || + pArchive->openMode == kNuOpenRW) && + pArchive->junkOffset < (long)pArchive->valJunkSkipMax) + { + pArchive->junkOffset++; + DBUG(("+++ scanning from offset %ld\n", pArchive->junkOffset)); + err = Nu_SeekArchive(pArchive, fp, pArchive->junkOffset, SEEK_SET); + BailError(err); + goto retry; + } + + err = kNuErrNotNuFX; + + if (isBinary2) { + err = kNuErrIsBinary2; + /*Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like Binary II, not NuFX");*/ + DBUG(("Looks like Binary II, not NuFX\n")); + } else if (isSea) + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like GS executable, not NuFX"); + else if (Nu_HeaderIOFailed(pArchive, fp) != kNuErrNone) + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't read enough data, not NuFX?"); + else + Nu_ReportError(NU_BLOB, kNuErrNone, + "Not a NuFX archive? Got 0x%02x%02x%02x%02x%02x%02x...", + pHeader->mhNufileID[0], pHeader->mhNufileID[1], + pHeader->mhNufileID[2], pHeader->mhNufileID[3], + pHeader->mhNufileID[4], pHeader->mhNufileID[5]); + goto bail; + } + + if (pArchive->junkOffset != 0) { + DBUG(("+++ found apparent start of archive at offset %ld\n", + pArchive->junkOffset)); + } + + crc = 0; + pHeader->mhMasterCRC = Nu_ReadTwo(pArchive, fp); + pHeader->mhTotalRecords = Nu_ReadFourC(pArchive, fp, &crc); + pHeader->mhArchiveCreateWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pHeader->mhArchiveModWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pHeader->mhMasterVersion = Nu_ReadTwoC(pArchive, fp, &crc); + Nu_ReadBytesC(pArchive, fp, pHeader->mhReserved1, + kNufileMasterReserved1Len, &crc); + pHeader->mhMasterEOF = Nu_ReadFourC(pArchive, fp, &crc); + Nu_ReadBytesC(pArchive, fp, pHeader->mhReserved2, + kNufileMasterReserved2Len, &crc); + + /* check for errors in any of the above reads */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading master header"); + goto bail; + } + if (pHeader->mhMasterVersion > kNuMaxMHVersion) { + err = kNuErrBadMHVersion; + Nu_ReportError(NU_BLOB, err, "Bad Master Header version %u", + pHeader->mhMasterVersion); + goto bail; + } + + /* compare the CRC */ + if (!pArchive->valIgnoreCRC && crc != pHeader->mhMasterCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, NULL, kNuErrBadMHCRC)) { + err = kNuErrBadMHCRC; + Nu_ReportError(NU_BLOB, err, "Stored MH CRC=0x%04x, calc=0x%04x", + pHeader->mhMasterCRC, crc); + goto bail; + } + } + + /* + * Check for an unusual condition. GS/ShrinkIt appears to update + * the archive structure in the disk file periodically as it writes, + * so it's possible to get an apparently complete archive (with + * correct CRCs in the master and record headers!) that is actually + * only partially written. I did this by accident when archiving a + * 3.5" disk across a slow AppleTalk network. The only obvious + * indication of brain-damage, until you try to unpack the archive, + * seems to be a bogus MasterEOF==48. + * + * Matthew Fischer found some archives that exhibit MasterEOF==0 + * but are otherwise functional, suggesting that there might be a + * version of ShrinkIt that created these without reporting an error. + * One such archive was a disk image with no filename entry, suggesting + * that it was created by an early version of P8 ShrinkIt. + * + * So, we only fail if the EOF equals 48. + */ + if (pHeader->mhMasterEOF == kNuMasterHeaderSize) { + err = kNuErrNoRecords; + Nu_ReportError(NU_BLOB, err, + "Master EOF is %u, archive is probably truncated", + pHeader->mhMasterEOF); + goto bail; + } + + /* + * Set up a few things in the archive structure on our way out. + */ + if (isBinary2) { + if (isSea) + pArchive->archiveType = kNuArchiveNuFXSelfExInBNY; + else + pArchive->archiveType = kNuArchiveNuFXInBNY; + } else { + if (isSea) + pArchive->archiveType = kNuArchiveNuFXSelfEx; + else + pArchive->archiveType = kNuArchiveNuFX; + } + + if (isSea || isBinary2) { + DBUG(("--- Archive isSea=%d isBinary2=%d type=%d\n", + isSea, isBinary2, pArchive->archiveType)); + } + + /*pArchive->origNumRecords = pHeader->mhTotalRecords;*/ + pArchive->currentOffset = pArchive->headerOffset + kNuMasterHeaderSize; + + /*DBUG(("--- GOT: records=%ld, vers=%d, EOF=%ld, type=%d, hdrOffset=%ld\n", + pHeader->mhTotalRecords, pHeader->mhMasterVersion, + pHeader->mhMasterEOF, pArchive->archiveType, pArchive->headerOffset));*/ + + pHeader->isValid = true; + +bail: + return err; +} + + +/* + * Prepare the NuArchive and NuMasterHeader structures for use with a + * newly-created archive. + */ +static void Nu_InitNewArchive(NuArchive* pArchive) +{ + NuMasterHeader* pHeader; + + Assert(pArchive != NULL); + + pHeader = &pArchive->masterHeader; + + memcpy(pHeader->mhNufileID, kNuMasterID, kNufileIDLen); + /*pHeader->mhMasterCRC*/ + pHeader->mhTotalRecords = 0; + Nu_SetCurrentDateTime(&pHeader->mhArchiveCreateWhen); + /*pHeader->mhArchiveModWhen*/ + pHeader->mhMasterVersion = kNuOurMHVersion; + /*pHeader->mhReserved1*/ + pHeader->mhMasterEOF = kNuMasterHeaderSize; + /*pHeader->mhReserved2*/ + + pHeader->isValid = true; + + /* no need to use a temp file for a newly-created archive */ + pArchive->valModifyOrig = true; +} + + +/* + * Open an archive in streaming read-only mode. + */ +NuError Nu_StreamOpenRO(FILE* infp, NuArchive** ppArchive) +{ + NuError err; + NuArchive* pArchive = NULL; + + Assert(infp != NULL); + Assert(ppArchive != NULL); + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenStreamingRO; + pArchive->archiveFp = infp; + pArchive->archivePathnameUNI = strdup("(stream)"); + + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + +bail: + if (err != kNuErrNone) { + if (pArchive != NULL) + (void) Nu_NuArchiveFree(pArchive); + *ppArchive = NULL; + } + return err; +} + + +/* + * Open an archive in non-streaming read-only mode. + */ +NuError Nu_OpenRO(const UNICHAR* archivePathnameUNI, NuArchive** ppArchive) +{ + NuError err; + NuArchive* pArchive = NULL; + FILE* fp = NULL; + + if (archivePathnameUNI == NULL || !strlen(archivePathnameUNI) || + ppArchive == NULL) + { + return kNuErrInvalidArg; + } + + *ppArchive = NULL; + + fp = fopen(archivePathnameUNI, kNuFileOpenReadOnly); + if (fp == NULL) { + Nu_ReportError(NU_BLOB, errno, "Unable to open '%s'", + archivePathnameUNI); + err = kNuErrFileOpen; + goto bail; + } + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenRO; + pArchive->archiveFp = fp; + fp = NULL; + pArchive->archivePathnameUNI = strdup(archivePathnameUNI); + + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + +bail: + if (err != kNuErrNone) { + if (pArchive != NULL) { + (void) Nu_CloseAndFree(pArchive); + *ppArchive = NULL; + } + if (fp != NULL) + fclose(fp); + } + return err; +} + + +/* + * Open a temp file. If "fileName" contains six Xs ("XXXXXX"), it will + * be treated as a mktemp-style template, and modified before use (so + * pass a copy of the string in). + * + * Thought for the day: consider using Win32 SetFileAttributes() to make + * temp files hidden. We will need to un-hide it before rolling it over. + */ +//#define HAVE_MKSTEMP +//#define HAVE_FDOPEN + +static NuError Nu_OpenTempFile(UNICHAR* fileNameUNI, FILE** pFp) +{ + NuArchive* pArchive = NULL; /* dummy for NU_BLOB */ + NuError err = kNuErrNone; + int len; + + /* + * If this is a mktemp-style template, use mktemp or mkstemp to fill in + * the blanks. + * + * BUG: not all implementations of mktemp actually generate a unique + * name. We probably need to do probing here. Some BSD variants like + * to complain about mktemp, since it's generally a bad way to do + * things. + */ + len = strlen(fileNameUNI); + if (len > 6 && strcmp(fileNameUNI + len - 6, "XXXXXX") == 0) { +#if defined(HAVE_MKSTEMP) && defined(HAVE_FDOPEN) + int fd; + + DBUG(("+++ Using mkstemp\n")); + + /* this modifies the template *and* opens the file */ + fd = mkstemp(fileNameUNI); + if (fd < 0) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, kNuErrNone, "mkstemp failed on '%s'", + fileNameUNI); + goto bail; + } + + DBUG(("--- Fd-opening temp file '%s'\n", fileNameUNI)); + *pFp = fdopen(fd, kNuFileOpenReadWriteCreat); + if (*pFp == NULL) { + close(fd); + err = errno ? errno : kNuErrFileOpen; + goto bail; + } + + /* file is open, we're done */ + goto bail; + +#else + char* result; + + DBUG(("+++ Using mktemp\n")); + result = mktemp(fileNameUNI); + if (result == NULL) { + Nu_ReportError(NU_BLOB, kNuErrNone, "mktemp failed on '%s'", + fileNameUNI); + err = kNuErrInternal; + goto bail; + } + + /* now open the filename as usual */ +#endif + } + + DBUG(("--- Opening temp file '%s'\n", fileNameUNI)); + +#if defined(HAVE_FDOPEN) + { + int fd; + + fd = open(fileNameUNI, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0600); + if (fd < 0) { + err = errno ? errno : kNuErrFileOpen; + goto bail; + } + + *pFp = fdopen(fd, kNuFileOpenReadWriteCreat); + if (*pFp == NULL) { + close(fd); + err = errno ? errno : kNuErrFileOpen; + goto bail; + } + } +#else + if (access(fileNameUNI, F_OK) == 0) { + err = kNuErrFileExists; + goto bail; + } + + *pFp = fopen(fileNameUNI, kNuFileOpenReadWriteCreat); + if (*pFp == NULL) { + err = errno ? errno : kNuErrFileOpen; + goto bail; + } +#endif + + +bail: + return err; +} + +/* + * Open an archive in read-write mode, optionally creating it if it doesn't + * exist. + */ +NuError Nu_OpenRW(const UNICHAR* archivePathnameUNI, + const UNICHAR* tmpPathnameUNI, uint32_t flags, NuArchive** ppArchive) +{ + NuError err; + FILE* fp = NULL; + FILE* tmpFp = NULL; + NuArchive* pArchive = NULL; + char* tmpPathDup = NULL; + Boolean archiveExists; + Boolean newlyCreated; + + if (archivePathnameUNI == NULL || !strlen(archivePathnameUNI) || + tmpPathnameUNI == NULL || !strlen(tmpPathnameUNI) || + ppArchive == NULL || (flags & ~(kNuOpenCreat|kNuOpenExcl)) != 0) + { + return kNuErrInvalidArg; + } + + archiveExists = (access(archivePathnameUNI, F_OK) == 0); + + /* + * Open or create archive file. + */ + if (archiveExists) { + if ((flags & kNuOpenCreat) && (flags & kNuOpenExcl)) { + err = kNuErrFileExists; + Nu_ReportError(NU_BLOB, err, "File '%s' exists", + archivePathnameUNI); + goto bail; + } + fp = fopen(archivePathnameUNI, kNuFileOpenReadWrite); + newlyCreated = false; + } else { + if (!(flags & kNuOpenCreat)) { + err = kNuErrFileNotFound; + Nu_ReportError(NU_BLOB, err, "File '%s' not found", + archivePathnameUNI); + goto bail; + } + fp = fopen(archivePathnameUNI, kNuFileOpenReadWriteCreat); + newlyCreated = true; + } + + if (fp == NULL) { + if (errno == EACCES) + err = kNuErrFileAccessDenied; + else + err = kNuErrFileOpen; + Nu_ReportError(NU_BLOB, errno, "Unable to open '%s'", + archivePathnameUNI); + goto bail; + } + + /* + * Treat zero-length files as newly-created archives. + */ + if (archiveExists && !newlyCreated) { + long length; + + err = Nu_GetFileLength(NULL, fp, &length); + BailError(err); + + if (!length) { + DBUG(("--- treating zero-length file as newly created archive\n")); + newlyCreated = true; + } + } + + /* + * Create a temp file. We don't need one for a newly-created archive, + * at least not right away. It's possible the caller could add some + * files, flush the changes, and then want to delete them without + * closing and reopening the archive. + * + * So, create a temp file whether we think we need one or not. Won't + * do any harm, and might save us some troubles later. + */ + tmpPathDup = strdup(tmpPathnameUNI); + BailNil(tmpPathDup); + err = Nu_OpenTempFile(tmpPathDup, &tmpFp); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed opening temp file '%s'", + tmpPathnameUNI); + goto bail; + } + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenRW; + pArchive->newlyCreated = newlyCreated; + pArchive->archivePathnameUNI = strdup(archivePathnameUNI); + pArchive->archiveFp = fp; + fp = NULL; + pArchive->tmpFp = tmpFp; + tmpFp = NULL; + pArchive->tmpPathnameUNI = tmpPathDup; + tmpPathDup = NULL; + + if (archiveExists && !newlyCreated) { + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + } else { + Nu_InitNewArchive(pArchive); + } + +bail: + if (err != kNuErrNone) { + if (pArchive != NULL) { + (void) Nu_CloseAndFree(pArchive); + *ppArchive = NULL; + } + if (fp != NULL) + fclose(fp); + if (tmpFp != NULL) + fclose(tmpFp); + if (tmpPathDup != NULL) + Nu_Free(pArchive, tmpPathDup); + } + return err; +} + + +/* + * =========================================================================== + * Update an archive + * =========================================================================== + */ + +/* + * Write the NuFX master header at the current offset. + */ +NuError Nu_WriteMasterHeader(NuArchive* pArchive, FILE* fp, + NuMasterHeader* pHeader) +{ + NuError err; + long crcOffset; + uint16_t crc; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pHeader != NULL); + Assert(pHeader->isValid); + Assert(pHeader->mhMasterVersion == kNuOurMHVersion); + + crc = 0; + + Nu_WriteBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + err = Nu_FTell(fp, &crcOffset); + BailError(err); + Nu_WriteTwo(pArchive, fp, 0); + Nu_WriteFourC(pArchive, fp, pHeader->mhTotalRecords, &crc); + Nu_WriteDateTimeC(pArchive, fp, pHeader->mhArchiveCreateWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pHeader->mhArchiveModWhen, &crc); + Nu_WriteTwoC(pArchive, fp, pHeader->mhMasterVersion, &crc); + Nu_WriteBytesC(pArchive, fp, pHeader->mhReserved1, + kNufileMasterReserved1Len, &crc); + Nu_WriteFourC(pArchive, fp, pHeader->mhMasterEOF, &crc); + Nu_WriteBytesC(pArchive, fp, pHeader->mhReserved2, + kNufileMasterReserved2Len, &crc); + + /* go back and write the CRC (sadly, the seek will flush the stdio buf) */ + pHeader->mhMasterCRC = crc; + err = Nu_FSeek(fp, crcOffset, SEEK_SET); + BailError(err); + Nu_WriteTwo(pArchive, fp, pHeader->mhMasterCRC); + + /* check for errors in any of the above writes */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing master header"); + goto bail; + } + + DBUG(("--- Master header written successfully at %ld (crc=0x%04x)\n", + crcOffset - kNufileIDLen, crc)); + +bail: + return err; +} + + +/* + * =========================================================================== + * Close an archive + * =========================================================================== + */ + +/* + * Close all open files, and free the memory associated with the structure. + * + * If it's a brand-new archive, and we didn't add anything to it, then we + * want to remove the stub archive file. + */ +static void Nu_CloseAndFree(NuArchive* pArchive) +{ + if (pArchive->archiveFp != NULL) { + DBUG(("--- Closing archive\n")); + fclose(pArchive->archiveFp); + pArchive->archiveFp = NULL; + } + + if (pArchive->tmpFp != NULL) { + DBUG(("--- Closing and removing temp file\n")); + fclose(pArchive->tmpFp); + pArchive->tmpFp = NULL; + Assert(pArchive->tmpPathnameUNI != NULL); + if (remove(pArchive->tmpPathnameUNI) != 0) { + Nu_ReportError(NU_BLOB, errno, "Unable to remove temp file '%s'", + pArchive->tmpPathnameUNI); + /* keep going */ + } + } + + if (pArchive->newlyCreated && Nu_RecordSet_IsEmpty(&pArchive->origRecordSet)) + { + DBUG(("--- Newly-created archive unmodified; removing it\n")); + if (remove(pArchive->archivePathnameUNI) != 0) { + Nu_ReportError(NU_BLOB, errno, "Unable to remove archive file '%s'", + pArchive->archivePathnameUNI); + } + } + + Nu_NuArchiveFree(pArchive); +} + +/* + * Flush pending changes to the archive, then close it. + */ +NuError Nu_Close(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + uint32_t flushStatus; + + Assert(pArchive != NULL); + + if (!Nu_IsReadOnly(pArchive)) + err = Nu_Flush(pArchive, &flushStatus); + if (err == kNuErrNone) + Nu_CloseAndFree(pArchive); + else { + DBUG(("--- Close NuFlush status was 0x%4lx\n", flushStatus)); + } + + if (err != kNuErrNone) { + DBUG(("--- Nu_Close returning error %d\n", err)); + } + return err; +} + + +/* + * =========================================================================== + * Delete and replace an archive + * =========================================================================== + */ + +/* + * Delete the archive file, which should already have been closed. + */ +NuError Nu_DeleteArchiveFile(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + Assert(pArchive->archiveFp == NULL); + Assert(pArchive->archivePathnameUNI != NULL); + + return Nu_DeleteFile(pArchive->archivePathnameUNI); +} + +/* + * Rename the temp file on top of the original archive. The temp file + * should be closed, and the archive file should be deleted. + */ +NuError Nu_RenameTempToArchive(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + Assert(pArchive->archiveFp == NULL); + Assert(pArchive->tmpFp == NULL); + Assert(pArchive->archivePathnameUNI != NULL); + Assert(pArchive->tmpPathnameUNI != NULL); + + return Nu_RenameFile(pArchive->tmpPathnameUNI, + pArchive->archivePathnameUNI); +} + diff --git a/nufxlib/ArchiveIO.c b/nufxlib/ArchiveIO.c new file mode 100644 index 0000000..893584b --- /dev/null +++ b/nufxlib/ArchiveIO.c @@ -0,0 +1,403 @@ +/* + * 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. + * + * Functions for reading from and writing to the archive. These are + * specialized functions that deal with byte ordering and CRC computation. + * The functions associated with reading from an archive work equally well + * with streaming archives. + */ +#include "NufxLibPriv.h" + + +/* this makes valgrind and purify happy, at some tiny cost in speed */ +#define CLEAN_INIT =0 +/*#define CLEAN_INIT */ + + +/* + * =========================================================================== + * Read and write + * =========================================================================== + */ + +/* + * Read one byte, optionally computing a CRC. + */ +uint8_t Nu_ReadOneC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc) +{ + int ic; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + + return (uint8_t) ic; +} + +uint8_t Nu_ReadOne(NuArchive* pArchive, FILE* fp) +{ + uint16_t dummyCrc CLEAN_INIT; + return Nu_ReadOneC(pArchive, fp, &dummyCrc); +} + +/* + * Write one byte, optionally computing a CRC. + */ +void Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uint8_t val, uint16_t* pCrc) +{ + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + putc(val, fp); +} + +void Nu_WriteOne(NuArchive* pArchive, FILE* fp, uint8_t val) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_WriteOneC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read two little-endian bytes, optionally computing a CRC. + */ +uint16_t Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc) +{ + int ic1, ic2; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic1 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic1, *pCrc); + ic2 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic2, *pCrc); + + return ic1 | ic2 << 8; +} + +uint16_t Nu_ReadTwo(NuArchive* pArchive, FILE* fp) +{ + uint16_t dummyCrc CLEAN_INIT; + return Nu_ReadTwoC(pArchive, fp, &dummyCrc); +} + + +/* + * Write two little-endian bytes, optionally computing a CRC. + */ +void Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, uint16_t val, uint16_t* pCrc) +{ + int ic1, ic2; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic1 = val & 0xff; + *pCrc = Nu_UpdateCRC16((uint8_t)ic1, *pCrc); + ic2 = val >> 8; + *pCrc = Nu_UpdateCRC16((uint8_t)ic2, *pCrc); + + putc(ic1, fp); + putc(ic2, fp); +} + +void Nu_WriteTwo(NuArchive* pArchive, FILE* fp, uint16_t val) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_WriteTwoC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read four little-endian bytes, optionally computing a CRC. + */ +uint32_t Nu_ReadFourC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc) +{ + int ic1, ic2, ic3, ic4; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic1 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic1, *pCrc); + ic2 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic2, *pCrc); + ic3 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic3, *pCrc); + ic4 = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic4, *pCrc); + + return ic1 | ic2 << 8 | (uint32_t)ic3 << 16 | (uint32_t)ic4 << 24; +} + +uint32_t Nu_ReadFour(NuArchive* pArchive, FILE* fp) +{ + uint16_t dummyCrc CLEAN_INIT; + return Nu_ReadFourC(pArchive, fp, &dummyCrc); +} + + +/* + * Write four little-endian bytes, optionally computing a CRC. + */ +void Nu_WriteFourC(NuArchive* pArchive, FILE* fp, uint32_t val, uint16_t* pCrc) +{ + int ic1, ic2, ic3, ic4; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic1 = val & 0xff; + *pCrc = Nu_UpdateCRC16((uint8_t)ic1, *pCrc); + ic2 = (val >> 8) & 0xff; + *pCrc = Nu_UpdateCRC16((uint8_t)ic2, *pCrc); + ic3 = (val >> 16) & 0xff; + *pCrc = Nu_UpdateCRC16((uint8_t)ic3, *pCrc); + ic4 = val >> 24; + *pCrc = Nu_UpdateCRC16((uint8_t)ic4, *pCrc); + + putc(ic1, fp); + putc(ic2, fp); + putc(ic3, fp); + putc(ic4, fp); +} + +void Nu_WriteFour(NuArchive* pArchive, FILE* fp, uint32_t val) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_WriteFourC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read an 8-byte NuFX Date/Time structure. + * + * I've chosen *not* to filter away the Y2K differences between P8 ShrinkIt + * and GS/ShrinkIt. It's easy enough to deal with, and I figure the less + * messing-with, the better. + */ +NuDateTime Nu_ReadDateTimeC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc) +{ + NuDateTime temp; + int ic; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.second = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.minute = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.hour = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.year = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.day = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.month = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.extra = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + temp.weekDay = ic; + + return temp; +} + +NuDateTime Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, uint16_t* pCrc) +{ + uint16_t dummyCrc CLEAN_INIT; + return Nu_ReadDateTimeC(pArchive, fp, &dummyCrc); +} + + +/* + * Write an 8-byte NuFX Date/Time structure. + */ +void Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime, + uint16_t* pCrc) +{ + int ic; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + ic = dateTime.second; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.minute; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.hour; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.year; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.day; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.month; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.extra; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + ic = dateTime.weekDay; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); +} + +void Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_WriteDateTimeC(pArchive, fp, dateTime, &dummyCrc); +} + + +/* + * Read N bytes from the stream, optionally computing a CRC. + */ +void Nu_ReadBytesC(NuArchive* pArchive, FILE* fp, void* vbuffer, long count, + uint16_t* pCrc) +{ + uint8_t* buffer = vbuffer; + int ic; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + Assert(buffer != NULL); + Assert(count > 0); + + while (count--) { + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + *buffer++ = ic; + } +} + +void Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_ReadBytesC(pArchive, fp, vbuffer, count, &dummyCrc); +} + + +/* + * Write N bytes to the stream, optionally computing a CRC. + */ +void Nu_WriteBytesC(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count, uint16_t* pCrc) +{ + const uint8_t* buffer = vbuffer; + int ic; + + Assert(pArchive != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + Assert(buffer != NULL); + Assert(count > 0); + + while (count--) { + ic = *buffer++; + *pCrc = Nu_UpdateCRC16((uint8_t)ic, *pCrc); + putc(ic, fp); + } +} + +void Nu_WriteBytes(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count) +{ + uint16_t dummyCrc CLEAN_INIT; + Nu_WriteBytesC(pArchive, fp, vbuffer, count, &dummyCrc); +} + + +/* + * =========================================================================== + * General + * =========================================================================== + */ + +/* + * Determine whether the stream completed the last set of operations + * successfully. + */ +NuError Nu_HeaderIOFailed(NuArchive* pArchive, FILE* fp) +{ + if (feof(fp) || ferror(fp)) + return kNuErrFile; + else + return kNuErrNone; +} + + +/* + * Seek around in an archive file. If this is a streaming-mode archive, + * we only allow forward relative seeks, which are emulated with read calls. + * + * The values for "ptrname" are the same as for fseek(). + */ +NuError Nu_SeekArchive(NuArchive* pArchive, FILE* fp, long offset, int ptrname) +{ + if (Nu_IsStreaming(pArchive)) { + Assert(ptrname == SEEK_CUR); + Assert(offset >= 0); + + /* OPT: might be faster to fread a chunk at a time */ + while (offset--) + (void) getc(fp); + + if (ferror(fp) || feof(fp)) + return kNuErrFileSeek; + } else { + if (fseek(fp, offset, ptrname) < 0) + return kNuErrFileSeek; + } + + return kNuErrNone; +} + + +/* + * Rewind an archive to the start of NuFX record data. + * + * Note that rewind(3S) resets the error indication, but this doesn't. + */ +NuError Nu_RewindArchive(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + Assert(!Nu_IsStreaming(pArchive)); + + if (Nu_SeekArchive(pArchive, pArchive->archiveFp, + pArchive->headerOffset + kNuMasterHeaderSize, SEEK_SET) != 0) + return kNuErrFileSeek; + + pArchive->currentOffset = pArchive->headerOffset + kNuMasterHeaderSize; + + return kNuErrNone; +} + diff --git a/nufxlib/Bzip2.c b/nufxlib/Bzip2.c new file mode 100644 index 0000000..6515e21 --- /dev/null +++ b/nufxlib/Bzip2.c @@ -0,0 +1,296 @@ +/* + * 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. + * + * Support for the "bzip2" (BTW+Huffman) algorithm, via "libbz2". + * + * This compression format is totally unsupported on the Apple II. This + * is provided primarily for the benefit of Apple II emulators that want + * a better storage format for disk images than SHK+LZW or a ZIP file. + * + * This code was developed and tested with libz2 version 1.0.2. Visit + * http://sources.redhat.com/bzip2/ for more information. + */ +#include "NufxLibPriv.h" + +#ifdef ENABLE_BZIP2 +#include "bzlib.h" + +#define kBZBlockSize 8 /* use 800K blocks */ +#define kBZVerbosity 1 /* library verbosity level (0-4) */ + + +/* + * Alloc and free functions provided to libbz2. + */ +static void* Nu_bzalloc(void* opaque, int items, int size) +{ + return Nu_Malloc(opaque, items * size); +} +static void Nu_bzfree(void* opaque, void* address) +{ + Nu_Free(opaque, address); +} + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +/* + * Compress "srcLen" bytes from "pStraw" to "fp". + */ +NuError Nu_CompressBzip2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + bz_stream bzstream; + int bzerr; + uint8_t* outbuf = NULL; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(fp != NULL); + Assert(srcLen > 0); + Assert(pDstLen != NULL); + Assert(pCrc != NULL); + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + /* allocate a similarly-sized buffer for the output */ + outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + BailAlloc(outbuf); + + /* + * Initialize the bz2lib stream. + */ + bzstream.bzalloc = Nu_bzalloc; + bzstream.bzfree = Nu_bzfree; + bzstream.opaque = pArchive; + bzstream.next_in = NULL; + bzstream.avail_in = 0; + bzstream.next_out = outbuf; + bzstream.avail_out = kNuGenCompBufSize; + + /* fourth arg is "workFactor"; set to zero for default (30) */ + bzerr = BZ2_bzCompressInit(&bzstream, kBZBlockSize, kBZVerbosity, 0); + if (bzerr != BZ_OK) { + err = kNuErrInternal; + if (bzerr == BZ_CONFIG_ERROR) { + Nu_ReportError(NU_BLOB, err, "error configuring bz2lib"); + } else { + Nu_ReportError(NU_BLOB, err, + "call to BZ2_bzCompressInit failed (bzerr=%d)", bzerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + uint32_t getSize; + int action; + + /* should be able to read a full buffer every time */ + if (bzstream.avail_in == 0 && srcLen) { + getSize = (srcLen > kNuGenCompBufSize) ? kNuGenCompBufSize : srcLen; + DBUG(("+++ reading %ld bytes\n", getSize)); + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "bzip2 read failed"); + goto bz_bail; + } + + srcLen -= getSize; + + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getSize); + + bzstream.next_in = pArchive->compBuf; + bzstream.avail_in = getSize; + } + + if (srcLen == 0) + action = BZ_FINISH; /* tell libbz2 that we're done */ + else + action = BZ_RUN; /* more to come! */ + + bzerr = BZ2_bzCompress(&bzstream, action); + if (bzerr != BZ_RUN_OK && bzerr != BZ_FINISH_OK && bzerr != BZ_STREAM_END) + { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, err, + "libbz2 compress call failed (bzerr=%d)", bzerr); + goto bz_bail; + } + + /* write when we're full or when we're done */ + if (bzstream.avail_out == 0 || + (bzerr == BZ_STREAM_END && bzstream.avail_out != kNuGenCompBufSize)) + { + DBUG(("+++ writing %d bytes\n", + (uint8_t*)bzstream.next_out - outbuf)); + err = Nu_FWrite(fp, outbuf, (uint8_t*)bzstream.next_out - outbuf); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "fwrite failed in bzip2"); + goto bz_bail; + } + + bzstream.next_out = outbuf; + bzstream.avail_out = kNuGenCompBufSize; + } + } while (bzerr != BZ_STREAM_END); + + *pDstLen = bzstream.total_out_lo32; + Assert(bzstream.total_out_hi32 == 0); /* no huge files for us */ + +bz_bail: + BZ2_bzCompressEnd(&bzstream); /* free up any allocated structures */ + +bail: + if (outbuf != NULL) + Nu_Free(NULL, outbuf); + return err; +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* + * Expand from "infp" to "pFunnel". + */ +NuError Nu_ExpandBzip2(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + bz_stream bzstream; + int bzerr; + uint32_t compRemaining; + uint8_t* outbuf; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(infp != NULL); + Assert(pFunnel != NULL); + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + /* allocate a similarly-sized buffer for the output */ + outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + BailAlloc(outbuf); + + compRemaining = pThread->thCompThreadEOF; + + /* + * Initialize the libbz2 stream. + */ + bzstream.bzalloc = Nu_bzalloc; + bzstream.bzfree = Nu_bzfree; + bzstream.opaque = pArchive; + bzstream.next_in = NULL; + bzstream.avail_in = 0; + bzstream.next_out = outbuf; + bzstream.avail_out = kNuGenCompBufSize; + + /* third arg is "small" (set nonzero to reduce mem) */ + bzerr = BZ2_bzDecompressInit(&bzstream, kBZVerbosity, 0); + if (bzerr != BZ_OK) { + err = kNuErrInternal; + if (bzerr == BZ_CONFIG_ERROR) { + Nu_ReportError(NU_BLOB, err, "error configuring libbz2"); + } else { + Nu_ReportError(NU_BLOB, err, + "call to BZ2_bzDecompressInit failed (bzerr=%d)", bzerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + uint32_t getSize; + + /* read as much as we can */ + if (bzstream.avail_in == 0) { + getSize = (compRemaining > kNuGenCompBufSize) ? + kNuGenCompBufSize : compRemaining; + DBUG(("+++ reading %ld bytes (%ld left)\n", getSize, + compRemaining)); + + err = Nu_FRead(infp, pArchive->compBuf, getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "bzip2 read failed"); + goto bz_bail; + } + + compRemaining -= getSize; + + bzstream.next_in = pArchive->compBuf; + bzstream.avail_in = getSize; + } + + /* uncompress the data */ + bzerr = BZ2_bzDecompress(&bzstream); + if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, err, + "libbz2 decompress call failed (bzerr=%d)", bzerr); + goto bz_bail; + } + + /* write every time there's anything (buffer will usually be full) */ + if (bzstream.avail_out != kNuGenCompBufSize) { + DBUG(("+++ writing %d bytes\n", + (uint8_t*) bzstream.next_out - outbuf)); + err = Nu_FunnelWrite(pArchive, pFunnel, outbuf, + (uint8_t*)bzstream.next_out - outbuf); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "write failed in bzip2"); + goto bz_bail; + } + + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, outbuf, + (uint8_t*) bzstream.next_out - outbuf); + + bzstream.next_out = outbuf; + bzstream.avail_out = kNuGenCompBufSize; + } + } while (bzerr == BZ_OK); + + Assert(bzerr == BZ_STREAM_END); /* other errors should've been caught */ + + Assert(bzstream.total_out_hi32 == 0); /* no huge files for us */ + + if (bzstream.total_out_lo32 != pThread->actualThreadEOF) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "size mismatch on expanded bzip2 file (%d vs %ld)", + bzstream.total_out_lo32, pThread->actualThreadEOF); + goto bz_bail; + } + +bz_bail: + BZ2_bzDecompressEnd(&bzstream); /* free up any allocated structures */ + +bail: + if (outbuf != NULL) + Nu_Free(NULL, outbuf); + return err; +} + +#endif /*ENABLE_BZIP2*/ diff --git a/nufxlib/CMakeLists.txt b/nufxlib/CMakeLists.txt new file mode 100644 index 0000000..17922fe --- /dev/null +++ b/nufxlib/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 3.0) + +set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(PROJECT_NAME nufx) +set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) +project(${PROJECT_NAME}) +#set (CMAKE_VERBOSE_MAKEFILE "1") + + +#set(ALL_DEFINES " " ) + +set (ALL_DEFINES "\ +-DSTDC_HEADERS=1 -DHAVE_FDOPEN=1 -DHAVE_FTRUNCATE=1 -DHAVE_LOCALTIME_R=1 -DHAVE_MEMMOVE=1 \ +-DHAVE_MKDIR=1 -DHAVE_MKSTEMP=1 -DHAVE_MKTIME=1 -DHAVE_SNPRINTF=1 -DHAVE_STRCASECMP=1 -DHAVE_STRNCASECMP=1 -DHAVE_STRERROR=1 \ +-DHAVE_STRTOUL=1 -DHAVE_TIMELOCAL=1 -DHAVE_VSNPRINTF=1 -DHAVE_FCNTL_H=1 -DHAVE_MALLOC_H=1 -DHAVE_STDLIB_H=1 -DHAVE_SYS_STAT_H=1 \ +-DHAVE_SYS_TIME_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_UTIME_H=1 -DSPRINTF_RETURNS_INT=1 -DSNPRINTF_DECLARED=1 \ +-DVSNPRINTF_DECLARED=1 -DENABLE_SQ=1 -DENABLE_LZW=1 -DENABLE_LZC=1 -DENABLE_DEFLATE=1 \ +" +) + +#message("${ALL_DEFINES}") + +#set(ALL_DEFINES "&" ) + +set(DEBUG_OPT "-Wall -D_DEBUG -DDEBUG -O0 -g3 ${ALL_DEFINES} " ) +#message("${DEBUG_OPT}") +set(RELEASE_OPT "-Wall -O3 ${ALL_DEFINES} " ) +#message("${RELEASE_OPT}") + +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) + + + +set (SOURCE +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 +) + +include_directories(BEFORE + ${PROJECT_ROOT} +) + +add_library( ${PROJECT_NAME} SHARED ${SOURCE}) +add_library( ${PROJECT_NAME}_static STATIC ${SOURCE}) + +target_link_libraries ( +${PROJECT_NAME} +) + + + diff --git a/nufxlib/COPYING-LIB b/nufxlib/COPYING-LIB new file mode 100644 index 0000000..d27a925 --- /dev/null +++ b/nufxlib/COPYING-LIB @@ -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. + diff --git a/nufxlib/ChangeLog.txt b/nufxlib/ChangeLog.txt new file mode 100644 index 0000000..628bb6f --- /dev/null +++ b/nufxlib/ChangeLog.txt @@ -0,0 +1,310 @@ +2017/09/21 ***** v3.1.0 shipped ***** + +2016/01/11 fadden + - Fix handling of disk images (broken by previous change). + +2015/12/26 fadden + - Fix handling of entries with missing threads. + - Improve handling of Mac OS X file type attributes. + +2015/01/09 ***** v3.0.0 shipped ***** + +2015/01/03 fadden + - Mac OS X: replace Carbon FinderInfo calls with BSD xattr. + - Mac OS X: fix resource fork naming. + - Mac OS X: disable use of native resource forks. + +2015/01/02 fadden + - Distinguish Unicode and Mac OS Roman strings. + +2014/12/22 fadden + - Source code cleanup. + +2014/10/30 ***** v2.2.2 shipped ***** + +2014/10/28 fadden + - Switched from CVS on sourceforge to github. + - Updated configure scripts and makefiles. + +2007/02/19 ***** v2.2.0 shipped ***** + +2007/02/19 fadden + - Auto-detect and handle "bad Mac" archives. + - Switched from LGPL to BSD license. + +2006/12/02 fadden + - Check for overrun when unpacking RLE. + +2006/02/18 ***** v2.1.1 shipped ***** + +2006/02/18 fadden + - Correct a wayward assert. (Changing the filetype of a file from an + HFS disk, which has zero-length data fork, falsely triggered the + assert.) + +2005/09/17 ***** v2.1.0 shipped ***** + +2005/09/17 fadden + - Added "kNuValIgnoreLZW2Len" flag, which enables NuLib2 to handle + archives created by an unknown but badly broken program. + - Fixed build for gcc v4.0. + +2004/10/11 ***** v2.0.3 shipped ***** + +2004/09/25 fadden + - Fixed: attempting to add files after deleting *all* entries in an + archive would fail. + - Removed use of a "ushort" from NufxLib.h. + +2004/09/20 fadden + - Corrected behavior after flush when original archive can't be + deleted. + +2004/09/09 fadden + - Added header offset and junk offset to NuGetAttr. + +2004/08/22 fadden + - Fixed obscure bug when recompressing a GSHK-added zero-length file + when "fake threads" is enabled. + +2004/03/10 ***** v2.0.2 shipped ***** + +2004/03/09 fadden + - Set access permissions based on umask when extracting a "locked" + file. My thanks to Matthew Fischer for sending a patch. + - Reject archives with a MasterEOF == 48, not <= 48. There are + some otherwise valid archives created by an old version of ShrinkIt + that have MasterEOF==0. + +2003/10/16 ***** v2.0.1 shipped ***** + +2003/10/16 fadden + - Added workaround for bad HFS option lists created by GSHK. + - Added junk-skipping feature. Up to 1024 bytes of crud (e.g. + MacBinary headers or HTTP remnants) will be searched for evidence + of an archive. + +2003/06/19 sheppy + - Added support for resource forks and file and aux types when built + for Mac OS X. + +2003/03/18 ***** v2.0.0 shipped ***** + +2003/03/10 fadden + - Added support for automatic high-ASCII text stripping. + +2003/02/23 fadden + - Added test-twirl to samples. + +2003/02/22 fadden + - Turn off EOL conversion when extracting disk images. + - Added NuTestRecord(). + +2003/02/18 fadden + - Added "original pathname" fields to NuFileDetails and NuErrorStatus. + - Changed callback setters to return NuCallback instead of NuError. + - Switched to case-sensitive filename comparisons. + +2003/02/08 fadden + - Upped version to v2.0.0. + - Changed DataSource API. Removed "doClose" and added an optional + callback function that handles releasing of resources. Necessary + to make Win32 DLLs work right with unsuspecting apps. + - Changed DataSource "copy" function to use refcounting. Still + not quite right, but it'll do for now. Memory leaks in DataSource + handling appear to be fixed. (I love valgrind.) + +2003/01/10 fadden + - Added version numbers to header. + - Added kNuValueMaskThreadless to control handling of "threadless" + records. Now records without threads can be silently "fixed" so + the application does need to handle them specially. + +2002/12/06 fadden + - Made changes to allow NufxLib to be built as a Win32 DLL. + +2002/10/20 ***** v1.1.0 shipped ***** + +2002/10/10 fadden + - changed behavior so that deleting all records is allowed + +2002/10/09 fadden + - added support for "bzip2" compression via libbz2 + - added ability to selectively disable compression methods + - added "-m" flag to samples/launder so you can specify compression + +2002/09/30 fadden + - added support for "deflate" compression via zlib + +2002/09/27 fadden + - added support for 12-bit and 16-bit LZC (UNIX compress) + +2002/09/26 fadden + - added support for SQueezed files (both compress and expand) + +2002/09/23 fadden + - ran the code through valgrind; found and fixed some minor bugs + +2002/09/20 fadden + - pulled the sources out and started fiddling with them again + - changed hard tabs to spaces + +2000/05/22 ***** v1.0.1 shipped ***** + +2000/05/22 fadden + - added workaround for buggy 140K DOS3.3 GSHK images + +2000/05/18 ***** v1.0.0 shipped ***** + +2000/05/18 fadden + - updated version information to indicate final release + +2000/03/25 ***** v0.6.1 shipped ***** + +2000/03/25 fadden + - Sheppy says Mac OS X PPC v1.02 and v1.2 work with minor SysDefs tweak + +2000/03/05 ***** v0.6.0 (beta) shipped ***** + +2000/03/05 fadden + - modified NuOpenRW to call mktemp or mkstemp if tmpPath looks like + a template + - removed DEBUG_MSGS from default CFLAGS + - updated version information to indicate beta release + +2000/02/24 ***** v0.5.1 shipped ***** + +2000/02/20 changes from Scott Blackman + - portability fixes for DJGPP under Win95 + +2000/02/17 changes from Devin Reade + - portability fixes for BSD, AIX, and others + +2000/02/09 ***** v0.5.0 (alpha) shipped ***** + +2000/02/08 fadden + - tweaked the BeOS/PPC config around a little + - deleted some commas to make "gcc -pendantic" happy + +2000/02/06 fadden + - include @CFLAGS@ in case somebody wants to override them + +2000/02/06 ***** v0.4.0b shipped ***** + +2000/02/06 fadden + - added "install-shared" make target + - portability fixes for HP/UX + - configure.in test for presence of snprintf/vsnprintf declarations + +2000/02/06 ***** v0.4.0a shipped ***** + +2000/02/06 fadden + - massaged configure.in for BeOS, and added some type casts for mwerks + +2000/02/06 ***** v0.4.0 shipped ***** + +2000/02/06 fadden + - added value range checking to Nu_SetValue + +2000/02/05 fadden + - finished "test-basic" + - added an "install" target to copy libnufx and NufxLib.h + - added "mkinstalldirs" + - fixed a memory leak in NuTest + - made several implicit typecasts explicit for Visual C++'s benefit + - renamed MiscStuff's replacement function to "Nu_function" + - use "rb" or "wb" as fopen arg in sample code for Win32 + +2000/02/04 fadden + - wrote a fair piece of "test-basic" + - added "stickyErr" to "toBuffer" data sink so we can catch overruns + +2000/02/02 fadden + - minor changes to get it working under Win32 (Visual C++ 6.0) + - added --enable-dmalloc to configuration + - instead of constantly allocating 16K buffers, use pArchive->compBuf + - ignore DataSink convertEOL value when doExpand is false + +2000/02/01 fadden + - added system-specific PATH_SEP define for samples (imgconv, exerciser) + - set the pathname in ErrorStatus for CRC failures + +2000/01/31 fadden + - fixed a typo causing zero-byte GSHK-damaged files to report CRC errors + - added support for DOS-ordered 2MG images to "imgconv" + +2000/01/29 ***** v0.3.0 shipped ***** + +2000/01/29 fadden + - renamed "tests" to "samples" + - changed library version to x.y.z format (major, minor, bug-fix) + - added DEBUG_VERBOSE define, took some stuff out of DEBUG_MSGS + +2000/01/28 fadden + - make the Skip result work when an input file can't be opened + - don't allow leading fssep chars in AddRecord + - don't treat a multi-file BNY that happens to have a ShrinkIt archive + in the first slot as a BXY + - added "-t" flag (write to temp) to "launder" + - in OpenReadWrite, treat zero-length archive files as newly-created + - added workaround for GSHK's zero-byte data fork bug + +2000/01/26 fadden + - added status result flags to NuFlush + - dropped kNuAbortAll and added kNuIgnore + - implemented kNuValueIgnoreCRC + - update the storageType whenever we change the record + +2000/01/25 fadden + - don't remove the temp file if the rename fails + - Nu_ReportError now optionally uses a callback instead of stderr + - pass NuArchive* and all the trimmings into Nu_ReportError so we can + do the callback thing; required adding arguments to lots of places + - clearly labeled BailError output as debug-only, then replaced most + of the BailErrorQuiet calls with BailError + - added global error message for when pArchive doesn't exist (e.g. Open) + +2000/01/24 fadden + - added args to "launder", and made it work right with 0-length threads + - reject disk image threads that aren't a valid size + - in NuFlush, recognize when a "copy" set hasn't had any changes made + - AddThread no longer makes a copy of the DataSource + +2000/01/24 ***** v0.2 shipped ***** + +2000/01/23 fadden + - added "sec" (Set ErrorHandler Callback) to exerciser + - wrote "launder" test program + - made "doExpand" option on data sinks work + +2000/01/22 fadden + - added OnlyUpdateOlder attribute and implemented for add and extract + - made HandleExisting work for AddFile/AddRecord + - AddThread's validation now blocks data and control threads in same + record + - AddFile and AddRecord now use same validation function as AddThread + +2000/01/20 fadden + - added Eric Shepherd's BeOS shared lib stuff to configure.in + - restructed the progress updater, and made it work when adding files + +2000/01/19 fadden + - normalized SysDefs.h, changing UNIX to UNIX_LIKE and defining for BeOS + - added "shared" target to makefile + - added BeOS stuff to autoconf setup + +2000/01/17 fadden + - fixed Makefile issue preventing "tests" from working with old GNU make + - fixed Lzw.c problem fouling up SunOS gcc v2.5.8 + - discovered "<" vs "<=" flapping in GSHK, which I can't Mimic + - fixed option list dump in debug print + - properly return from all Malloc errors; abort is now debug-only again + - lots of BeOS/Metrowerks "it's not gcc" changes from Eric Shepherd + +2000/01/17 ***** v0.1 shipped ***** + +(much time passes) + +mid-1998 fadden + - work begins + diff --git a/nufxlib/Charset.c b/nufxlib/Charset.c new file mode 100644 index 0000000..9ab10c7 --- /dev/null +++ b/nufxlib/Charset.c @@ -0,0 +1,553 @@ +/* + * NuFX archive manipulation library + * Copyright (C) 2014 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. + * + * Miscellaneous NufxLib utility functions. + */ +#include "NufxLibPriv.h" + +/* + * Convert Mac OS Roman to Unicode. Mapping comes from: + * + * http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT + * + * We use the "Control Pictures" block for the control characters + * (0x00-0x1f, 0x7f --> 0x2400-0x241f, 0x2421). This is a bit nicer + * than embedding control characters in filenames. + */ +static const uint16_t gMORToUnicode[256] = { + /*0x00*/ 0x2400, // [control] NULL + /*0x01*/ 0x2401, // [control] START OF HEADING + /*0x02*/ 0x2402, // [control] START OF TEXT + /*0x03*/ 0x2403, // [control] END OF TEXT + /*0x04*/ 0x2404, // [control] END OF TRANSMISSION + /*0x05*/ 0x2405, // [control] ENQUIRY + /*0x06*/ 0x2406, // [control] ACKNOWLEDGE + /*0x07*/ 0x2407, // [control] BELL + /*0x08*/ 0x2408, // [control] BACKSPACE + /*0x09*/ 0x2409, // [control] HORIZONTAL TABULATION + /*0x0a*/ 0x240a, // [control] LINE FEED + /*0x0b*/ 0x240b, // [control] VERTICAL TABULATION + /*0x0c*/ 0x240c, // [control] FORM FEED + /*0x0d*/ 0x240d, // [control] CARRIAGE RETURN + /*0x0e*/ 0x240e, // [control] SHIFT OUT + /*0x0f*/ 0x240f, // [control] SHIFT IN + /*0x10*/ 0x2410, // [control] DATA LINK ESCAPE + /*0x11*/ 0x2411, // [control] DEVICE CONTROL ONE + /*0x12*/ 0x2412, // [control] DEVICE CONTROL TWO + /*0x13*/ 0x2413, // [control] DEVICE CONTROL THREE + /*0x14*/ 0x2414, // [control] DEVICE CONTROL FOUR + /*0x15*/ 0x2415, // [control] NEGATIVE ACKNOWLEDGE + /*0x16*/ 0x2416, // [control] SYNCHRONOUS IDLE + /*0x17*/ 0x2417, // [control] END OF TRANSMISSION BLOCK + /*0x18*/ 0x2418, // [control] CANCEL + /*0x19*/ 0x2419, // [control] END OF MEDIUM + /*0x1a*/ 0x241a, // [control] SUBSTITUTE + /*0x1b*/ 0x241b, // [control] ESCAPE + /*0x1c*/ 0x241c, // [control] FILE SEPARATOR + /*0x1d*/ 0x241d, // [control] GROUP SEPARATOR + /*0x1e*/ 0x241e, // [control] RECORD SEPARATOR + /*0x1f*/ 0x241f, // [control] UNIT SEPARATOR + /*0x20*/ 0x0020, // SPACE + /*0x21*/ 0x0021, // EXCLAMATION MARK + /*0x22*/ 0x0022, // QUOTATION MARK + /*0x23*/ 0x0023, // NUMBER SIGN + /*0x24*/ 0x0024, // DOLLAR SIGN + /*0x25*/ 0x0025, // PERCENT SIGN + /*0x26*/ 0x0026, // AMPERSAND + /*0x27*/ 0x0027, // APOSTROPHE + /*0x28*/ 0x0028, // LEFT PARENTHESIS + /*0x29*/ 0x0029, // RIGHT PARENTHESIS + /*0x2A*/ 0x002A, // ASTERISK + /*0x2B*/ 0x002B, // PLUS SIGN + /*0x2C*/ 0x002C, // COMMA + /*0x2D*/ 0x002D, // HYPHEN-MINUS + /*0x2E*/ 0x002E, // FULL STOP + /*0x2F*/ 0x002F, // SOLIDUS + /*0x30*/ 0x0030, // DIGIT ZERO + /*0x31*/ 0x0031, // DIGIT ONE + /*0x32*/ 0x0032, // DIGIT TWO + /*0x33*/ 0x0033, // DIGIT THREE + /*0x34*/ 0x0034, // DIGIT FOUR + /*0x35*/ 0x0035, // DIGIT FIVE + /*0x36*/ 0x0036, // DIGIT SIX + /*0x37*/ 0x0037, // DIGIT SEVEN + /*0x38*/ 0x0038, // DIGIT EIGHT + /*0x39*/ 0x0039, // DIGIT NINE + /*0x3A*/ 0x003A, // COLON + /*0x3B*/ 0x003B, // SEMICOLON + /*0x3C*/ 0x003C, // LESS-THAN SIGN + /*0x3D*/ 0x003D, // EQUALS SIGN + /*0x3E*/ 0x003E, // GREATER-THAN SIGN + /*0x3F*/ 0x003F, // QUESTION MARK + /*0x40*/ 0x0040, // COMMERCIAL AT + /*0x41*/ 0x0041, // LATIN CAPITAL LETTER A + /*0x42*/ 0x0042, // LATIN CAPITAL LETTER B + /*0x43*/ 0x0043, // LATIN CAPITAL LETTER C + /*0x44*/ 0x0044, // LATIN CAPITAL LETTER D + /*0x45*/ 0x0045, // LATIN CAPITAL LETTER E + /*0x46*/ 0x0046, // LATIN CAPITAL LETTER F + /*0x47*/ 0x0047, // LATIN CAPITAL LETTER G + /*0x48*/ 0x0048, // LATIN CAPITAL LETTER H + /*0x49*/ 0x0049, // LATIN CAPITAL LETTER I + /*0x4A*/ 0x004A, // LATIN CAPITAL LETTER J + /*0x4B*/ 0x004B, // LATIN CAPITAL LETTER K + /*0x4C*/ 0x004C, // LATIN CAPITAL LETTER L + /*0x4D*/ 0x004D, // LATIN CAPITAL LETTER M + /*0x4E*/ 0x004E, // LATIN CAPITAL LETTER N + /*0x4F*/ 0x004F, // LATIN CAPITAL LETTER O + /*0x50*/ 0x0050, // LATIN CAPITAL LETTER P + /*0x51*/ 0x0051, // LATIN CAPITAL LETTER Q + /*0x52*/ 0x0052, // LATIN CAPITAL LETTER R + /*0x53*/ 0x0053, // LATIN CAPITAL LETTER S + /*0x54*/ 0x0054, // LATIN CAPITAL LETTER T + /*0x55*/ 0x0055, // LATIN CAPITAL LETTER U + /*0x56*/ 0x0056, // LATIN CAPITAL LETTER V + /*0x57*/ 0x0057, // LATIN CAPITAL LETTER W + /*0x58*/ 0x0058, // LATIN CAPITAL LETTER X + /*0x59*/ 0x0059, // LATIN CAPITAL LETTER Y + /*0x5A*/ 0x005A, // LATIN CAPITAL LETTER Z + /*0x5B*/ 0x005B, // LEFT SQUARE BRACKET + /*0x5C*/ 0x005C, // REVERSE SOLIDUS + /*0x5D*/ 0x005D, // RIGHT SQUARE BRACKET + /*0x5E*/ 0x005E, // CIRCUMFLEX ACCENT + /*0x5F*/ 0x005F, // LOW LINE + /*0x60*/ 0x0060, // GRAVE ACCENT + /*0x61*/ 0x0061, // LATIN SMALL LETTER A + /*0x62*/ 0x0062, // LATIN SMALL LETTER B + /*0x63*/ 0x0063, // LATIN SMALL LETTER C + /*0x64*/ 0x0064, // LATIN SMALL LETTER D + /*0x65*/ 0x0065, // LATIN SMALL LETTER E + /*0x66*/ 0x0066, // LATIN SMALL LETTER F + /*0x67*/ 0x0067, // LATIN SMALL LETTER G + /*0x68*/ 0x0068, // LATIN SMALL LETTER H + /*0x69*/ 0x0069, // LATIN SMALL LETTER I + /*0x6A*/ 0x006A, // LATIN SMALL LETTER J + /*0x6B*/ 0x006B, // LATIN SMALL LETTER K + /*0x6C*/ 0x006C, // LATIN SMALL LETTER L + /*0x6D*/ 0x006D, // LATIN SMALL LETTER M + /*0x6E*/ 0x006E, // LATIN SMALL LETTER N + /*0x6F*/ 0x006F, // LATIN SMALL LETTER O + /*0x70*/ 0x0070, // LATIN SMALL LETTER P + /*0x71*/ 0x0071, // LATIN SMALL LETTER Q + /*0x72*/ 0x0072, // LATIN SMALL LETTER R + /*0x73*/ 0x0073, // LATIN SMALL LETTER S + /*0x74*/ 0x0074, // LATIN SMALL LETTER T + /*0x75*/ 0x0075, // LATIN SMALL LETTER U + /*0x76*/ 0x0076, // LATIN SMALL LETTER V + /*0x77*/ 0x0077, // LATIN SMALL LETTER W + /*0x78*/ 0x0078, // LATIN SMALL LETTER X + /*0x79*/ 0x0079, // LATIN SMALL LETTER Y + /*0x7A*/ 0x007A, // LATIN SMALL LETTER Z + /*0x7B*/ 0x007B, // LEFT CURLY BRACKET + /*0x7C*/ 0x007C, // VERTICAL LINE + /*0x7D*/ 0x007D, // RIGHT CURLY BRACKET + /*0x7E*/ 0x007E, // TILDE + /*0x7f*/ 0x2421, // [control] DELETE + /*0x80*/ 0x00C4, // LATIN CAPITAL LETTER A WITH DIAERESIS + /*0x81*/ 0x00C5, // LATIN CAPITAL LETTER A WITH RING ABOVE + /*0x82*/ 0x00C7, // LATIN CAPITAL LETTER C WITH CEDILLA + /*0x83*/ 0x00C9, // LATIN CAPITAL LETTER E WITH ACUTE + /*0x84*/ 0x00D1, // LATIN CAPITAL LETTER N WITH TILDE + /*0x85*/ 0x00D6, // LATIN CAPITAL LETTER O WITH DIAERESIS + /*0x86*/ 0x00DC, // LATIN CAPITAL LETTER U WITH DIAERESIS + /*0x87*/ 0x00E1, // LATIN SMALL LETTER A WITH ACUTE + /*0x88*/ 0x00E0, // LATIN SMALL LETTER A WITH GRAVE + /*0x89*/ 0x00E2, // LATIN SMALL LETTER A WITH CIRCUMFLEX + /*0x8A*/ 0x00E4, // LATIN SMALL LETTER A WITH DIAERESIS + /*0x8B*/ 0x00E3, // LATIN SMALL LETTER A WITH TILDE + /*0x8C*/ 0x00E5, // LATIN SMALL LETTER A WITH RING ABOVE + /*0x8D*/ 0x00E7, // LATIN SMALL LETTER C WITH CEDILLA + /*0x8E*/ 0x00E9, // LATIN SMALL LETTER E WITH ACUTE + /*0x8F*/ 0x00E8, // LATIN SMALL LETTER E WITH GRAVE + /*0x90*/ 0x00EA, // LATIN SMALL LETTER E WITH CIRCUMFLEX + /*0x91*/ 0x00EB, // LATIN SMALL LETTER E WITH DIAERESIS + /*0x92*/ 0x00ED, // LATIN SMALL LETTER I WITH ACUTE + /*0x93*/ 0x00EC, // LATIN SMALL LETTER I WITH GRAVE + /*0x94*/ 0x00EE, // LATIN SMALL LETTER I WITH CIRCUMFLEX + /*0x95*/ 0x00EF, // LATIN SMALL LETTER I WITH DIAERESIS + /*0x96*/ 0x00F1, // LATIN SMALL LETTER N WITH TILDE + /*0x97*/ 0x00F3, // LATIN SMALL LETTER O WITH ACUTE + /*0x98*/ 0x00F2, // LATIN SMALL LETTER O WITH GRAVE + /*0x99*/ 0x00F4, // LATIN SMALL LETTER O WITH CIRCUMFLEX + /*0x9A*/ 0x00F6, // LATIN SMALL LETTER O WITH DIAERESIS + /*0x9B*/ 0x00F5, // LATIN SMALL LETTER O WITH TILDE + /*0x9C*/ 0x00FA, // LATIN SMALL LETTER U WITH ACUTE + /*0x9D*/ 0x00F9, // LATIN SMALL LETTER U WITH GRAVE + /*0x9E*/ 0x00FB, // LATIN SMALL LETTER U WITH CIRCUMFLEX + /*0x9F*/ 0x00FC, // LATIN SMALL LETTER U WITH DIAERESIS + /*0xA0*/ 0x2020, // DAGGER + /*0xA1*/ 0x00B0, // DEGREE SIGN + /*0xA2*/ 0x00A2, // CENT SIGN + /*0xA3*/ 0x00A3, // POUND SIGN + /*0xA4*/ 0x00A7, // SECTION SIGN + /*0xA5*/ 0x2022, // BULLET + /*0xA6*/ 0x00B6, // PILCROW SIGN + /*0xA7*/ 0x00DF, // LATIN SMALL LETTER SHARP S + /*0xA8*/ 0x00AE, // REGISTERED SIGN + /*0xA9*/ 0x00A9, // COPYRIGHT SIGN + /*0xAA*/ 0x2122, // TRADE MARK SIGN + /*0xAB*/ 0x00B4, // ACUTE ACCENT + /*0xAC*/ 0x00A8, // DIAERESIS + /*0xAD*/ 0x2260, // NOT EQUAL TO + /*0xAE*/ 0x00C6, // LATIN CAPITAL LETTER AE + /*0xAF*/ 0x00D8, // LATIN CAPITAL LETTER O WITH STROKE + /*0xB0*/ 0x221E, // INFINITY + /*0xB1*/ 0x00B1, // PLUS-MINUS SIGN + /*0xB2*/ 0x2264, // LESS-THAN OR EQUAL TO + /*0xB3*/ 0x2265, // GREATER-THAN OR EQUAL TO + /*0xB4*/ 0x00A5, // YEN SIGN + /*0xB5*/ 0x00B5, // MICRO SIGN + /*0xB6*/ 0x2202, // PARTIAL DIFFERENTIAL + /*0xB7*/ 0x2211, // N-ARY SUMMATION + /*0xB8*/ 0x220F, // N-ARY PRODUCT + /*0xB9*/ 0x03C0, // GREEK SMALL LETTER PI + /*0xBA*/ 0x222B, // INTEGRAL + /*0xBB*/ 0x00AA, // FEMININE ORDINAL INDICATOR + /*0xBC*/ 0x00BA, // MASCULINE ORDINAL INDICATOR + /*0xBD*/ 0x03A9, // GREEK CAPITAL LETTER OMEGA + /*0xBE*/ 0x00E6, // LATIN SMALL LETTER AE + /*0xBF*/ 0x00F8, // LATIN SMALL LETTER O WITH STROKE + /*0xC0*/ 0x00BF, // INVERTED QUESTION MARK + /*0xC1*/ 0x00A1, // INVERTED EXCLAMATION MARK + /*0xC2*/ 0x00AC, // NOT SIGN + /*0xC3*/ 0x221A, // SQUARE ROOT + /*0xC4*/ 0x0192, // LATIN SMALL LETTER F WITH HOOK + /*0xC5*/ 0x2248, // ALMOST EQUAL TO + /*0xC6*/ 0x2206, // INCREMENT + /*0xC7*/ 0x00AB, // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + /*0xC8*/ 0x00BB, // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + /*0xC9*/ 0x2026, // HORIZONTAL ELLIPSIS + /*0xCA*/ 0x00A0, // NO-BREAK SPACE + /*0xCB*/ 0x00C0, // LATIN CAPITAL LETTER A WITH GRAVE + /*0xCC*/ 0x00C3, // LATIN CAPITAL LETTER A WITH TILDE + /*0xCD*/ 0x00D5, // LATIN CAPITAL LETTER O WITH TILDE + /*0xCE*/ 0x0152, // LATIN CAPITAL LIGATURE OE + /*0xCF*/ 0x0153, // LATIN SMALL LIGATURE OE + /*0xD0*/ 0x2013, // EN DASH + /*0xD1*/ 0x2014, // EM DASH + /*0xD2*/ 0x201C, // LEFT DOUBLE QUOTATION MARK + /*0xD3*/ 0x201D, // RIGHT DOUBLE QUOTATION MARK + /*0xD4*/ 0x2018, // LEFT SINGLE QUOTATION MARK + /*0xD5*/ 0x2019, // RIGHT SINGLE QUOTATION MARK + /*0xD6*/ 0x00F7, // DIVISION SIGN + /*0xD7*/ 0x25CA, // LOZENGE + /*0xD8*/ 0x00FF, // LATIN SMALL LETTER Y WITH DIAERESIS + /*0xD9*/ 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS + /*0xDA*/ 0x2044, // FRACTION SLASH + /*0xDB*/ 0x00A4, // CURRENCY SIGN (was EURO SIGN) + /*0xDC*/ 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + /*0xDD*/ 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + /*0xDE*/ 0xFB01, // LATIN SMALL LIGATURE FI + /*0xDF*/ 0xFB02, // LATIN SMALL LIGATURE FL + /*0xE0*/ 0x2021, // DOUBLE DAGGER + /*0xE1*/ 0x00B7, // MIDDLE DOT + /*0xE2*/ 0x201A, // SINGLE LOW-9 QUOTATION MARK + /*0xE3*/ 0x201E, // DOUBLE LOW-9 QUOTATION MARK + /*0xE4*/ 0x2030, // PER MILLE SIGN + /*0xE5*/ 0x00C2, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX + /*0xE6*/ 0x00CA, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX + /*0xE7*/ 0x00C1, // LATIN CAPITAL LETTER A WITH ACUTE + /*0xE8*/ 0x00CB, // LATIN CAPITAL LETTER E WITH DIAERESIS + /*0xE9*/ 0x00C8, // LATIN CAPITAL LETTER E WITH GRAVE + /*0xEA*/ 0x00CD, // LATIN CAPITAL LETTER I WITH ACUTE + /*0xEB*/ 0x00CE, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX + /*0xEC*/ 0x00CF, // LATIN CAPITAL LETTER I WITH DIAERESIS + /*0xED*/ 0x00CC, // LATIN CAPITAL LETTER I WITH GRAVE + /*0xEE*/ 0x00D3, // LATIN CAPITAL LETTER O WITH ACUTE + /*0xEF*/ 0x00D4, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX + /*0xF0*/ 0xF8FF, // Apple logo + /*0xF1*/ 0x00D2, // LATIN CAPITAL LETTER O WITH GRAVE + /*0xF2*/ 0x00DA, // LATIN CAPITAL LETTER U WITH ACUTE + /*0xF3*/ 0x00DB, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX + /*0xF4*/ 0x00D9, // LATIN CAPITAL LETTER U WITH GRAVE + /*0xF5*/ 0x0131, // LATIN SMALL LETTER DOTLESS I + /*0xF6*/ 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT + /*0xF7*/ 0x02DC, // SMALL TILDE + /*0xF8*/ 0x00AF, // MACRON + /*0xF9*/ 0x02D8, // BREVE + /*0xFA*/ 0x02D9, // DOT ABOVE + /*0xFB*/ 0x02DA, // RING ABOVE + /*0xFC*/ 0x00B8, // CEDILLA + /*0xFD*/ 0x02DD, // DOUBLE ACUTE ACCENT + /*0xFE*/ 0x02DB, // OGONEK + /*0xFF*/ 0x02C7 // CARON +}; + +/* + * Static table, populated on first use. Provides the inverse map. + * + * An entry with 0x00 indicates no conversion. That's incorrect for + * the entry for '\0', but since we're operating on null-terminated + * strings that's never valid anyway. (It's possible for a filename + * to contain 0x2400, but that would translate to 0x00, which we don't + * allow; so it makes more sense to treat it as illegal.) + */ +static uint8_t gUnicodeToMOR[65536] = { 0xff /*indicates not initialized*/ }; + +static void Nu_GenerateUnicodeToMOR(void) +{ + memset(gUnicodeToMOR, 0, sizeof(gUnicodeToMOR)); + + int i; + for (i = 0; i < 256; i++) { + int codePoint = gMORToUnicode[i]; + Assert(codePoint >= 0 && codePoint < 65536); + gUnicodeToMOR[codePoint] = i; + } +} + + +/* + * Converts stringMOR to Unicode, storing the output in bufUNI until it's + * full. Null termination is guaranteed. If the buffer size is zero or + * bufUNI is NULL, no string data is returned. + * + * Returns the number of bytes required to represent stringMOR in Unicode. + */ +size_t Nu_ConvertMORToUNI(const char* stringMOR, UNICHAR* bufUNI, + size_t bufSize) +{ + Assert(stringMOR != 0); + +#ifdef _WIN32 + /* place-holder if we're not using UTF-16 yet */ + Assert(sizeof(UNICHAR) == 1); + size_t morLen = strlen(stringMOR) + 1; + if (bufUNI != NULL && bufSize != 0) { + size_t copyLen = morLen < bufSize ? morLen : bufSize; + memcpy(bufUNI, stringMOR, copyLen); + bufUNI[bufSize-1] = '\0'; + } + return morLen; +#else + /* + * Convert Mac OS Roman to UTF-8. We only output full code points, + * so if only the first byte of a UTF-8 sequence will fit we just + * stop early. + */ + size_t uniLen = 0; + Boolean doOutput = (bufUNI != NULL); + + while (*stringMOR != '\0') { + // ASCII values just "convert" to themselves in this table + uint16_t us = gMORToUnicode[(uint8_t)*stringMOR]; + if (us < 0x80) { + // single byte, no conversion + if (uniLen+1 >= bufSize) { + doOutput = false; + } + if (doOutput) { + bufUNI[uniLen] = (char) us; + } + uniLen++; + } else if (us < 0x7ff) { + // two bytes + if (uniLen+2 >= bufSize) { + doOutput = false; + } + if (doOutput) { + bufUNI[uniLen] = (us >> 6) | 0xc0; + bufUNI[uniLen+1] = (us & 0x3f) | 0x80; + } + uniLen += 2; + } else { + // three bytes + if (uniLen+3 >= bufSize) { + doOutput = false; + } + if (doOutput) { + bufUNI[uniLen] = (us >> 12) | 0xe0; + bufUNI[uniLen+1] = ((us >> 6) & 0x3f) | 0x80; + bufUNI[uniLen+2] = (us & 0x3f) | 0x80; + } + uniLen += 3; + } + + stringMOR++; + } + + // null-terminate + if (doOutput && uniLen < bufSize) { + bufUNI[uniLen] = '\0'; + } + uniLen++; + + return uniLen; +#endif +} + +/* + * Decode a single Unicode code point from a UTF-8 string. This will + * consume 1 to 4 bytes. If an error is detected, only one byte is + * consumed, and the code point value will be 0xDCnn (invalid). + * + * cf. http://en.wikipedia.org/wiki/UTF-8#Sample_code + */ +static uint32_t Nu_DecodeUTF8(const char** pStr) +{ + const uint8_t* str = (const uint8_t*) *pStr; + uint32_t codePoint; + uint32_t uc1, uc2, uc3, uc4; + uc1 = *str++; + + if (uc1 < 0x80) { + // single byte + codePoint = uc1; + } else if (uc1 < 0xc2) { + // illegal: continuation or overlong 2-byte sequence + goto fail; + } else if (uc1 < 0xe0) { + // 2-byte sequence + uc2 = *str++; + if ((uc2 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + codePoint = (uc1 << 6) + uc2 - 0x3080; + } else if (uc1 < 0xf0) { + // 3-byte sequence */ + uc2 = *str++; + if ((uc2 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + if (uc1 == 0xe0 && uc2 < 0xa0) { + goto fail; // overlong + } + uc3 = *str++; + if ((uc3 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + codePoint = (uc1 << 12) + (uc2 << 6) + uc3 - 0xE2080; + } else if (uc1 < 0xf5) { + uc2 = *str++; + if ((uc2 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + if (uc1 == 0xf0 && uc2 < 0x90) { + goto fail; // overlong + } + if (uc1 == 0xf4 && uc2 >= 0x90) { + goto fail; // U+10FFFF + } + uc3 = *str++; + if ((uc3 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + uc4 = *str++; + if ((uc4 & 0xc0) != 0x80) { + goto fail; // not a continuation + } + codePoint = (uc1 << 18) + (uc2 << 12) + (uc3 << 6) + uc4 - 0x3C82080; + } else { + // illegal: > U+10FFFF + goto fail; + } + + *pStr = (const UNICHAR*) str; + return codePoint; + +fail: + (*pStr)++; // advance one char only + return 0xdc00 | uc1; +} + +/* + * Converts stringUNI to Mac OS Roman, storing the output in bufMOR + * until it's full. Null termination is guaranteed. If the buffer + * size is zero or bufMOR is NULL, no string data is returned. + * + * Returns the number of bytes required to represent stringUNI in MOR. + */ +size_t Nu_ConvertUNIToMOR(const UNICHAR* stringUNI, char* bufMOR, + size_t bufSize) +{ + Assert(stringUNI != 0); + +#ifdef _WIN32 + /* + * Place-holder if we're not using UTF-16 yet. This doesn't pass + * tests that check for behavior with non-MOR Unicode values. + */ + Assert(sizeof(UNICHAR) == 1); + size_t uniLen = strlen(stringUNI) + 1; + if (bufMOR != NULL && bufSize != 0) { + size_t copyLen = uniLen < bufSize ? uniLen : bufSize; + memcpy(bufMOR, stringUNI, copyLen); + bufMOR[bufSize-1] = '\0'; + } + return uniLen; +#else + /* + * Convert UTF-8 to Mac OS Roman. If the code point doesn't have + * a valid conversion (either because it's not in the table, or the + * UTF-8 code is damaged) we just insert an ASCII '?'. + */ + if (gUnicodeToMOR[0] == 0xff) { + Nu_GenerateUnicodeToMOR(); + Assert(gUnicodeToMOR[0] != 0xff); + } + + uint32_t codePoint; + size_t morLen = 0; + Boolean doOutput = (bufMOR != NULL); + + while (*stringUNI != '\0') { + codePoint = Nu_DecodeUTF8(&stringUNI); + char mc; + + if (codePoint < 0x80) { + mc = (char) codePoint; + } else if (codePoint < 0xffff) { + // UTF-8 errors come back as 0xDCnn, which has no mapping in table + mc = gUnicodeToMOR[codePoint]; + if (mc == 0x00) { + mc = '?'; + } + } else { + // non-BMP code point + mc = '?'; + } + if (morLen+1 >= bufSize) { + doOutput = false; + } + if (doOutput) { + bufMOR[morLen] = mc; + } + morLen++; + } + + // null-terminate + if (doOutput && morLen < bufSize) { + bufMOR[morLen] = '\0'; + } + morLen++; + + return morLen; +#endif +} + +/* + * Utility function that wraps NuConvertMORToUTF8, allocating a new + * buffer to hold the converted string. The caller must free the result. + * + * Returns NULL if stringMOR is NULL or the conversion fails. + */ +UNICHAR* Nu_CopyMORToUNI(const char* stringMOR) +{ + size_t uniLen; + UNICHAR* uniBuf; + + if (stringMOR == NULL) { + return NULL; + } + + uniLen = Nu_ConvertMORToUNI(stringMOR, NULL, 0); + if (uniLen == (size_t) -1) { + return NULL; + } + uniBuf = (UNICHAR*) Nu_Malloc(NULL, uniLen); + Nu_ConvertMORToUNI(stringMOR, uniBuf, uniLen); + return uniBuf; +} diff --git a/nufxlib/Compress.c b/nufxlib/Compress.c new file mode 100644 index 0000000..d6bfc6a --- /dev/null +++ b/nufxlib/Compress.c @@ -0,0 +1,401 @@ +/* + * 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. + * + * Compress data into an archive. + */ +#include "NufxLibPriv.h" + +/* for ShrinkIt-mimic mode, don't compress files under 512 bytes */ +#define kNuSHKLZWThreshold 512 + + +/* + * "Compress" an uncompressed thread. + */ +static NuError Nu_CompressUncompressed(NuArchive* pArchive, NuStraw* pStraw, + FILE* fp, uint32_t srcLen, uint32_t* pDstLen, uint16_t *pCrc) +{ + NuError err = kNuErrNone; + /*uint8_t* buffer = NULL;*/ + uint32_t count, getsize; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(fp != NULL); + Assert(srcLen > 0); + + *pDstLen = srcLen; /* get this over with */ + + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + if (pCrc != NULL) + *pCrc = kNuInitialThreadCRC; + count = srcLen; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getsize); + BailError(err); + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getsize); + err = Nu_FWrite(fp, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + + +/* + * Compress from a data source to an archive. + * + * All archive-specified fields in "pThread" will be filled in, as will + * "actualThreadEOF". The "nuThreadIdx" and "fileOffset" fields will + * not be modified, and must be specified before calling here. + * + * If "sourceFormat" is uncompressed: + * "targetFormat" will be used to compress the data + * the data source length will be placed into pThread->thThreadEOF + * the compressed size will be placed into pThread->thCompThreadEOF + * the CRC is computed + * + * If "sourceFormat" is compressed: + * the data will be copied without compression (targetFormat is ignored) + * the data source "otherLen" value will be placed into pThread->thThreadEOF + * the data source length will be placed into pThread->thCompThreadEOF + * the CRC is retrieved from Nu_DataSourceGetRawCrc + * + * The actual format used will be placed in pThread->thThreadFormat, and + * the CRC of the uncompressed data will be placed in pThread->thThreadCRC. + * The remaining fields of "pThread", thThreadClass and thThreadKind, will + * be set based on the fields in "pDataSource". + * + * Data will be written to "dstFp", which must be positioned at the + * correct point in the output. The position is expected to match + * pThread->fileOffset. + * + * On exit, the output file will be positioned after the last byte of the + * output. (For a pre-sized buffer, this may not be the desired result.) + */ +NuError Nu_CompressToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, NuThreadFormat sourceFormat, + NuThreadFormat targetFormat, NuProgressData* pProgressData, FILE* dstFp, + NuThread* pThread) +{ + NuError err; + long origOffset; + NuStraw* pStraw = NULL; + NuDataSink* pDataSink = NULL; + uint32_t srcLen = 0, dstLen = 0; + uint16_t threadCrc; + + Assert(pArchive != NULL); + Assert(pDataSource != NULL); + /* okay if pProgressData is NULL */ + Assert(dstFp != NULL); + Assert(pThread != NULL); + + /* remember file offset, so we can back up if compression fails */ + err = Nu_FTell(dstFp, &origOffset); + BailError(err); + Assert(origOffset == pThread->fileOffset); /* can get rid of ftell? */ + + /* fill in some thread fields */ + threadCrc = kNuInitialThreadCRC; + + pThread->thThreadClass = NuThreadIDGetClass(threadID); + pThread->thThreadKind = NuThreadIDGetKind(threadID); + pThread->actualThreadEOF = (uint32_t)-1; + /* nuThreadIdx and fileOffset should already be set */ + + /* + * Get the input length. For "buffer" and "fp" sources, this is just + * a value passed in. For "file" sources, this is the length of the + * file on disk. The file should already have been opened successfully + * by the caller. + * + * If the input file is zero bytes long, "store" it uncompressed and + * bail immediately. + * + * (Our desire to store uncompressible data without compression clashes + * with a passing interest in doing CRLF conversions on input data. We + * want to know the length ahead of time, which potentially makes the + * compression code simpler, but prevents us from doing the conversion + * unless we pre-flight the conversion with a separate pass through the + * input file. Of course, it's still possible for the application to + * convert the file into a temp file and add from there, so all is + * not lost.) + */ + srcLen = Nu_DataSourceGetDataLen(pDataSource); + /*DBUG(("+++ input file length is %lu\n", srcLen));*/ + + /* + * Create a "Straw" to slurp the input through and track progress. + */ + err = Nu_StrawNew(pArchive, pDataSource, pProgressData, &pStraw); + BailError(err); + + if (!srcLen) { + /* empty file! */ + if (sourceFormat != kNuThreadFormatUncompressed) { + DBUG(("ODD: empty source is compressed?\n")); + } + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadCRC = threadCrc; + pThread->thThreadEOF = 0; + pThread->thCompThreadEOF = 0; + pThread->actualThreadEOF = 0; + goto done; /* send final progress message */ + } + + if (sourceFormat == kNuThreadFormatUncompressed) { + /* + * Compress the input to the requested target format. + */ + + /* for some reason, GSHK doesn't compress anything under 512 bytes */ + if (pArchive->valMimicSHK && srcLen < kNuSHKLZWThreshold) + targetFormat = kNuThreadFormatUncompressed; + + if (pProgressData != NULL) { + if (targetFormat != kNuThreadFormatUncompressed) + Nu_StrawSetProgressState(pStraw, kNuProgressCompressing); + else + Nu_StrawSetProgressState(pStraw, kNuProgressStoring); + } + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, targetFormat, + srcLen); + BailError(err); + + switch (targetFormat) { + case kNuThreadFormatUncompressed: + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, &threadCrc); + break; + #ifdef ENABLE_SQ + case kNuThreadFormatHuffmanSQ: + err = Nu_CompressHuffmanSQ(pArchive, pStraw, dstFp, srcLen, + &dstLen, &threadCrc); + break; + #endif + #ifdef ENABLE_LZW + case kNuThreadFormatLZW1: + err = Nu_CompressLZW1(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + case kNuThreadFormatLZW2: + err = Nu_CompressLZW2(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + #endif + #ifdef ENABLE_LZC + case kNuThreadFormatLZC12: + err = Nu_CompressLZC12(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + case kNuThreadFormatLZC16: + err = Nu_CompressLZC16(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + #endif + #ifdef ENABLE_DEFLATE + case kNuThreadFormatDeflate: + err = Nu_CompressDeflate(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + #endif + #ifdef ENABLE_BZIP2 + case kNuThreadFormatBzip2: + err = Nu_CompressBzip2(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + #endif + default: + /* should've been blocked in Value.c */ + Assert(0); + err = kNuErrInternal; + goto bail; + } + + BailError(err); + + pThread->thThreadCRC = threadCrc; /* CRC of uncompressed data */ + + if (dstLen < srcLen || + (dstLen == srcLen && targetFormat == kNuThreadFormatUncompressed)) + { + /* got smaller, or we didn't try to compress it; keep it */ + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = dstLen; + pThread->thThreadFormat = targetFormat; + } else { + /* got bigger, store it uncompressed */ + err = Nu_FSeek(dstFp, origOffset, SEEK_SET); + BailError(err); + err = Nu_StrawRewind(pArchive, pStraw); + BailError(err); + if (pProgressData != NULL) + Nu_StrawSetProgressState(pStraw, kNuProgressStoring); + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, + kNuThreadFormatUncompressed, srcLen); + BailError(err); + + DBUG(("--- compression (%d) failed (%ld vs %ld), storing\n", + targetFormat, dstLen, srcLen)); + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, &threadCrc); + BailError(err); + + /* + * This holds so long as the previous attempt at compressing + * computed a CRC on the entire file (i.e. didn't stop early + * when it noticed the output was larger than the input). If + * this is always the case, then we can change "&threadCrc" + * a few lines back to "NULL" and avoid re-computing the CRC. + * If this is not always the case, remove this assert. + */ + Assert(threadCrc == pThread->thThreadCRC); + + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = dstLen; + pThread->thThreadFormat = kNuThreadFormatUncompressed; + } + + } else { + /* + * Copy the already-compressed input. + */ + if (pProgressData != NULL) + Nu_StrawSetProgressState(pStraw, kNuProgressCopying); + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, + kNuThreadFormatUncompressed, srcLen); + BailError(err); + + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, NULL); + BailError(err); + + pThread->thThreadEOF = Nu_DataSourceGetOtherLen(pDataSource); + pThread->thCompThreadEOF = srcLen; + pThread->thThreadFormat = sourceFormat; + pThread->thThreadCRC = Nu_DataSourceGetRawCrc(pDataSource); + } + pThread->actualThreadEOF = pThread->thThreadEOF; + +done: + DBUG(("+++ srcLen=%ld, dstLen=%ld, actual=%ld\n", + srcLen, dstLen, pThread->actualThreadEOF)); + + /* make sure we send a final "success" progress message at 100% */ + if (pProgressData != NULL) { + (void) Nu_StrawSetProgressState(pStraw, kNuProgressDone); + err = Nu_StrawSendProgressUpdate(pArchive, pStraw); + BailError(err); + } + +bail: + (void) Nu_StrawFree(pArchive, pStraw); + (void) Nu_DataSinkFree(pDataSink); + return err; +} + + +/* + * Copy pre-sized data into the archive at the current offset. + * + * All archive-specified fields in "pThread" will be filled in, as will + * "actualThreadEOF". The "nuThreadIdx" and "fileOffset" fields will + * not be modified. + * + * Pre-sized data is always uncompressed, and doesn't have a CRC. This + * will copy the data, and then continue writing zeros to fill out the rest + * of the pre-sized buffer. + */ +NuError Nu_CopyPresizedToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, FILE* dstFp, NuThread* pThread, char** ppSavedCopy) +{ + NuError err = kNuErrNone; + NuStraw* pStraw = NULL; + uint32_t srcLen, bufferLen; + uint32_t count, getsize; + + srcLen = Nu_DataSourceGetDataLen(pDataSource); + bufferLen = Nu_DataSourceGetOtherLen(pDataSource); + if (bufferLen < srcLen) { + /* hey, this won't fit! */ + DBUG(("--- can't fit %lu into buffer of %lu!\n", srcLen, bufferLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + DBUG(("+++ copying %lu into buffer of %lu\n", srcLen, bufferLen)); + + pThread->thThreadClass = NuThreadIDGetClass(threadID); + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadKind = NuThreadIDGetKind(threadID); + pThread->thThreadCRC = 0; /* no CRC on pre-sized stuff */ + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = bufferLen; + pThread->actualThreadEOF = srcLen; + /* nuThreadIdx and fileOffset should already be set */ + + /* + * Prepare to copy the data through a buffer. The "straw" thing + * is a convenient way to deal with the dataSource, even though we + * don't have a progress updater. + */ + err = Nu_StrawNew(pArchive, pDataSource, NULL, &pStraw); + BailError(err); + + count = srcLen; + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getsize); + BailError(err); + err = Nu_FWrite(dstFp, pArchive->compBuf, getsize); + BailError(err); + + if (ppSavedCopy != NULL && *ppSavedCopy == NULL) { + /* + * Grab a copy of the filename for our own use. This assumes + * that the filename fits in kNuGenCompBufSize, which is a + * pretty safe thing to assume. + */ + Assert(threadID == kNuThreadIDFilename); + Assert(count == getsize); + *ppSavedCopy = Nu_Malloc(pArchive, getsize+1); + BailAlloc(*ppSavedCopy); + memcpy(*ppSavedCopy, pArchive->compBuf, getsize); + (*ppSavedCopy)[getsize] = '\0'; /* make sure it's terminated */ + } + + count -= getsize; + } + + /* + * Pad out the rest of the buffer. Could probably do this more + * efficiently through the buffer we've allocated, but these regions + * tend to be either 32 or 200 bytes. + */ + count = bufferLen - srcLen; + while (count--) + Nu_WriteOne(pArchive, dstFp, 0); + +bail: + (void) Nu_StrawFree(pArchive, pStraw); + /*Nu_Free(pArchive, buffer);*/ + return err; +} + diff --git a/nufxlib/Crc16.c b/nufxlib/Crc16.c new file mode 100644 index 0000000..1f92397 --- /dev/null +++ b/nufxlib/Crc16.c @@ -0,0 +1,107 @@ +/* + * 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. + * + * Compute 16-bit CRCs. Depending on the hardware, the table version + * might be slower than the loop computation. + */ +#include "NufxLibPriv.h" + +#define CRC_TAB +#ifdef CRC_TAB +/* + * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. + * NOTE: First srgument must be in range 0 to 255. + * Second argument is referenced twice. + * + * Programmers may incorporate any or all code into their programs, + * giving proper credit within the source. Publication of the + * source routines is permitted so long as proper credit is given + * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, + * Omen Technology. + */ + + +/*#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)*/ +#define updcrc(cp, crc) ( (crctab[((crc >> 8) & 0xFF) ^ cp] ^ (crc << 8)) & 0xFFFF) + + +/* crctab calculated by Mark G. Mendel, Network Systems Corporation */ +const uint16_t gNuCrc16Table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; +#endif + + +/* + * Calculate CRC on a region + * + * A CRC is the result of a mathematical operation based on the + * coefficients of a polynomial when multiplied by X^16 then divided by + * the generator polynomial (X^16 + X^12 + X^5 + 1) using modulo two + * arithmetic. + * + * This routine is a slightly modified verison of one found in: + * _Advanced Programming Techniques for the Apple //gs Toolbox_ + * By Morgan Davis and Dan Gookin (Compute! Publications, Inc.) + * It can either calculate the CRC bit-by-bit or use a table. + * + * Depending on CPU architecture, one may be dramatically faster than + * the other. + */ +uint16_t Nu_CalcCRC16(uint16_t seed, const uint8_t* ptr, int count) +{ + uint16_t CRC = seed; +#ifndef CRC_TAB + int x; +#endif + + do { +#ifndef CRC_TAB + CRC ^= *ptr++ << 8; /* XOR hi-byte of CRC w/dat */ + for (x = 8; x; --x) /* Then, for 8 bit shifts... */ + if (CRC & 0x8000) /* Test hi order bit of CRC */ + CRC = CRC << 1 ^ 0x1021; /* if set, shift & XOR w/$1021 */ + else + CRC <<= 1; /* Else, just shift left once. */ +#else + CRC = Nu_UpdateCRC16(*ptr++, CRC); /* look up new value in table */ +#endif + } while (--count); + + return (CRC); +} + diff --git a/nufxlib/Debug.c b/nufxlib/Debug.c new file mode 100644 index 0000000..8a704f9 --- /dev/null +++ b/nufxlib/Debug.c @@ -0,0 +1,390 @@ +/* + * 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. + * + * Debugging functions. These are omitted from the non-debug build. + */ +#include "NufxLibPriv.h" + +#if defined(DEBUG_MSGS) + + +/* pull a string out of one of the static arrays */ +#define GetStaticString(index, staticArray) ( \ + (index) >= NELEM(staticArray) ? "" : staticArray[index] \ + ) + +/* thread's thread_class */ +static const char* gThreadClassNames[] = { + "message_thread", + "control_thread", + "data_thread", + "filename_thread", +}; + +/* thread's thread_format */ +static const char* gThreadFormatNames[] = { + "uncompressed", + "Huffman Squeeze", + "dynamic LZW/1", + "dynamic LZW/2", + "12-bit LZC", + "16-bit LZC", + "deflate", + "bzip2" +}; + +/* days of the week */ +static const char* gDayNames[] = { + "[ null ]", + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +/* months of the year */ +static const char* gMonths[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +#define kNuDateOutputLen 64 + +/* file_sys_id values */ +static const char* gFileSysIDs[] = { + "Reserved/unknown ($00)", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", + "Apple II Pascal", "Macintosh (HFS)", "Macintosh (MFS)", + "LISA file system", "Apple CP/M", "Reserved 0x09", "MS-DOS", + "High-Sierra", "ISO 9660", "AppleShare" +}; + + +/* + * Convert a DateTime structure into something printable. + * + * The buffer passed in must hold at least kNuDateOutputLen bytes. + * + * Returns "buffer" for the benefit of printf() calls. + */ +static char* Nu_DebugDumpDate(const NuDateTime* pDateTime, char* buffer) +{ + char* cp; + + /* is it valid? */ + if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 || + pDateTime->minute > 59) + { + strcpy(buffer, " "); + goto bail; + } + + /* is it empty? */ + if ((pDateTime->second | pDateTime->minute | pDateTime->hour | + pDateTime->year | pDateTime->day | pDateTime->month | + pDateTime->extra | pDateTime->weekDay) == 0) + { + strcpy(buffer, " [No Date] "); + goto bail; + } + + cp = buffer; + + /* only print weekDay if one was stored */ + if (pDateTime->weekDay) { + if (pDateTime->weekDay < NELEM(gDayNames)) + sprintf(cp, "%s, ", gDayNames[pDateTime->weekDay]); + else + sprintf(cp, "??%d, ", pDateTime->weekDay); + cp += strlen(cp); + } + + sprintf(cp, "%02d-%s-%04d %02d:%02d:%02d", + pDateTime->day+1, gMonths[pDateTime->month], + pDateTime->year < 40 ? pDateTime->year + 2000 : pDateTime->year + 1900, + pDateTime->hour, pDateTime->minute, pDateTime->second); + +bail: + sprintf(buffer + strlen(buffer), " [s%d m%d h%d Y%d D%d M%d x%d w%d]", + pDateTime->second, pDateTime->minute, pDateTime->hour, + pDateTime->year, pDateTime->day, pDateTime->month, pDateTime->extra, + pDateTime->weekDay); + + return buffer; +} + + +/* + * Convert a buffer into a hexadecimal character string. + * + * The result will be 2x the size of the original, +1 for a null byte. + */ +static void ConvertToHexStr(const uint8_t* inBuf, int inLen, char* outBuf) +{ + while (inLen--) { + *outBuf++ = HexConv((*inBuf >> 4) & 0x0f); + *outBuf++ = HexConv(*inBuf & 0x0f); + inBuf++; + } + *outBuf = '\0'; +} + + +/* + * Dump everything we know about pThread. + */ +void Nu_DebugDumpThread(const NuThread* pThread) +{ + static const char* kInd = " "; + NuThreadID threadID; + const char* descr; + + Assert(pThread != NULL); + + printf("%sThreadClass: 0x%04x (%s)\n", kInd, + pThread->thThreadClass, + GetStaticString(pThread->thThreadClass, gThreadClassNames)); + printf("%sThreadFormat: 0x%04x (%s)\n", kInd, + pThread->thThreadFormat, + GetStaticString(pThread->thThreadFormat, gThreadFormatNames)); + + threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind); + switch (threadID) { + case kNuThreadIDOldComment: descr = "old comment"; break; + case kNuThreadIDComment: descr = "comment"; break; + case kNuThreadIDIcon: descr = "icon"; break; + case kNuThreadIDMkdir: descr = "mkdir"; break; + case kNuThreadIDDataFork: descr = "data fork"; break; + case kNuThreadIDDiskImage: descr = "disk image"; break; + case kNuThreadIDRsrcFork: descr = "rsrc fork"; break; + case kNuThreadIDFilename: descr = "filename"; break; + default: descr = ""; break; + } + printf("%sThreadKind: 0x%04x (%s)\n", kInd, + pThread->thThreadKind, descr); + + printf("%sThreadCRC: 0x%04x ThreadEOF: %u CompThreadEOF: %u\n", kInd, + pThread->thThreadCRC, pThread->thThreadEOF, pThread->thCompThreadEOF); + printf("%s*File data offset: %ld actualThreadEOF: %d\n", kInd, + pThread->fileOffset, pThread->actualThreadEOF); +} + +/* + * Dump everything we know about pRecord, including its threads and ThreadMods. + * + * Changes to existing records are made to the "copy" set, not the "orig" + * set. Pass in the "orig" copy in "pRecord", and optionally pass in the + * "copy" set in "pXrefRecord" to glean data from both. + */ +static void Nu_DebugDumpRecord(NuArchive* pArchive, const NuRecord* pRecord, + const NuRecord* pXrefRecord, Boolean isDeleted) +{ + NuError err; /* dummy */ + static const char* kInd = " "; + char dateBuf[kNuDateOutputLen]; + const NuThreadMod* pThreadMod; + const NuThread* pThread; + uint32_t idx; + + Assert(pRecord != NULL); + + /*printf("PTR: pRecord=0x%08lx pXrefRecord=0x%08lx\n", (long) pRecord, + (long) pXrefRecord);*/ + + UNICHAR* filenameUNI = Nu_CopyMORToUNI(pRecord->filenameMOR); + printf("%s%s%sFilename: '%s' (idx=%u)\n", kInd, + isDeleted ? "[DEL] " : "", + pXrefRecord != NULL && pXrefRecord->pThreadMods != NULL ? "[MOD] " : "", + filenameUNI == NULL ? "" : filenameUNI, + pRecord->recordIdx); + free(filenameUNI); + printf("%sHeaderID: '%.4s' VersionNumber: 0x%04x HeaderCRC: 0x%04x\n", + kInd, + pRecord->recNufxID, pRecord->recVersionNumber, pRecord->recHeaderCRC); + printf("%sAttribCount: %u TotalThreads: %u\n", kInd, + pRecord->recAttribCount, pRecord->recTotalThreads); + printf("%sFileSysID: %u (%s) FileSysInfo: 0x%04x ('%c')\n", kInd, + pRecord->recFileSysID, + GetStaticString(pRecord->recFileSysID, gFileSysIDs), + pRecord->recFileSysInfo, + NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + /* do something fancy for ProDOS? */ + printf("%sFileType: 0x%08x ExtraType: 0x%08x Access: 0x%08x\n", kInd, + pRecord->recFileType, pRecord->recExtraType, pRecord->recAccess); + printf("%sCreateWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recCreateWhen, dateBuf)); + printf("%sModWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recModWhen, dateBuf)); + printf("%sArchiveWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recArchiveWhen, dateBuf)); + printf("%sStorageType: %u OptionSize: %u FilenameLength: %u\n", kInd, + pRecord->recStorageType, pRecord->recOptionSize, + pRecord->recFilenameLength); + if (pRecord->recOptionSize) { + char* outBuf = Nu_Malloc(pArchive, pRecord->recOptionSize * 2 +1); + BailAlloc(outBuf); + Assert(pRecord->recOptionList != NULL); + ConvertToHexStr(pRecord->recOptionList, pRecord->recOptionSize, outBuf); + printf("%sOptionList: [%s]\n", kInd, outBuf); + Nu_Free(pArchive, outBuf); + } + + printf("%s*ExtraCount: %d RecFileOffset: %ld RecHeaderLength: %d\n", + kInd, + pRecord->extraCount, pRecord->fileOffset, pRecord->recHeaderLength); + + for (idx = 0; idx < pRecord->recTotalThreads; idx++) { + Boolean isFake; + + isFake = (idx >= pRecord->recTotalThreads - pRecord->fakeThreads); + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + printf("%s--Thread #%u (idx=%u)%s\n", kInd, idx, pThread->threadIdx, + isFake ? " [FAKE]" : ""); + Nu_DebugDumpThread(pThread); + } + + if (pXrefRecord != NULL) + pThreadMod = pXrefRecord->pThreadMods; + else + pThreadMod = pRecord->pThreadMods; /* probably empty */ + + if (pThreadMod != NULL) + printf("%s*ThreadMods -----\n", kInd); + while (pThreadMod != NULL) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + printf("%s *-ThreadMod ADD 0x%08x 0x%04x (sourceType=%d)\n", kInd, + pThreadMod->entry.add.threadID, + pThreadMod->entry.add.threadFormat, + Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource)); + break; + case kNuThreadModUpdate: + printf("%s *-ThreadMod UPDATE %6d\n", kInd, + pThreadMod->entry.update.threadIdx); + break; + case kNuThreadModDelete: + printf("%s *-ThreadMod DELETE %6d\n", kInd, + pThreadMod->entry.delete.threadIdx); + break; + case kNuThreadModUnknown: + default: + Assert(0); + printf("%s++ThreadMod UNKNOWN\n", kInd); + break; + } + + pThreadMod = pThreadMod->pNext; + } + + /*printf("%s*TotalLength: %ld TotalCompLength: %ld\n", + kInd, pRecord->totalLength, pRecord->totalCompLength);*/ + printf("%s*TotalCompLength: %u\n", kInd, pRecord->totalCompLength); + printf("\n"); + +bail: + return; +} + +/* + * Dump the records in a RecordSet. + */ +static void Nu_DebugDumpRecordSet(NuArchive* pArchive, + const NuRecordSet* pRecordSet, const NuRecordSet* pXrefSet) +{ + const NuRecord* pRecord; + const NuRecord* pXrefRecord; + Boolean doXref; + long count; + + doXref = false; + pXrefRecord = NULL; + if (pXrefSet != NULL && Nu_RecordSet_GetLoaded(pXrefSet)) { + pXrefRecord = Nu_RecordSet_GetListHead(pXrefSet); + doXref = true; + } + + /* dump every record, if we've loaded them */ + count = Nu_RecordSet_GetNumRecords(pRecordSet); + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + if (pRecord != NULL) { + Assert(count != 0); + while (count--) { + Assert(pRecord != NULL); + + if (pXrefRecord != NULL && + pRecord->recordIdx == pXrefRecord->recordIdx) + { + Nu_DebugDumpRecord(pArchive, pRecord, pXrefRecord, false); + pXrefRecord = pXrefRecord->pNext; + } else { + Nu_DebugDumpRecord(pArchive, pRecord, NULL, doXref); + } + pRecord = pRecord->pNext; + } + } else { + Assert(count == 0); + } +} + +/* + * Dump the master header block. + */ +static void Nu_DebugDumpMH(const NuMasterHeader* pMasterHeader) +{ + static const char* kInd = " "; + char dateBuf1[kNuDateOutputLen]; + + Assert(pMasterHeader != NULL); + + printf("%sNufileID: '%.6s' MasterCRC: 0x%04x TotalRecords: %u\n", kInd, + pMasterHeader->mhNufileID, pMasterHeader->mhMasterCRC, + pMasterHeader->mhTotalRecords); + printf("%sArchiveCreateWhen: %s\n", kInd, + Nu_DebugDumpDate(&pMasterHeader->mhArchiveCreateWhen, dateBuf1)); + printf("%sArchiveModWhen: %s\n", kInd, + Nu_DebugDumpDate(&pMasterHeader->mhArchiveModWhen, dateBuf1)); + printf("%sMasterVersion: %u MasterEOF: %u\n", kInd, + pMasterHeader->mhMasterVersion, pMasterHeader->mhMasterEOF); +} + +/* + * Dump everything we know about pArchive. + * + * This will only print the records that we have seen so far. If the + * application hasn't caused us to scan through all of the records in + * the archive, then this won't be very interesting. This will never + * show any records for streaming-mode archives. + */ +void Nu_DebugDumpAll(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + + printf("*Archive pathname: '%s'\n", pArchive->archivePathnameUNI); + printf("*Archive type: %d\n", pArchive->archiveType); + printf("*Header offset: %ld (junk offset=%ld)\n", + pArchive->headerOffset, pArchive->junkOffset); + printf("*Num records: %u orig, %u copy, %u new\n", + Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet), + Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet), + Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet)); + printf("*NuRecordIdx seed: %u NuRecordIdx next: %u\n", + pArchive->recordIdxSeed, pArchive->nextRecordIdx); + + /* master header */ + Nu_DebugDumpMH(&pArchive->masterHeader); + + printf(" *ORIG record set (x-ref with COPY):\n"); + Nu_DebugDumpRecordSet(pArchive, &pArchive->origRecordSet, + &pArchive->copyRecordSet); + printf(" *NEW record set:\n"); + Nu_DebugDumpRecordSet(pArchive, &pArchive->newRecordSet, NULL); + + if (!Nu_RecordSet_GetLoaded(&pArchive->origRecordSet) && + !Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + { + printf("*** DEBUG: original records not loaded yet? ***\n"); + } + +} + +#endif /*DEBUG_MSGS*/ diff --git a/nufxlib/Deferred.c b/nufxlib/Deferred.c new file mode 100644 index 0000000..c5f293d --- /dev/null +++ b/nufxlib/Deferred.c @@ -0,0 +1,2559 @@ +/* + * 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. + * + * Deferred write handling. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * NuThreadMod functions + * =========================================================================== + */ + +/* + * Alloc and initialize a new "add" ThreadMod. + * + * Caller is allowed to dispose of the data source, as this makes a copy. + * + * NOTE: threadFormat is how you want the data to be compressed. The + * threadFormat passed to DataSource describes the source data. + */ +NuError Nu_ThreadModAdd_New(NuArchive* pArchive, NuThreadID threadID, + NuThreadFormat threadFormat, NuDataSource* pDataSource, + NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != NULL); + Assert(pDataSource != NULL); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == NULL) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModAdd; + (*ppThreadMod)->entry.add.used = false; + (*ppThreadMod)->entry.add.threadIdx = Nu_GetNextThreadIdx(pArchive); + (*ppThreadMod)->entry.add.threadID = threadID; + (*ppThreadMod)->entry.add.threadFormat = threadFormat; + (*ppThreadMod)->entry.add.pDataSource = Nu_DataSourceCopy(pDataSource); + + /* decide if this is a pre-sized thread [do we want to do this here??] */ + (*ppThreadMod)->entry.add.isPresized = Nu_IsPresizedThreadID(threadID); + + return kNuErrNone; +} + +/* + * Alloc and initialize a new "update" ThreadMod. + * + * Caller is allowed to dispose of the data source. + */ +NuError Nu_ThreadModUpdate_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != NULL); + Assert(pDataSource != NULL); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == NULL) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModUpdate; + (*ppThreadMod)->entry.update.used = false; + (*ppThreadMod)->entry.update.threadIdx = threadIdx; + (*ppThreadMod)->entry.update.pDataSource = Nu_DataSourceCopy(pDataSource); + + return kNuErrNone; +} + +/* + * Alloc and initialize a new "delete" ThreadMod. + * + * The "threadID" argument is really only needed for filename threads. We + * use it when trying to track how many filename threads we really have. + */ +NuError Nu_ThreadModDelete_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuThreadID threadID, NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != NULL); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == NULL) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModDelete; + (*ppThreadMod)->entry.delete.used = false; + (*ppThreadMod)->entry.delete.threadIdx = threadIdx; + (*ppThreadMod)->entry.delete.threadID = threadID; + + return kNuErrNone; +} + +/* + * Free a single NuThreadMod. + */ +void Nu_ThreadModFree(NuArchive* pArchive, NuThreadMod* pThreadMod) +{ + if (pThreadMod == NULL) + return; + + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + Nu_DataSourceFree(pThreadMod->entry.add.pDataSource); + break; + case kNuThreadModUpdate: + Nu_DataSourceFree(pThreadMod->entry.update.pDataSource); + break; + default: + break; + } + + Nu_Free(pArchive, pThreadMod); +} + + +/* + * Return a threadMod with a matching "threadIdx", if any. Because "add" + * threads can't have a threadIdx that matches an existing thread, this + * will only return updates and deletes. + * + * We don't allow more than one threadMod on the same thread, so we don't + * have to deal with having more than one match. (To be safe, we go + * ahead and do debug-only checks for multiple matches. There shouldn't + * be more than three or four threads per record, so the extra search + * isn't costly.) + * + * Returns "NULL" if nothing found. + */ +NuThreadMod* Nu_ThreadMod_FindByThreadIdx(const NuRecord* pRecord, + NuThreadIdx threadIdx) +{ + NuThreadMod* pThreadMod; + NuThreadMod* pMatch = NULL; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + /* can't happen */ + Assert(pThreadMod->entry.add.threadIdx != threadIdx); + break; + case kNuThreadModUpdate: + if (pThreadMod->entry.update.threadIdx == threadIdx) { + Assert(pMatch == NULL); + pMatch = pThreadMod; + } + break; + case kNuThreadModDelete: + if (pThreadMod->entry.delete.threadIdx == threadIdx) { + Assert(pMatch == NULL); + pMatch = pThreadMod; + } + break; + default: + Assert(0); + /* keep going, I guess */ + } + pThreadMod = pThreadMod->pNext; + } + + return pMatch; +} + + +/* + * =========================================================================== + * ThreadMod list operations + * =========================================================================== + */ + +/* + * Search for an "add" ThreadMod, by threadID. + */ +NuError Nu_ThreadModAdd_FindByThreadID(const NuRecord* pRecord, + NuThreadID threadID, NuThreadMod** ppThreadMod) +{ + NuThreadMod* pThreadMod; + + Assert(pRecord != NULL); + Assert(ppThreadMod != NULL); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + if (pThreadMod->entry.kind != kNuThreadModAdd) + continue; + + if (pThreadMod->entry.add.threadID == threadID) { + *ppThreadMod = pThreadMod; + return kNuErrNone; + } + + pThreadMod = pThreadMod->pNext; + } + + return kNuErrNotFound; +} + + +/* + * Free up the list of NuThreadMods in this record. + */ +void Nu_FreeThreadMods(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThreadMod* pThreadMod; + NuThreadMod* pNext; + + Assert(pRecord != NULL); + pThreadMod = pRecord->pThreadMods; + + if (pThreadMod == NULL) + return; + + while (pThreadMod != NULL) { + pNext = pThreadMod->pNext; + + Nu_ThreadModFree(pArchive, pThreadMod); + pThreadMod = pNext; + } + + pRecord->pThreadMods = NULL; +} + + +/* + * =========================================================================== + * Temporary structure for holding updated thread info + * =========================================================================== + */ + +/* used when constructing a new set of threads */ +typedef struct { + int numThreads; /* max #of threads */ + int nextSlot; /* where the next one goes */ + NuThread* pThreads; /* static-sized array */ +} NuNewThreads; + +/* + * Allocate and initialize a NuNewThreads struct. + */ +static NuError Nu_NewThreads_New(NuArchive* pArchive, + NuNewThreads** ppNewThreads, long numThreads) +{ + NuError err = kNuErrNone; + + *ppNewThreads = Nu_Malloc(pArchive, sizeof(**ppNewThreads)); + BailAlloc(*ppNewThreads); + (*ppNewThreads)->numThreads = numThreads; + (*ppNewThreads)->nextSlot = 0; + (*ppNewThreads)->pThreads = + Nu_Malloc(pArchive, numThreads * sizeof(NuThread)); + BailAlloc((*ppNewThreads)->pThreads); + +bail: + return err; +} + +/* + * Free a NuNewThreads struct. + */ +static void Nu_NewThreads_Free(NuArchive* pArchive, NuNewThreads* pNewThreads) +{ + if (pNewThreads != NULL) { + Nu_Free(pArchive, pNewThreads->pThreads); + Nu_Free(pArchive, pNewThreads); + } +} + +/* + * Returns true if "pNewThreads" has room for another entry, false otherwise. + */ +static Boolean Nu_NewThreads_HasRoom(const NuNewThreads* pNewThreads) +{ + if (pNewThreads->nextSlot < pNewThreads->numThreads) + return true; + else + return false; +} + +/* + * Get the next available slot. The contents of the slot are first + * initialized. + * + * The "next slot" marker is automatically advanced. + */ +static NuThread* Nu_NewThreads_GetNext(NuNewThreads* pNewThreads, + NuArchive* pArchive) +{ + NuThread* pThread; + + pThread = &pNewThreads->pThreads[pNewThreads->nextSlot]; + memset(pThread, 0, sizeof(*pThread)); + + pThread->fileOffset = -1; /* mark as invalid */ + + /* advance slot */ + pNewThreads->nextSlot++; + Assert(pNewThreads->nextSlot <= pNewThreads->numThreads); + + return pThread; +} + +/* + * Return the #of threads we're meant to hold. + */ +static int Nu_NewThreads_GetNumThreads(const NuNewThreads* pNewThreads) +{ + Assert(pNewThreads != NULL); + + return pNewThreads->numThreads; +} + +/* + * Total up the compressed EOFs of all threads. + */ +static uint32_t Nu_NewThreads_TotalCompThreadEOF(NuNewThreads* pNewThreads) +{ + uint32_t compThreadEOF; + int i; + + /* we should be all full up at this point; if not, we have a bug */ + Assert(pNewThreads != NULL); + Assert(pNewThreads->numThreads == pNewThreads->nextSlot); + + compThreadEOF = 0; + for (i = 0; i < pNewThreads->numThreads; i++) + compThreadEOF += pNewThreads->pThreads[i].thCompThreadEOF; + + return compThreadEOF; +} + + +/* + * "Donate" the thread collection to the caller. This returns a pointer + * to the thread array, and then nukes our copy of the pointer. This + * allows us to transfer ownership of the storage to the caller. + */ +static NuThread* Nu_NewThreads_DonateThreads(NuNewThreads* pNewThreads) +{ + NuThread* pThreads = pNewThreads->pThreads; + + pNewThreads->pThreads = NULL; + return pThreads; +} + + +/* + * =========================================================================== + * Archive construction - Record-level functions + * =========================================================================== + */ + +/* + * Copy an entire record (threads and all) from the source archive to the + * current offset in the temp file. + * + * Pass in the record from the *copy* set, not the original. + */ +static NuError Nu_CopyArchiveRecord(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + long offsetAdjust; + long outputOffset; + int i; + + err = Nu_FTell(pArchive->tmpFp, &outputOffset); + BailError(err); + offsetAdjust = outputOffset - pRecord->fileOffset; + + DBUG(("--- Copying record '%s' (curOff=%ld adj=%ld)\n", pRecord->filename, + outputOffset, offsetAdjust)); + + /* seek to the start point in the source file, and copy the whole thing */ + err = Nu_FSeek(pArchive->archiveFp, pRecord->fileOffset, SEEK_SET); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, pArchive->archiveFp, + pRecord->recHeaderLength + pRecord->totalCompLength); + BailError(err); + + /* adjust the file offsets in the record header and in the threads */ + pRecord->fileOffset += offsetAdjust; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + NuThread* pThread = Nu_GetThread(pRecord, i); + + pThread->fileOffset += offsetAdjust; + } + + Assert(outputOffset + pRecord->recHeaderLength + pRecord->totalCompLength == + (uint32_t)ftell(pArchive->tmpFp)); + Assert(pRecord->fileOffset == outputOffset); + +bail: + return err; +} + + +/* + * Count the number of threads that will eventually inhabit this record. + * + * Returns -1 on error. + */ +static NuError Nu_CountEventualThreads(const NuRecord* pRecord, + long* pTotalThreads, long* pFilenameThreads) +{ + const NuThreadMod* pThreadMod; + const NuThread* pThread; + long idx, numThreads, numFilenameThreads; + + /* + * Number of threads is equal to: + * the number of existing threads + * MINUS the number of "delete" threadMods (you can't delete the same + * thread more than once) + * PLUS the number of "add" threadMods + */ + numThreads = pRecord->recTotalThreads; + numFilenameThreads = 0; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + numThreads++; + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) + numFilenameThreads++; + break; + case kNuThreadModDelete: + numThreads--; + if (pThreadMod->entry.delete.threadID == kNuThreadIDFilename) + numFilenameThreads--; + break; + case kNuThreadModUpdate: + break; + default: + Assert(0); + break; + } + + pThreadMod = pThreadMod->pNext; + } + + /* + * If the record has more than one filename thread, we only keep + * the first one, so remove it from our accounting here. It should + * not have been possible to add a new filename thread when an + * existing one was present, so we don't check the threadMods. + */ + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + if (NuGetThreadID(pThread) == kNuThreadIDFilename) + numFilenameThreads++; + } + Assert(numFilenameThreads >= 0); + if (numFilenameThreads > 1) { + DBUG(("--- ODD: found multiple filename threads (%ld)\n", + numFilenameThreads)); + numThreads -= (numFilenameThreads -1); + } + + /* + * Records with no threads should've been screened out already. + */ + if (numThreads <= 0) + return kNuErrInternal; + + *pTotalThreads = numThreads; + *pFilenameThreads = numFilenameThreads; /* [should cap this at 1?] */ + return kNuErrNone; +} + + +/* + * Verify that all of the threads and threadMods in a record have + * been touched. This is done after the record has been written to + * the destination archive, in order to ensure that we don't leave + * anything behind. + * + * All items, including things like duplicate filename threads that + * we ignore, are marked "used" during processing, so we don't need + * to be terribly bright here. + */ +static Boolean Nu_VerifyAllTouched(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThreadMod* pThreadMod; + const NuThread* pThread; + long idx; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + Assert(pThreadMod->entry.generic.used == false || + pThreadMod->entry.generic.used == true); + if (!pThreadMod->entry.generic.used) + return false; + pThreadMod = pThreadMod->pNext; + } + + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + Assert(pThread->used == false || pThread->used == true); + if (!pThread->used) + return false; + } + + return true; +} + + +/* + * Set the threadFilename field of a record to a new value. This does + * not affect the record header filename. + * + * This call should only be made after an "add" or "update" threadMod has + * successfully completed. + * + * "newName" must be allocated storage, Mac OS Roman charset. + */ +static void Nu_SetNewThreadFilename(NuArchive* pArchive, NuRecord* pRecord, + char* newNameMOR) +{ + Assert(pRecord != NULL); + Assert(newNameMOR != NULL); + + Nu_Free(pArchive, pRecord->threadFilenameMOR); + pRecord->threadFilenameMOR = newNameMOR; + pRecord->filenameMOR = pRecord->threadFilenameMOR; +} + +/* + * If this is a disk image, we require that the uncompressed length + * be equal to recExtraType * recStorageType (where recStorageType + * is the block size, usually 512). If they haven't set those to + * appropriate values, we'll set them on their behalf, so long as + * the uncompressed size is a multiple of 512. + */ +static NuError Nu_UpdateDiskImageFields(NuArchive* pArchive, NuRecord* pRecord, + long sourceLen) +{ + NuError err = kNuErrNone; + long actualLen; + + if (pRecord->recStorageType <= 13) + pRecord->recStorageType = 512; + actualLen = pRecord->recExtraType * pRecord->recStorageType; + + if (actualLen != sourceLen) { + /* didn't match, see if we can fix it */ + DBUG(("--- fixing up disk image size\n")); + if ((sourceLen & 0x1ff) == 0) { + pRecord->recStorageType = 512; + pRecord->recExtraType = sourceLen / 512; + } else { + /* oh dear */ + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, kNuErrNone,"disk image size of %ld invalid", + sourceLen); + /* fall through and out */ + } + } + + return err; +} + +/* + * As part of thread construction or in-place updating, handle a single + * "update" threadMod. We have an existing thread, and are replacing + * the contents of it with new data. + * + * "pThread" is a thread from the copy list or a "new" thread (a copy of + * the thread from the "copy" list), and "pThreadMod" is a threadMod that + * effects pThread. + * + * "fp" is a pointer into the archive at the offset where the data is + * to be written. On exit, "fp" will point past the end of the pre-sized + * buffer. + * + * Possible side-effects on "pRecord": threadFilename may be updated. + */ +static NuError Nu_ConstructArchiveUpdate(NuArchive* pArchive, FILE* fp, + NuRecord* pRecord, NuThread* pThread, const NuThreadMod* pThreadMod) +{ + NuError err; + NuDataSource* pDataSource = NULL; + uint32_t sourceLen; + uint32_t threadBufSize; + + /* + * We're going to copy the data out of the data source. Because + * "update" actions only operate on pre-sized chunks, and the data + * is never compressed, this should be straightforward. However, + * we do need to make sure that the data will fit. + * + * I expect these to be small, and it's just a raw data copy, so no + * progress updater is used. + */ + Assert(Nu_IsPresizedThreadID(NuGetThreadID(pThread))); + Assert(pThread->thCompThreadEOF >= pThread->thThreadEOF); + threadBufSize = pThread->thCompThreadEOF; + pDataSource = pThreadMod->entry.update.pDataSource; + Assert(pDataSource != NULL); + + err = Nu_DataSourcePrepareInput(pArchive, pDataSource); + if (err == kNuErrSkipped) { + /* something failed (during file open?), just skip this one */ + DBUG(("--- skipping pre-sized thread update to %ld\n", + pThread->threadIdx)); + err = kNuErrNone; + goto skip_update; + } else if (err != kNuErrNone) + goto bail; + + /* + * Check to see if the data will fit. In some cases we can verify + * the size during the UpdatePresizedThread call, but if it's being + * added from a file we can't tell until now. + * + * We could be nice and give the user a chance to do something about + * this, but frankly the application should have checked the file + * size before handing it to us. + */ + sourceLen = Nu_DataSourceGetDataLen(pDataSource); + if (sourceLen > pThread->thCompThreadEOF) { + err = kNuErrPreSizeOverflow; + Nu_ReportError(NU_BLOB, err, "can't fit %u bytes into %u-byte buffer", + sourceLen, pThread->thCompThreadEOF); + goto bail; + } + + /* + * During an update operation, the user's specification of "otherLen" + * doesn't really matter, because we're not going to change the size + * of the region in the archive. However, this size *is* used by + * the code to figure out how big the buffer should be, and will + * determine where the file pointer ends up when the call returns. + * So, we jam in the "real" value. + */ + Nu_DataSourceSetOtherLen(pDataSource, pThread->thCompThreadEOF); + + if (NuGetThreadID(pThread) == kNuThreadIDFilename) { + /* special handling for filename updates */ + char* savedCopyMOR = NULL; + err = Nu_CopyPresizedToArchive(pArchive, pDataSource, + NuGetThreadID(pThread), fp, pThread, &savedCopyMOR); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread update failed"); + goto bail; + } + Nu_SetNewThreadFilename(pArchive, pRecord, savedCopyMOR); + + } else { + err = Nu_CopyPresizedToArchive(pArchive, pDataSource, + NuGetThreadID(pThread), fp, pThread, NULL); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread update failed"); + goto bail; + } + } + Assert((uint32_t)ftell(fp) == pThread->fileOffset + threadBufSize); + +skip_update: + Nu_DataSourceUnPrepareInput(pArchive, pDataSource); + +bail: + return err; +} + + +/* + * Handle all "add" threadMods in the current record. This is invoked both + * when creating a new record from the "new" list or constructing a + * modified record from the "copy" list. + * + * Writes to either the archiveFp or tmpFp; pass in the correct one, + * properly positioned. + * + * If something goes wrong with one of the "adds", this will return + * immediately with kNuErrSkipped. The caller is expected to abort the + * entire record, so there's no point in continuing to process other + * threads. + * + * Possible side-effects on "pRecord": disk image fields may be revised + * (storage type, extra type), and threadFilename may be updated. + */ +static NuError Nu_HandleAddThreadMods(NuArchive* pArchive, NuRecord* pRecord, + NuThreadID threadID, Boolean doKeepFirstOnly, NuNewThreads* pNewThreads, + FILE* dstFp) +{ + NuError err = kNuErrNone; + + NuProgressData progressData; + NuProgressData* pProgressData; + NuThreadMod* pThreadMod; + NuThread* pNewThread; + UNICHAR* pathnameUNIStorage = NULL; + Boolean foundOne = false; + + /* + * Now find all "add" threadMods with matching threadIDs. Allow + * matching by wildcards, but don't re-use "used" entries. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + !pThreadMod->entry.generic.used && + (pThreadMod->entry.add.threadID == threadID || + threadID == kNuThreadIDWildcard)) + { + DBUG(("+++ found ADD for 0x%08lx\n", pThreadMod->entry.add.threadID)); + pThreadMod->entry.generic.used = true; + + /* if we're adding filename threads, stop after first one */ + /* [shouldn't be able to happen... we only allow one filename!] */ + if (doKeepFirstOnly && foundOne) { + Assert(0); /* can this happen?? */ + continue; + } + foundOne = true; + + if (!Nu_NewThreads_HasRoom(pNewThreads)) { + Assert(0); + err = kNuErrInternal; + goto bail; + } + + /* if this is a data thread, prepare the progress message */ + pProgressData = NULL; + if (NuThreadIDGetClass(pThreadMod->entry.add.threadID) == + kNuThreadClassData) + { + /* + * We're going to show the name as it appears in the + * archive, rather than the name of the file we're + * reading the data out of. We could do this differently + * for a "file" data source, but we might as well be + * consistent. + * + * [Actually, the above remark is bogus. During a bulk add + * there's no other way to recover the original filename. + * Do something different here for data sinks with + * filenames attached. ++ATM 2003/02/17] + */ + pathnameUNIStorage = Nu_CopyMORToUNI(pRecord->filenameMOR); + if (Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource) + == kNuDataSourceFromFile) + { + /* use on-disk filename */ + err = Nu_ProgressDataInit_Compress(pArchive, &progressData, + pRecord, Nu_DataSourceFile_GetPathname( + pThreadMod->entry.add.pDataSource), + pathnameUNIStorage); + } else { + /* use archive filename for both */ + err = Nu_ProgressDataInit_Compress(pArchive, &progressData, + pRecord, pathnameUNIStorage, pathnameUNIStorage); + } + BailError(err); + + /* send initial progress so they see name if "open" fails */ + progressData.state = kNuProgressOpening; + err = Nu_SendInitialProgress(pArchive, &progressData); + BailError(err); + + pProgressData = &progressData; + } + + /* get new thread storage, and init the thread's data offset */ + /* (the threadIdx is set by GetNext) */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + pNewThread->threadIdx = pThreadMod->entry.add.threadIdx; + err = Nu_FTell(dstFp, &pNewThread->fileOffset); + BailError(err); + + /* this returns kNuErrSkipped if user elects to skip */ + err = Nu_DataSourcePrepareInput(pArchive, + pThreadMod->entry.add.pDataSource); + BailError(err); + + /* + * If they're adding a disk image thread, make sure the disk- + * related fields in the record header are correct. + */ + if (pThreadMod->entry.add.threadID == kNuThreadIDDiskImage) { + const NuDataSource* pDataSource = + pThreadMod->entry.add.pDataSource; + uint32_t uncompLen; + + if (Nu_DataSourceGetThreadFormat(pDataSource) == + kNuThreadFormatUncompressed) + { + uncompLen = Nu_DataSourceGetDataLen(pDataSource); + } else { + uncompLen = Nu_DataSourceGetOtherLen(pDataSource); + } + + err = Nu_UpdateDiskImageFields(pArchive, pRecord, uncompLen); + BailError(err); + } + + if (Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource) == + kNuDataSourceFromFile) + { + DBUG(("+++ ADDING from '%s' for '%s' (idx=%ld id=0x%08lx)\n", + Nu_DataSourceFile_GetPathname(pThreadMod->entry.add.pDataSource), + pRecord->filename, + pThreadMod->entry.add.threadIdx, + pThreadMod->entry.add.threadID)); + } else { + DBUG(("+++ ADDING from (type=%d) for '%s' (idx=%ld id=0x%08lx)\n", + Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource), + pRecord->filename, + pThreadMod->entry.add.threadIdx, + pThreadMod->entry.add.threadID)); + } + + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) { + /* filenames are special */ + char* savedCopyMOR = NULL; + + Assert(pThreadMod->entry.add.threadFormat == + kNuThreadFormatUncompressed); + err = Nu_CopyPresizedToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + dstFp, pNewThread, &savedCopyMOR); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "fn thread add failed"); + goto bail; + } + /* NOTE: on failure, "dropRecFilename" is still set. This + doesn't matter though, since we'll either copy the original + record, or abort the entire thing. At any rate, we can't + just clear it, because we've already made space for the + record header, and didn't include the filename in it. */ + + Nu_SetNewThreadFilename(pArchive, pRecord, savedCopyMOR); + + } else if (pThreadMod->entry.add.isPresized) { + /* don't compress, just copy */ + Assert(pThreadMod->entry.add.threadFormat == + kNuThreadFormatUncompressed); + err = Nu_CopyPresizedToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + dstFp, pNewThread, NULL); + /* fall through with err */ + + } else { + /* compress (possibly by just copying) the source to dstFp */ + err = Nu_CompressToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + Nu_DataSourceGetThreadFormat( + pThreadMod->entry.add.pDataSource), + pThreadMod->entry.add.threadFormat, + pProgressData, dstFp, pNewThread); + /* fall through with err */ + } + + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread add failed"); + goto bail; + } + Nu_DataSourceUnPrepareInput(pArchive, + pThreadMod->entry.add.pDataSource); + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + Nu_Free(pArchive, pathnameUNIStorage); + return err; +} + +/* + * Run through the list of threads and threadMods, looking for threads + * with an ID that matches "threadID". When one is found, we take all + * the appropriate steps to get the data into the archive. + * + * This takes into account the ThreadMods, including "delete" (ignore + * existing thread), "update" (use data from threadMod instead of + * existing thread), and "add" (use data from threadMod). + * + * Threads that are used or discarded will have a flag set so that + * future examinations, notably those where "threadID" is a wildcard, + * will ignore them. + * + * Always writes to the temp file. The temp file must be positioned in + * the proper location. + * + * "pRecord" must be from the "copy" data set. + */ +static NuError Nu_ConstructArchiveThreads(NuArchive* pArchive, + NuRecord* pRecord, NuThreadID threadID, Boolean doKeepFirstOnly, + NuNewThreads* pNewThreads) +{ + NuError err = kNuErrNone; + NuThread* pThread; + NuThreadMod* pThreadMod; + Boolean foundOne = false; + NuThread* pNewThread; + int idx; + + /* + * First, find any existing threads that match. If they have a + * "delete" threadMod, ignore them; if they have an "update" threadMod, + * use that instead. + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + DBUG(("+++ THREAD #%d (used=%d)\n", idx, pThread->used)); + if (threadID == kNuThreadIDWildcard || + threadID == NuGetThreadID(pThread)) + { + /* match! */ + DBUG(("+++ MATCH THREAD #%d\n", idx)); + if (pThread->used) + continue; + pThread->used = true; /* no matter what, we're done with this */ + + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + + if (pThreadMod != NULL) { + /* + * The thread has a related ThreadMod. Deal with it. + */ + + pThreadMod->entry.generic.used = true; /* for Assert, later */ + + if (pThreadMod->entry.kind == kNuThreadModDelete) { + /* this is a delete, ignore this thread */ + DBUG(("+++ deleted %ld!\n", pThread->threadIdx)); + continue; + } else if (pThreadMod->entry.kind == kNuThreadModUpdate) { + /* update pre-sized data in place */ + + DBUG(("+++ updating threadIdx=%ld\n", + pThread->threadIdx)); + + /* only one filename per customer */ + /* [does this make sense here??] */ + if (doKeepFirstOnly && foundOne) + continue; + foundOne = true; + + /* add an entry in the new list of threads */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + Nu_CopyThreadContents(pNewThread, pThread); + + /* set the thread's file offset */ + err = Nu_FTell(pArchive->tmpFp, &pNewThread->fileOffset); + BailError(err); + + err = Nu_ConstructArchiveUpdate(pArchive, pArchive->tmpFp, + pRecord, pNewThread, pThreadMod); + BailError(err); + } else { + /* unknown ThreadMod type - this shouldn't happen! */ + Assert(0); + err = kNuErrInternal; + goto bail; + } + } else { + /* + * Thread is unmodified. + */ + + /* only one filename per customer */ + if (doKeepFirstOnly && foundOne) + continue; + foundOne = true; + + /* + * Copy the original data to the new location. Right now, + * pThread->fileOffset has the correct offset for the + * original file, and tmpFp is positioned at the correct + * output offset. We want to seek the source file, replace + * pThread->fileOffset with the *new* offset, and then + * copy the data. + * + * This feels skankier than it really is because we're + * using the thread in the "copy" set for two purposes. + * It'd be cleaner to pass in the thread from the "orig" + * set, but there's really not much value in doing so. + * + * [should this have a progress meter associated?] + */ + DBUG(("+++ just copying threadIdx=%ld\n", + pThread->threadIdx)); + err = Nu_FSeek(pArchive->archiveFp, pThread->fileOffset, + SEEK_SET); + BailError(err); + err = Nu_FTell(pArchive->tmpFp, &pThread->fileOffset); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, + pArchive->archiveFp, pThread->thCompThreadEOF); + BailError(err); + + /* copy an entry over into the replacement thread list */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + Nu_CopyThreadContents(pNewThread, pThread); + } + } + } + + /* no need to check for "add" mods; there can't be one for us */ + if (doKeepFirstOnly && foundOne) + goto bail; + + /* + * Now handle any "add" threadMods. + */ + err = Nu_HandleAddThreadMods(pArchive, pRecord, threadID, doKeepFirstOnly, + pNewThreads, pArchive->tmpFp); + BailError(err); + +bail: + return err; +} + +/* + * Construct a record in the temp file, based on the contents of the + * original. Takes into account "dirty" headers and threadMod changes. + * + * Pass in the record from the *copy* set, not the original. The temp + * file should be positioned at the correct spot. + * + * If something goes wrong, and the user wants to abort the record but + * not the entire operation, we rewind the temp file to the initial + * position. It's not possible to abandon part of a record; either you + * get everything you asked for or nothing at all. We then return + * kNuErrSkipped, which should cause the caller to simply copy the + * previous record. + */ +static NuError Nu_ConstructArchiveRecord(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err; + NuNewThreads* pNewThreads = NULL; + long threadDisp; + long initialOffset, finalOffset; + long numThreads, numFilenameThreads; + int newHeaderSize; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + DBUG(("--- Reconstructing '%s'\n", pRecord->filename)); + + err = Nu_FTell(pArchive->tmpFp, &initialOffset); + BailError(err); + Assert(initialOffset != 0); + + /* + * Figure out how large the record header is. This requires + * measuring the static elements, the not-so-static elements like + * the GS/OS option list and perhaps the filename, and getting an + * accurate count of the number of threads. + * + * Since we're going to keep any option lists and extra junk stored in + * the header originally, the size of the new base record header is + * equal to the original recAttribCount. The attribute count conveniently + * does *not* include the filename, so if we've moved it out of the + * record header and into a thread, it won't affect us here. + */ + err = Nu_CountEventualThreads(pRecord, &numThreads, &numFilenameThreads); + BailError(err); + Assert(numThreads > 0); /* threadless records should already be gone */ + if (numThreads <= 0) { + err = kNuErrInternal; + goto bail; + } + + /* + * Handle filename deletion. + */ + if (!numFilenameThreads && pRecord->threadFilenameMOR != NULL) { + /* looks like a previously existing filename thread got removed */ + DBUG(("--- Dropping thread filename '%s'\n", + pRecord->threadFilenameMOR)); + if (pRecord->filenameMOR == pRecord->threadFilenameMOR) + pRecord->filenameMOR = NULL; /* don't point at freed memory! */ + Nu_Free(pArchive, pRecord->threadFilenameMOR); + pRecord->threadFilenameMOR = NULL; + + /* I don't think this is possible, but check it anyway */ + if (pRecord->filenameMOR == NULL && pRecord->recFilenameMOR != NULL && + !pRecord->dropRecFilename) + { + DBUG(("--- HEY, how did this happen?\n")); + pRecord->filenameMOR = pRecord->recFilenameMOR; + } + } + if (pRecord->filenameMOR == NULL) + pRecord->filenameMOR = kNuDefaultRecordName; + + /* + * Make a hole, including the header filename if we're not dropping it. + * + * This ignores fake vs. non-fake threads, because once we're done + * writing they're all "real". + */ + newHeaderSize = pRecord->recAttribCount + numThreads * kNuThreadHeaderSize; + if (!pRecord->dropRecFilename) + newHeaderSize += pRecord->recFilenameLength; + + DBUG(("+++ new header size = %d\n", newHeaderSize)); + err = Nu_FSeek(pArchive->tmpFp, newHeaderSize, SEEK_CUR); + BailError(err); + + /* + * It is important to arrange the threads in a specific order. For + * example, we can have trouble doing a streaming archive read if the + * filename isn't the first thread the collection. It's prudent to + * mimic GSHK's behavior, so we act to ensure that things appear in + * the following order: + * + * (1) filename thread + * (2) comment thread(s) + * (3) data thread with data fork + * (4) data thread with disk image + * (5) data thread with rsrc fork + * (6) everything else + * + * If we ended up with two filename threads (perhaps some other aberrant + * application created the archive; we certainly wouldn't do that), we + * keep the first one. We're more lenient on propagating strange + * multiple comment and data thread situations, even though the + * thread updating mechanism in this library won't necessarily allow + * such situations. + */ + + err = Nu_NewThreads_New(pArchive, &pNewThreads, numThreads); + BailError(err); + + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDFilename, + true, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDComment, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDDataFork, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDDiskImage, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDRsrcFork, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDWildcard, + false, pNewThreads); + BailError(err); + + /* + * Perform some sanity checks. + */ + Assert(!Nu_NewThreads_HasRoom(pNewThreads)); + + /* verify that all threads and threadMods have been touched */ + if (!Nu_VerifyAllTouched(pArchive, pRecord)) { + err = kNuErrInternal; + Assert(0); + goto bail; + } + + /* verify that file displacement is where it should be */ + threadDisp = (long)Nu_NewThreads_TotalCompThreadEOF(pNewThreads); + err = Nu_FTell(pArchive->tmpFp, &finalOffset); + BailError(err); + Assert(finalOffset > initialOffset); + if (finalOffset - (initialOffset + newHeaderSize) != threadDisp) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "ERROR: didn't end up where expected (%ld %ld %ld)", + initialOffset, finalOffset, threadDisp); + err = kNuErrInternal; + Assert(0); + goto bail; + } + + /* + * Free existing Threads and ThreadMods, and move the list from + * pNewThreads over. + */ + Nu_Free(pArchive, pRecord->pThreads); + Nu_FreeThreadMods(pArchive, pRecord); + pRecord->pThreads = Nu_NewThreads_DonateThreads(pNewThreads); + pRecord->recTotalThreads = Nu_NewThreads_GetNumThreads(pNewThreads); + + /* + * Now, seek back and write the record header. + */ + err = Nu_FSeek(pArchive->tmpFp, initialOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, pArchive->tmpFp); + BailError(err); + + Assert(newHeaderSize == (int) pRecord->recHeaderLength); + + /* + * Seek forward once again, so we are positioned at the correct + * place to write the next record. + */ + err = Nu_FSeek(pArchive->tmpFp, finalOffset, SEEK_SET); + BailError(err); + + /* update the record's fileOffset to reflect its new position */ + DBUG(("+++ record shifted by %ld bytes\n", + initialOffset - pRecord->fileOffset)); + pRecord->fileOffset = initialOffset; + +bail: + if (err == kNuErrSkipped) { + /* + * Something went wrong and they want to skip this record but + * keep going otherwise. We need to back up in the file so the + * original copy of the record can go here. + */ + err = Nu_FSeek(pArchive->tmpFp, initialOffset, SEEK_SET); + if (err == kNuErrNone) + err = kNuErrSkipped; /* tell the caller we skipped it */ + } + + Nu_NewThreads_Free(pArchive, pNewThreads); + return err; +} + + +/* + * Construct a new record and add it to the original or temp file. The + * new record has no threads but some number of threadMods. (This + * function is a cousin to Nu_ConstructArchiveRecord.) "pRecord" must + * come from the "new" record set. + * + * The original/temp file should be positioned at the correct spot. + * + * If something goes wrong, and the user wants to abort the record but + * not the entire operation, we rewind the temp file to the initial + * position and return kNuErrSkipped. + */ +static NuError Nu_ConstructNewRecord(NuArchive* pArchive, NuRecord* pRecord, + FILE* fp) +{ + NuError err; + NuNewThreads* pNewThreads = NULL; + NuThreadMod* pThreadMod; + long threadDisp; + long initialOffset, finalOffset; + long numThreadMods, numFilenameThreads; + int newHeaderSize; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + DBUG(("--- Constructing '%s'\n", pRecord->filename)); + + err = Nu_FTell(fp, &initialOffset); + BailError(err); + Assert(initialOffset != 0); + + /* + * Quick sanity check: verify that the record has no threads of its + * own, and all threadMods are "add" threadMods. While we're at it, + * make ourselves useful by counting up the number of eventual + * threads, and verify that there is exactly one filename thread. + */ + Assert(pRecord->pThreads == NULL); + + numThreadMods = 0; + numFilenameThreads = 0; + pThreadMod = pRecord->pThreadMods; + while (pThreadMod) { + if (pThreadMod->entry.kind != kNuThreadModAdd) { + Nu_ReportError(NU_BLOB, kNuErrNone, "unexpected non-add threadMod"); + err = kNuErrInternal; + Assert(0); + goto bail; + } + numThreadMods++; + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) + numFilenameThreads++; + + pThreadMod = pThreadMod->pNext; + } + Assert(numFilenameThreads <= 1); + + /* + * If there's no filename thread, make one. We do this for brand-new + * records when the application doesn't explicitly add a thread. + */ + if (!numFilenameThreads) { + NuDataSource* pTmpDataSource = NULL; + NuThreadMod* pNewThreadMod = NULL; + int len, maxLen; + + /* + * Generally speaking, the "add file" call should set the + * filename. If somehow it didn't, assign a default. + */ + if (pRecord->filenameMOR == NULL) { + pRecord->newFilenameMOR = strdup(kNuDefaultRecordName); + pRecord->filenameMOR = pRecord->newFilenameMOR; + } + + DBUG(("--- No filename thread found, adding one ('%s')\n", + pRecord->filenameMOR)); + + /* + * Create a trivial data source for the filename. The size of + * the filename buffer is the larger of the filename length and + * the default filename buffer size. This mimics GSHK's behavior. + * (If we're really serious about renaming it, maybe we should + * leave some extra space on the end...?) + */ + len = strlen(pRecord->filenameMOR); + maxLen = len > kNuDefaultFilenameThreadSize ? + len : kNuDefaultFilenameThreadSize; + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, + maxLen, (const uint8_t*)pRecord->filenameMOR, 0, + strlen(pRecord->filenameMOR), NULL, &pTmpDataSource); + BailError(err); + + /* put in a new "add" threadMod (which copies the data source) */ + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDFilename, + kNuThreadFormatUncompressed, pTmpDataSource, &pNewThreadMod); + Nu_DataSourceFree(pTmpDataSource); + BailError(err); + + /* add it to the list */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = NULL; + + numFilenameThreads++; + numThreadMods++; + } + + /* + * Figure out how large the record header is. We don't generate + * GS/OS option lists or "extra" data here, and we always put the + * filename in a thread, so the size is constant. (If somebody + * does a GS/OS or Mac port and wants to add option lists, it should + * not be hard to adjust the size accordingly.) + * + * This initializes the record's attribCount. We use the "base size" + * and add two for the (unused) filename length. + */ + pRecord->recAttribCount = kNuRecordHeaderBaseSize +2; + newHeaderSize = pRecord->recAttribCount + numThreadMods * kNuThreadHeaderSize; + + DBUG(("+++ new header size = %d\n", newHeaderSize)); + + /* leave a hole */ + err = Nu_FSeek(fp, newHeaderSize, SEEK_CUR); + BailError(err); + + /* + * It is important to arrange the threads in a specific order. See + * the comments in Nu_ConstructArchiveRecord for the rationale. + */ + err = Nu_NewThreads_New(pArchive, &pNewThreads, numThreadMods); + BailError(err); + + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDFilename, + true, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDComment, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDDataFork, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDDiskImage, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDRsrcFork, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDWildcard, + false, pNewThreads, fp); + BailError(err); + + /* + * Perform some sanity checks. + */ + Assert(!Nu_NewThreads_HasRoom(pNewThreads)); + + /* verify that all threads and threadMods have been touched */ + if (!Nu_VerifyAllTouched(pArchive, pRecord)) { + err = kNuErrInternal; + Assert(0); + goto bail; + } + + /* verify that file displacement is where it should be */ + threadDisp = Nu_NewThreads_TotalCompThreadEOF(pNewThreads); + err = Nu_FTell(fp, &finalOffset); + BailError(err); + Assert(finalOffset > initialOffset); + if (finalOffset - (initialOffset + newHeaderSize) != threadDisp) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "ERROR: didn't end up where expected (%ld %ld %ld)", + initialOffset, finalOffset, threadDisp); + err = kNuErrInternal; + Assert(0); + goto bail; + } + + /* + * Install pNewThreads as the thread list. + */ + Assert(pRecord->pThreads == NULL && pRecord->recTotalThreads == 0); + pRecord->pThreads = Nu_NewThreads_DonateThreads(pNewThreads); + pRecord->recTotalThreads = Nu_NewThreads_GetNumThreads(pNewThreads); + + /* + * Fill in misc record header fields. + * + * We could set recArchiveWhen here, if we wanted to override what + * the application set, but I don't think there's any value in that. + */ + pRecord->fileOffset = initialOffset; + + /* + * Now, seek back and write the record header. + */ + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, fp); + BailError(err); + + /* + * Seek forward once again, so we are positioned at the correct + * place to write the next record. + */ + err = Nu_FSeek(fp, finalOffset, SEEK_SET); + BailError(err); + + /* + * Trash the threadMods. + */ + Nu_FreeThreadMods(pArchive, pRecord); + +bail: + if (err == kNuErrSkipped) { + /* + * Something went wrong and they want to skip this record but + * keep going otherwise. We need to back up in the file so the + * next record can go here. + */ + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + if (err == kNuErrNone) + err = kNuErrSkipped; /* tell the caller we skipped it */ + } + + Nu_NewThreads_Free(pArchive, pNewThreads); + return err; +} + + +/* + * Update a given record in the original archive file. + * + * "pRecord" is the record from the "copy" set. It can have the + * "dirtyHeader" flag set, and may have "update" threadMods, but + * that's all. + * + * The position of pArchive->archiveFp on entry and on exit is not + * defined. + */ +static NuError Nu_UpdateRecordInOriginal(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + NuThread* pThread; + const NuThreadMod* pThreadMod; + + /* + * Loop through all threadMods. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + Assert(pThreadMod->entry.kind == kNuThreadModUpdate); + + /* find the thread associated with this threadMod */ + err = Nu_FindThreadByIdx(pRecord, pThreadMod->entry.update.threadIdx, + &pThread); + BailError(err); /* should never happen */ + + /* seek to the appropriate spot */ + err = Nu_FSeek(pArchive->archiveFp, pThread->fileOffset, SEEK_SET); + BailError(err); + + /* do the update; this updates "pThread" with the new info */ + err = Nu_ConstructArchiveUpdate(pArchive, pArchive->archiveFp, + pRecord, pThread, pThreadMod); + BailError(err); + + pThreadMod = pThreadMod->pNext; + } + + + /* + * We have to write a new record header without disturbing + * anything around it. Nothing we've done should've changed + * the size of the record header, so just go ahead and write it. + * + * We have to do this regardless of "dirtyHeader", because we just + * tweaked some of our threads around, and we need to rewrite the + * thread headers (which updates the record header CRC, and so on). + */ + err = Nu_FSeek(pArchive->archiveFp, pRecord->fileOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, pArchive->archiveFp); + BailError(err); + + /* + * Let's be paranoid and verify that the write didn't overflow + * into the thread header. We compare our current offset against + * the offset of the first thread. (If we're in a weird record + * with no threads, we could compare against the offset of the + * next record, but I don't want to deal with a case that should + * never happen anyway.) + */ + DBUG(("--- record header wrote %ld bytes\n", + pArchive->currentOffset - pRecord->fileOffset)); + pThread = pRecord->pThreads; + if (pThread != NULL && pArchive->currentOffset != pThread->fileOffset) { + /* guess what, we just trashed the archive */ + err = kNuErrDamaged; + Nu_ReportError(NU_BLOB, err, + "Bad record header write (off by %ld), archive damaged", + pArchive->currentOffset - pThread->fileOffset); + goto bail; + } + DBUG(("--- record header written safely\n")); + + + /* + * It's customary to throw out the thread mods when you're done. (I'm + * not really sure why I'm doing this now, but here we are.) + */ + Nu_FreeThreadMods(pArchive, pRecord); + +bail: + return err; +} + + +/* + * =========================================================================== + * Archive construction - main functions + * =========================================================================== + */ + +/* + * Fill in the temp file with the contents of the original archive. The + * file offsets and any other generated data in the "copy" set will be + * updated as appropriate, so that the "copy" set can eventually replace + * the "orig" set. + * + * On exit, pArchive->tmpFp will point at the archive EOF. + */ +static NuError Nu_CreateTempFromOriginal(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + Assert(pArchive->tmpFp != 0); + Assert(ftell(pArchive->tmpFp) == 0); /* should be empty as well */ + + /* + * Leave space for the master header and (if we're preserving it) any + * header gunk. + */ + Assert(!pArchive->valDiscardWrapper || pArchive->headerOffset == 0); + err = Nu_FSeek(pArchive->tmpFp, + pArchive->headerOffset + kNuMasterHeaderSize, SEEK_SET); + BailError(err); + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + /* + * Run through the "copy" records. If the original record header is + * umodified, just copy it; otherwise write a new one with a new CRC. + */ + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet)) { + /* new archive or all records deleted */ + DBUG(("--- No records in 'copy' set\n")); + goto bail; + } + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + } else { + /* + * There's no "copy" set defined. If we have an "orig" set, we + * must be doing nothing but add files to an existing archive + * without the "modify orig" flag set. + */ + if (Nu_RecordSet_IsEmpty(&pArchive->origRecordSet)) { + DBUG(("--- No records in 'copy' or 'orig' set\n")); + goto bail; + } + pRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + } + + /* + * Reconstruct or copy the records. It's probably not necessary + * to reconstruct the entire record if we're just updating the + * record header, but since all we do is copy the data anyway, + * it's not much slower. + */ + while (pRecord != NULL) { + if (!pRecord->dirtyHeader && pRecord->pThreadMods == NULL) { + err = Nu_CopyArchiveRecord(pArchive, pRecord); + BailError(err); + } else { + err = Nu_ConstructArchiveRecord(pArchive, pRecord); + if (err == kNuErrSkipped) { + /* + * We're going to retain the original. This requires us + * to copy the original record from the "orig" record set + * and replace what we had in the "copy" set, so that at + * the end of the day the "copy" set accurately reflects + * what's in the archive. + */ + DBUG(("--- Skipping, copying %ld instead\n", + pRecord->recordIdx)); + err = Nu_RecordSet_ReplaceRecord(pArchive, + &pArchive->copyRecordSet, pRecord, + &pArchive->origRecordSet, &pRecord); + BailError(err); + err = Nu_CopyArchiveRecord(pArchive, pRecord); + BailError(err); + } + BailError(err); + } + + pRecord = pRecord->pNext; + } + +bail: + return err; +} + + +/* + * Perform updates to certain items in the original archive. None of + * the operations changes the position of items within. + * + * On exit, pArchive->archiveFp will point at the archive EOF. + */ +static NuError Nu_UpdateInOriginal(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + /* + * There's nothing for us to do; we probably just have a + * bunch of new stuff being added. + */ + DBUG(("--- UpdateInOriginal: nothing to do\n")); + goto done; + } + + /* + * Run through and process all the updates. + */ + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pRecord != NULL) { + if (pRecord->dirtyHeader || pRecord->pThreadMods != NULL) { + err = Nu_UpdateRecordInOriginal(pArchive, pRecord); + BailError(err); + } + + pRecord = pRecord->pNext; + } + +done: + /* seek to the end of the archive */ + err = Nu_FSeek(pArchive->archiveFp, + pArchive->headerOffset + pArchive->masterHeader.mhMasterEOF, + SEEK_SET); + BailError(err); + +bail: + return err; +} + + +/* + * Create new records for all items in the "new" list, writing them to + * "fp" at the current offset. + * + * On completion, "fp" will point at the end of the archive. + */ +static NuError Nu_CreateNewRecords(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(&pArchive->newRecordSet); + while (pRecord != NULL) { + err = Nu_ConstructNewRecord(pArchive, pRecord, fp); + if (err == kNuErrSkipped) { + /* + * We decided to skip this record, so delete it from "new". + * + * (I think this is the only time we delete something from the + * "new" set...) + */ + NuRecord* pNextRecord = pRecord->pNext; + + DBUG(("--- Skipping, deleting new %ld\n", pRecord->recordIdx)); + err = Nu_RecordSet_DeleteRecord(pArchive, &pArchive->newRecordSet, + pRecord); + Assert(err == kNuErrNone); + BailError(err); + pRecord = pNextRecord; + } else { + BailError(err); + pRecord = pRecord->pNext; + } + } + +bail: + return err; +} + + +/* + * =========================================================================== + * Archive update helpers + * =========================================================================== + */ + +/* + * Determine if any "heavy updates" have been made. A "heavy" update is + * one that requires us to create and rename a temp file. + * + * If the "copy" record set hasn't been loaded, we're done. If it has + * been loaded, we scan through the list for thread mods other than updates + * to pre-sized fields. We also have to check to see if any records were + * deleted. + * + * At present, a "dirtyHeader" flag is not of itself cause to rebuild + * the archive, so we don't test for it here. + */ +static Boolean Nu_NoHeavyUpdates(NuArchive* pArchive) +{ + const NuRecord* pRecord; + long count; + + /* if not loaded, then *no* changes were made to original records */ + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + return true; + + /* + * You can't add to "copy" set, so any deletions are visible by the + * reduced record count. The function that deletes records from + * which all threads have been removed should be called before we + * get here. + */ + if (Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet) != + Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet)) + { + return false; + } + + /* + * Run through the set of records, looking for a threadMod with a + * change type we can't handle in place. + */ + count = Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet); + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (count--) { + const NuThreadMod* pThreadMod; + + Assert(pRecord != NULL); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + /* the only acceptable kind is "update" */ + if (pThreadMod->entry.kind != kNuThreadModUpdate) + return false; + + pThreadMod = pThreadMod->pNext; + } + + pRecord = pRecord->pNext; + } + + return true; +} + + +/* + * Purge any records that don't have any threads. This has to take into + * account pending modifications, so that we dispose of any records that + * have had all of their threads deleted. + * + * Simplest approach is to count up the #of "delete" mods and subtract + * it from the number of threads, skipping on if the record has any + * "add" thread mods. + */ +static NuError Nu_PurgeEmptyRecords(NuArchive* pArchive, + NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuRecord** ppRecord; + + Assert(pArchive != NULL); + Assert(pRecordSet != NULL); + + if (Nu_RecordSet_IsEmpty(pRecordSet)) + return kNuErrNone; + + ppRecord = Nu_RecordSet_GetListHeadPtr(pRecordSet); + Assert(ppRecord != NULL); + Assert(*ppRecord != NULL); + + /* maintain a pointer to the pointer, so we can delete easily */ + while (*ppRecord != NULL) { + pRecord = *ppRecord; + + if (Nu_RecordIsEmpty(pArchive, pRecord)) { + DBUG(("--- Purging empty record %06ld '%s' (0x%08lx-->0x%08lx)\n", + pRecord->recordIdx, pRecord->filename, + (uint32_t)ppRecord, (uint32_t)pRecord)); + err = Nu_RecordSet_DeleteRecordPtr(pArchive, pRecordSet, ppRecord); + BailError(err); + /* pRecord is now invalid, and *ppRecord has been updated */ + } else { + ppRecord = &pRecord->pNext; + } + } + +bail: + return err; +} + + +/* + * Update the "new" master header block with the contents of the modified + * archive, and write it to the file. + * + * Pass in a correctly positioned "fp" and the total length of the archive + * file. + */ +static NuError Nu_UpdateMasterHeader(NuArchive* pArchive, FILE* fp, + long archiveEOF) +{ + NuError err; + long numRecords; + + Nu_MasterHeaderCopy(pArchive, &pArchive->newMasterHeader, + &pArchive->masterHeader); + + numRecords = 0; + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet); + else + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet); + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet); + #if 0 /* we allow delete-all now */ + if (numRecords == 0) { + /* don't allow empty archives */ + DBUG(("--- UpdateMasterHeader didn't find any records\n")); + err = kNuErrNoRecords; + goto bail; + } + #endif + + pArchive->newMasterHeader.mhTotalRecords = numRecords; + pArchive->newMasterHeader.mhMasterEOF = archiveEOF; + pArchive->newMasterHeader.mhMasterVersion = kNuOurMHVersion; + Nu_SetCurrentDateTime(&pArchive->newMasterHeader.mhArchiveModWhen); + + err = Nu_WriteMasterHeader(pArchive, fp, &pArchive->newMasterHeader); + BailError(err); + +bail: + return err; +} + + +/* + * Reset the temp file to a known (empty) state. + */ +static NuError Nu_ResetTempFile(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + + /* read-only archives don't have a temp file */ + if (Nu_IsReadOnly(pArchive)) + return kNuErrNone; /* or kNuErrArchiveRO? */ + + Assert(pArchive != NULL); + Assert(pArchive->tmpPathnameUNI != NULL); + +#if 0 /* keep the temp file around for examination */ +if (pArchive->tmpFp != NULL) { + DBUG(("--- NOT Resetting temp file\n")); + fflush(pArchive->tmpFp); + goto bail; +} +#endif + + DBUG(("--- Resetting temp file\n")); + + /* if we renamed the temp over the original, we need to open a new temp */ + if (pArchive->tmpFp == NULL) { + // as in Nu_OpenTempFile, skip the wchar conversion for the temp + // file name, which we lazily assume to be ASCII + pArchive->tmpFp = fopen(pArchive->tmpPathnameUNI, + kNuFileOpenReadWriteCreat); + if (pArchive->tmpFp == NULL) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, errno, "Unable to open temp file '%s'", + pArchive->tmpPathnameUNI); + goto bail; + } + } else { + /* + * Truncate the temp file. + */ + err = Nu_FSeek(pArchive->tmpFp, 0, SEEK_SET); + BailError(err); + err = Nu_TruncateOpenFile(pArchive->tmpFp, 0); + if (err == kNuErrInternal) { + /* do it the hard way if we don't have ftruncate or equivalent */ + err = kNuErrNone; + fclose(pArchive->tmpFp); + pArchive->tmpFp = fopen(pArchive->tmpPathnameUNI, + kNuFileOpenWriteTrunc); + if (pArchive->tmpFp == NULL) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, err, "failed truncating tmp file"); + goto bail; + } + fclose(pArchive->tmpFp); + pArchive->tmpFp = fopen(pArchive->tmpPathnameUNI, + kNuFileOpenReadWriteCreat); + if (pArchive->tmpFp == NULL) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, err, "Unable to open temp file '%s'", + pArchive->tmpPathnameUNI); + goto bail; + } + } + } + +bail: + return err; +} + +/* + * Ensure that all of the threads and threadMods in a record are in + * a pristine state, i.e. "threads" aren't marked used and "threadMods" + * don't even exist. This is done as we are cleaning up the record sets + * after a successful (or aborted) update. + */ +static NuError Nu_RecordResetUsedFlags(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThread* pThread; + long idx; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + /* these should already be clear */ + if (pRecord->pThreadMods) { + Assert(0); + return kNuErrInternal; + } + + /* these might still be set */ + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + pThread->used = false; + } + + /* and this */ + pRecord->dirtyHeader = false; + + return kNuErrNone; +} + +/* + * Invoke Nu_RecordResetUsedFlags on all records in a record set. + */ +static NuError Nu_ResetUsedFlags(NuArchive* pArchive, NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + while (pRecord != NULL) { + err = Nu_RecordResetUsedFlags(pArchive, pRecord); + if (err != kNuErrNone) { + Assert(0); + break; + } + + pRecord = pRecord->pNext; + } + + return err; +} + + +/* + * If nothing in the "copy" set has actually been disturbed, throw it out. + */ +static void Nu_ResetCopySetIfUntouched(NuArchive* pArchive) +{ + const NuRecord* pRecord; + + /* have any records been deleted? */ + if (Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet) != + pArchive->masterHeader.mhTotalRecords) + { + return; + } + + /* do we have any thread mods or dirty record headers? */ + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pRecord != NULL) { + if (pRecord->pThreadMods != NULL || pRecord->dirtyHeader) + return; + + pRecord = pRecord->pNext; + } + + /* looks like nothing has been touched */ + DBUG(("--- copy set untouched, trashing it\n")); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); +} + + +/* + * GSHK always adds a comment to the first new record added to an archive. + * Imitate this behavior. + */ +static NuError Nu_AddCommentToFirstNewRecord(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuThreadMod* pThreadMod = NULL; + NuThreadMod* pExistingThreadMod = NULL; + NuDataSource* pDataSource = NULL; + + /* if there aren't any records there, skip this */ + if (Nu_RecordSet_IsEmpty(&pArchive->newRecordSet)) + goto bail; + + pRecord = Nu_RecordSet_GetListHead(&pArchive->newRecordSet); + + /* + * See if this record already has a comment. If so, don't add + * another one. + */ + err = Nu_ThreadModAdd_FindByThreadID(pRecord, kNuThreadIDComment, + &pExistingThreadMod); + if (err == kNuErrNone) { + DBUG(("+++ record already has a comment, not adding another\n")); + goto bail; /* already exists */ + } + err = kNuErrNone; + + /* create a new data source with nothing in it */ + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, + kNuDefaultCommentSize, NULL, 0, 0, NULL, &pDataSource); + BailError(err); + Assert(pDataSource != NULL); + + /* create a new ThreadMod */ + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDComment, + kNuThreadFormatUncompressed, pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != NULL); + /*pDataSource = NULL;*/ /* ThreadModAdd_New makes a copy */ + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + pThreadMod = NULL; /* don't free on exit */ + +bail: + Nu_ThreadModFree(pArchive, pThreadMod); + Nu_DataSourceFree(pDataSource); + return err; +} + + +/* + * =========================================================================== + * Main entry points + * =========================================================================== + */ + +/* + * Force all deferred changes to occur. + * + * If the flush fails, the archive state may be aborted or even placed + * into read-only mode to prevent problems from compounding. + * + * If the things this function is doing aren't making any sense at all, + * read "NOTES.txt" for an introduction. + */ +NuError Nu_Flush(NuArchive* pArchive, uint32_t* pStatusFlags) +{ + NuError err = kNuErrNone; + Boolean canAbort = true; + Boolean writeToTemp = true; + Boolean deleteAll = false; + long initialEOF, finalOffset; + + DBUG(("--- FLUSH\n")); + + if (pStatusFlags == NULL) + return kNuErrInvalidArg; + /* these do get set on error, so clear them no matter what */ + *pStatusFlags = 0; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + err = Nu_GetFileLength(pArchive, pArchive->archiveFp, &initialEOF); + BailError(err); + + /* + * Step 1: figure out if we have anything to do. If the "copy" and "new" + * lists are empty, then there's nothing for us to do. + * + * As a special case, we test for an archive that had all of its + * records deleted. This looks a lot like an archive that has had + * nothing done, because we would have made a "copy" list and then + * deleted all the records, leaving us with an empty list. (The + * difference is that an untouched archive wouldn't have a "copy" + * list allocated.) + * + * In some cases, such as doing a bulk delete that doesn't end up + * matching anything or an attempted UpdatePresizedThread on a thread + * that isn't actually pre-sized, we create the "copy" list but don't + * actually change anything. We deal with that by frying the "copy" + * list if it doesn't have anything interesting in it (i.e. it's an + * exact match of the "orig" list). + */ + Nu_ResetCopySetIfUntouched(pArchive); + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet) && + Nu_RecordSet_IsEmpty(&pArchive->newRecordSet)) + { + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + DBUG(("--- All records deleted!\n")); + #if 0 + /* + * Options: + * (1) allow it, leaving an archive with nothing but a header + * that will probably be rejected by other NuFX applications + * (2) reject it, returning an error + * (3) allow it, and just delete the original archive + * + * I dislike #1, and #3 can be implemented by the application + * when it gets a #2. + */ + err = kNuErrAllDeleted; + goto bail; + #else + /* + * (4) go ahead and delete everything, then mark the archive + * as brand new, so that closing the archive with new + * records in it will trigger deletion of the archive file. + */ + deleteAll = true; + #endif + } else { + DBUG(("--- Nothing pending\n")); + goto flushed; + } + } + + /* if we have any changes, we certainly should have the TOC by now */ + Assert(pArchive->haveToc); + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + + /* + * Step 2: purge any records from the "copy" and "new" lists that don't + * have any threads. You can't delete threads from the "new" list, but + * it's possible somebody called NuAddRecord and never put anything in it. + */ + err = Nu_PurgeEmptyRecords(pArchive, &pArchive->copyRecordSet); + BailError(err); + err = Nu_PurgeEmptyRecords(pArchive, &pArchive->newRecordSet); + BailError(err); + + /* we checked delete-all actions above, so just check for empty */ + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet) && + Nu_RecordSet_IsEmpty(&pArchive->newRecordSet) && + !deleteAll) + { + DBUG(("--- Nothing pending after purge\n")); + goto flushed; + } + + /* + * Step 3: if we're in ShrinkIt-compatibility mode, add a comment + * thread to the first record in the new list. GSHK does this every + * time it adds files, regardless of the prior contents of the archive. + */ + if (pArchive->valMimicSHK) { + err = Nu_AddCommentToFirstNewRecord(pArchive); + BailError(err); + } + + /* + * Step 4: decide if we want to make changes in place, or write to + * a temp file. Any deletions or additions to existing records will + * require writing to a temp file. Additions of new records and + * updates to pre-sized threads can be done in place. + */ + writeToTemp = true; + if (pArchive->valModifyOrig && Nu_NoHeavyUpdates(pArchive)) + writeToTemp = false; + /* discard the wrapper, if desired */ + if (writeToTemp && pArchive->valDiscardWrapper) + pArchive->headerOffset = 0; + + /* + * Step 5: handle updates to existing records. + */ + if (!writeToTemp) { + /* + * Step 5a: modifying in place, process all UPDATE ThreadMods now. + */ + DBUG(("--- No heavy updates found, updating in place\n")); + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + canAbort = false; /* modifying original, can't cleanly abort */ + + err = Nu_UpdateInOriginal(pArchive); + if (err == kNuErrDamaged) + *pStatusFlags |= kNuFlushCorrupted; + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "update to original failed"); + goto bail; + } + } else { + /* + * Step 5b: not modifying in place, reconstruct the appropriate + * parts of the original archive in the temp file, possibly copying + * the front bits over first. Updates and thread-adds will be + * done here. + */ + DBUG(("--- Updating to temp file (valModifyOrig=%ld)\n", + pArchive->valModifyOrig)); + err = Nu_CreateTempFromOriginal(pArchive); + if (err != kNuErrNone) { + DBUG(("--- Create temp from original failed\n")); + goto bail; + } + } + /* on completion, tmpFp (or archiveFp) points to current archive EOF */ + + /* + * Step 6: add the new records from the "new" list, if any. Add a + * filename thread to records where one wasn't provided. These records + * are either added to the original archive or the temp file as + * appropriate. + */ + if (writeToTemp) + err = Nu_CreateNewRecords(pArchive, pArchive->tmpFp); + else + err = Nu_CreateNewRecords(pArchive, pArchive->archiveFp); + BailError(err); + + /* on completion, tmpFp (or archiveFp) points to current archive EOF */ + + /* + * Step 7: truncate the archive. This isn't strictly necessary. It + * comes in handy if we were compressing the very last file and it + * actually expanded. We went back and wrote the uncompressed data, + * but there's a bunch of junk after it from the first try. + * + * On systems like Win32 that don't support ftruncate, this will fail, + * so we just ignore the result. + */ + if (writeToTemp) { + err = Nu_FTell(pArchive->tmpFp, &finalOffset); + BailError(err); + (void) Nu_TruncateOpenFile(pArchive->tmpFp, finalOffset); + } else { + err = Nu_FTell(pArchive->archiveFp, &finalOffset); + BailError(err); + (void) Nu_TruncateOpenFile(pArchive->archiveFp, finalOffset); + } + + /* + * Step 8: create an updated master header, and write it to the + * appropriate file. The "newMasterHeader" field in pArchive will + * hold the new header. + */ + Assert(!pArchive->newMasterHeader.isValid); + if (writeToTemp) { + err = Nu_FSeek(pArchive->tmpFp, pArchive->headerOffset, SEEK_SET); + BailError(err); + err = Nu_UpdateMasterHeader(pArchive, pArchive->tmpFp, + finalOffset - pArchive->headerOffset); + /* fall through with err */ + } else { + err = Nu_FSeek(pArchive->archiveFp, pArchive->headerOffset, SEEK_SET); + BailError(err); + err = Nu_UpdateMasterHeader(pArchive, pArchive->archiveFp, + finalOffset - pArchive->headerOffset); + /* fall through with err */ + } + if (err == kNuErrNoRecords && !deleteAll) { + /* + * Somehow we ended up without any records at all. If we managed + * to get this far, it could only be because the user told us to + * skip adding everything. + */ + Nu_ReportError(NU_BLOB, kNuErrNone, "no records in this archive"); + goto bail; + } else if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "failed writing master header"); + goto bail; + } + Assert(pArchive->newMasterHeader.isValid); + + + /* + * Step 9: carry forward the BXY, SEA, or BSE header, if necessary. This + * implicitly assumes that the header doesn't change size. If this + * assumption is invalid, we'd need to adjust "headerOffset" earlier, + * or do lots of data copying. Looks like Binary II and SEA headers + * are both fixed size, so we should be okay. + * + * We also carry forward any unrecognized junk. + */ + if (pArchive->headerOffset) { + if (writeToTemp) { + if (!pArchive->valDiscardWrapper) { + DBUG(("--- Preserving wrapper\n")); + /* copy header to temp */ + err = Nu_CopyWrapperToTemp(pArchive); + BailError(err); + /* update fields that require it */ + err = Nu_UpdateWrapper(pArchive, pArchive->tmpFp); + BailError(err); + /* check the padding */ + err = Nu_AdjustWrapperPadding(pArchive, pArchive->tmpFp); + BailError(err); + } + } else { + /* may need to tweak what's in place? */ + DBUG(("--- Updating wrapper\n")); + err = Nu_UpdateWrapper(pArchive, pArchive->archiveFp); + BailError(err); + /* should only be necessary if we've added new records */ + err = Nu_AdjustWrapperPadding(pArchive, pArchive->archiveFp); + BailError(err); + } + } + + /* + * Step 10: if necessary, remove the original file and rename the + * temp file over it. + * + * I'm not messing with access permissions on the archive file here, + * because if they opened it read-write then the archive itself + * must also be read-write (unless somebody snuck in and chmodded it + * while we were busy). The temp file is certainly writable, so we + * should be able to just leave it all alone. + * + * I'm closing both temp and archive before renaming, because on some + * operating systems you can't do certain things with open files. + */ + if (writeToTemp) { + canAbort = false; /* no going back */ + *pStatusFlags |= kNuFlushSucceeded; /* temp file is fully valid */ + + fclose(pArchive->archiveFp); + pArchive->archiveFp = NULL; + + err = Nu_DeleteArchiveFile(pArchive); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "unable to remove original archive"); + Nu_ReportError(NU_BLOB, kNuErrNone, "New data is in '%s'", + pArchive->tmpPathnameUNI); + *pStatusFlags |= kNuFlushInaccessible; + goto bail_reopen; /* must re-open archiveFp */ + } + + fclose(pArchive->tmpFp); + pArchive->tmpFp = NULL; + + err = Nu_RenameTempToArchive(pArchive); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "unable to rename temp file"); + Nu_ReportError(NU_BLOB, kNuErrNone, + "NOTE: only copy of archive is in '%s'", + pArchive->tmpPathnameUNI); + /* maintain Entry.c semantics (and keep them from removing temp) */ + Nu_Free(pArchive, pArchive->archivePathnameUNI); + pArchive->archivePathnameUNI = NULL; + Nu_Free(pArchive, pArchive->tmpPathnameUNI); + pArchive->tmpPathnameUNI = NULL; + /* bail will put us into read-only mode, which is what we want */ + goto bail; + } + +bail_reopen: + pArchive->archiveFp = fopen(pArchive->archivePathnameUNI, + kNuFileOpenReadWrite); + if (pArchive->archiveFp == NULL) { + err = errno ? errno : -1; + Nu_ReportError(NU_BLOB, err, + "unable to reopen archive file '%s' after rename", + pArchive->archivePathnameUNI); + *pStatusFlags |= kNuFlushInaccessible; + goto bail; /* the Entry.c funcs will obstruct further use */ + } + + if (err != kNuErrNone) // earlier failure? + goto bail; + } else { + fflush(pArchive->archiveFp); + if (ferror(pArchive->archiveFp)) { + err = kNuErrFileWrite; + Nu_ReportError(NU_BLOB, kNuErrNone, "final archive flush failed"); + *pStatusFlags |= kNuFlushCorrupted; + goto bail; + } + canAbort = false; + *pStatusFlags |= kNuFlushSucceeded; + } + + Assert(canAbort == false); + + /* + * Step 11: clean up data structures. If we have a "copy" list, then + * throw out the "orig" list and move the "copy" list over it. Append + * anything in the "new" list to it. Move the "new" master header + * over the original. + */ + Assert(pArchive->newMasterHeader.isValid); + Nu_MasterHeaderCopy(pArchive, &pArchive->masterHeader, + &pArchive->newMasterHeader); + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_MoveAllRecords(pArchive, &pArchive->origRecordSet, + &pArchive->copyRecordSet); + BailError(err); + } + err = Nu_RecordSet_MoveAllRecords(pArchive, &pArchive->origRecordSet, + &pArchive->newRecordSet); + BailError(err); + err = Nu_ResetUsedFlags(pArchive, &pArchive->origRecordSet); + BailError(err); + +flushed: + /* + * Step 12: reset the "copy" and "new" lists, and reset the temp file. + * Clear out the "new" master header copy. + */ + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + BailError(err); + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + BailError(err); + pArchive->newMasterHeader.isValid = false; + + err = Nu_ResetTempFile(pArchive); + if (err != kNuErrNone) { + /* can't NuAbort() our way out of a bad temp file */ + canAbort = false; + goto bail; + } + + if (deleteAll) { + /* there's nothing in it, so treat it like a newly-created archive */ + /* (that way it gets deleted if the app closes without adding stuff) */ + DBUG(("--- marking archive as newly created\n")); + pArchive->newlyCreated = true; + /*pArchive->valModifyOrig = true;*/ + } + +bail: + if (err != kNuErrNone) { + if (canAbort) { + (void) Nu_Abort(pArchive); + Assert(!(*pStatusFlags & kNuFlushSucceeded)); + *pStatusFlags |= kNuFlushAborted; + + /* + * If we were adding to original archive, truncate it back if + * we are able to do so. This retains any BXY/BSE wrapper padding. + */ + if (!writeToTemp) { + NuError err2; + err2 = Nu_TruncateOpenFile(pArchive->archiveFp, initialEOF); + if (err2 == kNuErrNone) { + DBUG(("+++ truncated orig archive back to %ld\n", + initialEOF)); + } else { + DBUG(("+++ truncate orig failed (err=%d)\n", err2)); + } + } + } else { + Nu_ReportError(NU_BLOB, kNuErrNone, + "disabling write access after failed update"); + pArchive->openMode = kNuOpenRO; + *pStatusFlags |= kNuFlushReadOnly; + } + } + + /* last-minute sanity check */ + Assert(pArchive->origRecordSet.numRecords == 0 || + (pArchive->origRecordSet.nuRecordHead != NULL && + pArchive->origRecordSet.nuRecordTail != NULL)); + + return err; +} + + +/* + * Abort any pending changes. + */ +NuError Nu_Abort(NuArchive* pArchive) +{ + Assert(pArchive != NULL); + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + DBUG(("--- Aborting changes\n")); + + /* + * Throw out the "copy" and "new" record sets, and reset the + * temp file. + */ + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + pArchive->newMasterHeader.isValid = false; + + return Nu_ResetTempFile(pArchive); +} + diff --git a/nufxlib/Deflate.c b/nufxlib/Deflate.c new file mode 100644 index 0000000..6a0e979 --- /dev/null +++ b/nufxlib/Deflate.c @@ -0,0 +1,295 @@ +/* + * 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. + * + * Support for the "deflate" algorithm, via the "zlib" library. + * + * This compression format is totally unsupported on the Apple II. This + * is provided primarily for the benefit of Apple II emulators that want + * a better storage format for disk images than SHK+LZW or a ZIP file. + * + * This code was developed and tested with ZLIB_VERSION "1.1.3". It is + * expected to work with any version >= 1.1.3 and < 2.x. Please visit + * http://www.zlib.org/ for more information. + */ +#include "NufxLibPriv.h" + +#ifdef ENABLE_DEFLATE +#include "zlib.h" + +#define kNuDeflateLevel 9 /* use maximum compression */ + + +/* + * Alloc and free functions provided to zlib. + */ +static voidpf Nu_zalloc(voidpf opaque, uInt items, uInt size) +{ + return Nu_Malloc(opaque, items * size); +} +static void Nu_zfree(voidpf opaque, voidpf address) +{ + Nu_Free(opaque, address); +} + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +/* + * Compress "srcLen" bytes from "pStraw" to "fp". + */ +NuError Nu_CompressDeflate(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + z_stream zstream; + int zerr; + Bytef* outbuf = NULL; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(fp != NULL); + Assert(srcLen > 0); + Assert(pDstLen != NULL); + Assert(pCrc != NULL); + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + /* allocate a similarly-sized buffer for the output */ + outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + BailAlloc(outbuf); + + /* + * Initialize the zlib stream. + */ + zstream.zalloc = Nu_zalloc; + zstream.zfree = Nu_zfree; + zstream.opaque = pArchive; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outbuf; + zstream.avail_out = kNuGenCompBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit(&zstream, kNuDeflateLevel); + if (zerr != Z_OK) { + err = kNuErrInternal; + if (zerr == Z_VERSION_ERROR) { + Nu_ReportError(NU_BLOB, err, + "installed zlib is not compatible with linked version (%s)", + ZLIB_VERSION); + } else { + Nu_ReportError(NU_BLOB, err, + "call to deflateInit failed (zerr=%d)", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + uint32_t getSize; + int flush; + + /* should be able to read a full buffer every time */ + if (zstream.avail_in == 0 && srcLen) { + getSize = (srcLen > kNuGenCompBufSize) ? kNuGenCompBufSize : srcLen; + DBUG(("+++ reading %ld bytes\n", getSize)); + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "deflate read failed"); + goto z_bail; + } + + srcLen -= getSize; + + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getSize); + + zstream.next_in = pArchive->compBuf; + zstream.avail_in = getSize; + } + + if (srcLen == 0) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, err, "zlib deflate call failed (zerr=%d)", + zerr); + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != kNuGenCompBufSize)) + { + DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); + err = Nu_FWrite(fp, outbuf, zstream.next_out - outbuf); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "fwrite failed in deflate"); + goto z_bail; + } + + zstream.next_out = outbuf; + zstream.avail_out = kNuGenCompBufSize; + } + } while (zerr == Z_OK); + + Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pDstLen = zstream.total_out; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + if (outbuf != NULL) + free(outbuf); + return err; +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* + * Expand from "infp" to "pFunnel". + */ +NuError Nu_ExpandDeflate(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + z_stream zstream; + int zerr; + uint32_t compRemaining; + Bytef* outbuf; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(infp != NULL); + Assert(pFunnel != NULL); + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + /* allocate a similarly-sized buffer for the output */ + outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + BailAlloc(outbuf); + + compRemaining = pThread->thCompThreadEOF; + + /* + * Initialize the zlib stream. + */ + zstream.zalloc = Nu_zalloc; + zstream.zfree = Nu_zfree; + zstream.opaque = pArchive; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outbuf; + zstream.avail_out = kNuGenCompBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = inflateInit(&zstream); + if (zerr != Z_OK) { + err = kNuErrInternal; + if (zerr == Z_VERSION_ERROR) { + Nu_ReportError(NU_BLOB, err, + "installed zlib is not compatible with linked version (%s)", + ZLIB_VERSION); + } else { + Nu_ReportError(NU_BLOB, err, + "call to inflateInit failed (zerr=%d)", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + uint32_t getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kNuGenCompBufSize) ? + kNuGenCompBufSize : compRemaining; + DBUG(("+++ reading %ld bytes (%ld left)\n", getSize, + compRemaining)); + + err = Nu_FRead(infp, pArchive->compBuf, getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "inflate read failed"); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = pArchive->compBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, err, "zlib inflate call failed (zerr=%d)", + zerr); + goto z_bail; + } + + /* write every time there's anything (buffer will usually be full) */ + if (zstream.avail_out != kNuGenCompBufSize) { + DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); + err = Nu_FunnelWrite(pArchive, pFunnel, outbuf, + zstream.next_out - outbuf); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "write failed in inflate"); + goto z_bail; + } + + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, outbuf, zstream.next_out - outbuf); + + zstream.next_out = outbuf; + zstream.avail_out = kNuGenCompBufSize; + } + } while (zerr == Z_OK); + + Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if (zstream.total_out != pThread->actualThreadEOF) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "size mismatch on inflated file (%ld vs %u)", + zstream.total_out, pThread->actualThreadEOF); + goto z_bail; + } + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + if (outbuf != NULL) + free(outbuf); + return err; +} + +#endif /*ENABLE_DEFLATE*/ diff --git a/nufxlib/Entry.c b/nufxlib/Entry.c new file mode 100644 index 0000000..e9f0fd3 --- /dev/null +++ b/nufxlib/Entry.c @@ -0,0 +1,832 @@ +/* + * 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. + * + * All external entry points. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Misc utils + * =========================================================================== + */ + +/* + * Set the busy flag. + * + * The busy flag is intended to prevent the caller from executing illegal + * operations while inside a callback function. It is NOT intended to + * allow concurrent access to the same archive from multiple threads, so + * it does not follow all sorts of crazy semaphore semantics. If you + * have the need, go ahead and fix it. + */ +static inline void Nu_SetBusy(NuArchive* pArchive) +{ + pArchive->busy = true; +} + +/* + * Clear the busy flag. + */ +static inline void Nu_ClearBusy(NuArchive* pArchive) +{ + pArchive->busy = false; +} + + +/* + * Do a partial validation on NuArchive. Some calls, such as GetExtraData, + * can be made during callback functions when the archive isn't fully + * consistent. + */ +static NuError Nu_PartiallyValidateNuArchive(const NuArchive* pArchive) +{ + if (pArchive == NULL) + return kNuErrInvalidArg; + + if (pArchive->structMagic != kNuArchiveStructMagic) + return kNuErrBadStruct; + + return kNuErrNone; +} + +/* + * Validate the NuArchive* argument passed in to us. + */ +static NuError Nu_ValidateNuArchive(const NuArchive* pArchive) +{ + NuError err; + + err = Nu_PartiallyValidateNuArchive(pArchive); + if (err != kNuErrNone) + return err; + + /* explicitly block reentrant calls */ + if (pArchive->busy) + return kNuErrBusy; + + /* make sure the TOC state is consistent */ + if (pArchive->haveToc) { + if (pArchive->masterHeader.mhTotalRecords != 0) + Assert(Nu_RecordSet_GetListHead(&pArchive->origRecordSet) != NULL); + Assert(Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet) == + pArchive->masterHeader.mhTotalRecords); + } else { + Assert(Nu_RecordSet_GetListHead(&pArchive->origRecordSet) == NULL); + } + + /* make sure we have open files to work with */ + Assert(pArchive->archivePathnameUNI == NULL || pArchive->archiveFp != NULL); + if (pArchive->archivePathnameUNI != NULL && pArchive->archiveFp == NULL) + return kNuErrInternal; + Assert(pArchive->tmpPathnameUNI == NULL || pArchive->tmpFp != NULL); + if (pArchive->tmpPathnameUNI != NULL && pArchive->tmpFp == NULL) + return kNuErrInternal; + + /* further validations */ + + return kNuErrNone; +} + + +/* + * =========================================================================== + * Streaming and non-streaming read-only + * =========================================================================== + */ + +NUFXLIB_API NuError NuStreamOpenRO(FILE* infp, NuArchive** ppArchive) +{ + NuError err; + + if (infp == NULL || ppArchive == NULL) + return kNuErrInvalidArg; + + err = Nu_StreamOpenRO(infp, (NuArchive**) ppArchive); + + return err; +} + +NUFXLIB_API NuError NuContents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamContents(pArchive, contentFunc); + else + err = Nu_Contents(pArchive, contentFunc); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuExtract(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamExtract(pArchive); + else + err = Nu_Extract(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuTest(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamTest(pArchive); + else + err = Nu_Test(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuTestRecord(NuArchive* pArchive, NuRecordIdx recordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_TestRecord(pArchive, recordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * Strictly non-streaming read-only + * =========================================================================== + */ + +NUFXLIB_API NuError NuOpenRO(const UNICHAR* archivePathnameUNI, + NuArchive** ppArchive) +{ + NuError err; + + err = Nu_OpenRO(archivePathnameUNI, (NuArchive**) ppArchive); + + return err; +} + +NUFXLIB_API NuError NuExtractRecord(NuArchive* pArchive, NuRecordIdx recordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_ExtractRecord(pArchive, recordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_ExtractThread(pArchive, threadIdx, pDataSink); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuGetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecord(pArchive, recordIdx, ppRecord); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuGetRecordIdxByName(NuArchive* pArchive, + const char* nameMOR, NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecordIdxByName(pArchive, nameMOR, pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuGetRecordIdxByPosition(NuArchive* pArchive, uint32_t position, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecordIdxByPosition(pArchive, position, pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * Read/Write + * =========================================================================== + */ + +NUFXLIB_API NuError NuOpenRW(const UNICHAR* archivePathnameUNI, + const UNICHAR* tmpPathnameUNI, uint32_t flags, NuArchive** ppArchive) +{ + NuError err; + + err = Nu_OpenRW(archivePathnameUNI, tmpPathnameUNI, flags, + (NuArchive**) ppArchive); + + return err; +} + +NUFXLIB_API NuError NuFlush(NuArchive* pArchive, uint32_t* pStatusFlags) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Flush(pArchive, pStatusFlags); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuAbort(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Abort(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuAddRecord(NuArchive* pArchive, + const NuFileDetails* pFileDetails, NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddRecord(pArchive, pFileDetails, pRecordIdx, NULL); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuAddThread(NuArchive* pArchive, NuRecordIdx recordIdx, + NuThreadID threadID, NuDataSource* pDataSource, NuThreadIdx* pThreadIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddThread(pArchive, recordIdx, threadID, + pDataSource, pThreadIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuAddFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + const NuFileDetails* pFileDetails, short isFromRsrcFork, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddFile(pArchive, pathnameUNI, pFileDetails, + (Boolean)(isFromRsrcFork != 0), pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuRename(NuArchive* pArchive, NuRecordIdx recordIdx, + const char* pathnameMOR, char fssep) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Rename(pArchive, recordIdx, pathnameMOR, fssep); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +NUFXLIB_API NuError NuSetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_SetRecordAttr(pArchive, recordIdx, pRecordAttr); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuUpdatePresizedThread(NuArchive* pArchive, + NuThreadIdx threadIdx, NuDataSource* pDataSource, int32_t* pMaxLen) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_UpdatePresizedThread(pArchive, threadIdx, + pDataSource, pMaxLen); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuDelete(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Delete(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuDeleteRecord(NuArchive* pArchive, NuRecordIdx recordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_DeleteRecord(pArchive, recordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuDeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_DeleteThread(pArchive, threadIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * General interfaces + * =========================================================================== + */ + +NUFXLIB_API NuError NuClose(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Close(pArchive); + /* on success, pArchive has been freed */ + if (err != kNuErrNone) + Nu_ClearBusy(pArchive); + } + + return err; +} + +NUFXLIB_API NuError NuGetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + err = Nu_GetMasterHeader(pArchive, ppMasterHeader); + + return err; +} + +NUFXLIB_API NuError NuGetExtraData(NuArchive* pArchive, void** ppData) +{ + NuError err; + + if (ppData == NULL) + return kNuErrInvalidArg; + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + *ppData = pArchive->extraData; + + return err; +} + +NUFXLIB_API NuError NuSetExtraData(NuArchive* pArchive, void* pData) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->extraData = pData; + + return err; +} + +NUFXLIB_API NuError NuGetValue(NuArchive* pArchive, NuValueID ident, + NuValue* pValue) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_GetValue(pArchive, ident, pValue); + + return err; +} + +NUFXLIB_API NuError NuSetValue(NuArchive* pArchive, NuValueID ident, + NuValue value) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_SetValue(pArchive, ident, value); + + return err; +} + +NUFXLIB_API NuError NuGetAttr(NuArchive* pArchive, NuAttrID ident, + NuAttr* pAttr) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_GetAttr(pArchive, ident, pAttr); + + return err; +} + +NUFXLIB_API NuError NuDebugDumpArchive(NuArchive* pArchive) +{ +#if defined(DEBUG_MSGS) + /* skip validation checks for this one */ + Nu_DebugDumpAll(pArchive); + return kNuErrNone; +#else + /* function doesn't exist */ + return kNuErrGeneric; +#endif +} + + +/* + * =========================================================================== + * Sources and Sinks + * =========================================================================== + */ + +NUFXLIB_API NuError NuCreateDataSourceForFile(NuThreadFormat threadFormat, + uint32_t otherLen, const UNICHAR* pathnameUNI, short isFromRsrcFork, + NuDataSource** ppDataSource) +{ + return Nu_DataSourceFile_New(threadFormat, otherLen, + pathnameUNI, (Boolean)(isFromRsrcFork != 0), ppDataSource); +} + +NUFXLIB_API NuError NuCreateDataSourceForFP(NuThreadFormat threadFormat, + uint32_t otherLen, FILE* fp, long offset, long length, + NuCallback fcloseFunc, NuDataSource** ppDataSource) +{ + return Nu_DataSourceFP_New(threadFormat, otherLen, + fp, offset, length, fcloseFunc, ppDataSource); +} + +NUFXLIB_API NuError NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, + uint32_t otherLen, const uint8_t* buffer, long offset, + long length, NuCallback freeFunc, NuDataSource** ppDataSource) +{ + return Nu_DataSourceBuffer_New(threadFormat, otherLen, + buffer, offset, length, freeFunc, ppDataSource); +} + +NUFXLIB_API NuError NuFreeDataSource(NuDataSource* pDataSource) +{ + return Nu_DataSourceFree(pDataSource); +} + +NUFXLIB_API NuError NuDataSourceSetRawCrc(NuDataSource* pDataSource, + uint16_t crc) +{ + if (pDataSource == NULL) + return kNuErrInvalidArg; + Nu_DataSourceSetRawCrc(pDataSource, crc); + return kNuErrNone; +} + +NUFXLIB_API NuError NuCreateDataSinkForFile(short doExpand, NuValue convertEOL, + const UNICHAR* pathnameUNI, UNICHAR fssep, NuDataSink** ppDataSink) +{ + return Nu_DataSinkFile_New((Boolean)(doExpand != 0), convertEOL, + pathnameUNI, fssep, ppDataSink); +} + +NUFXLIB_API NuError NuCreateDataSinkForFP(short doExpand, NuValue convertEOL, + FILE* fp, NuDataSink** ppDataSink) +{ + return Nu_DataSinkFP_New((Boolean)(doExpand != 0), convertEOL, fp, + ppDataSink); +} + +NUFXLIB_API NuError NuCreateDataSinkForBuffer(short doExpand, + NuValue convertEOL, uint8_t* buffer, uint32_t bufLen, + NuDataSink** ppDataSink) +{ + return Nu_DataSinkBuffer_New((Boolean)(doExpand != 0), convertEOL, buffer, + bufLen, ppDataSink); +} + +NUFXLIB_API NuError NuFreeDataSink(NuDataSink* pDataSink) +{ + return Nu_DataSinkFree(pDataSink); +} + +NUFXLIB_API NuError NuDataSinkGetOutCount(NuDataSink* pDataSink, + uint32_t* pOutCount) +{ + if (pDataSink == NULL || pOutCount == NULL) + return kNuErrInvalidArg; + + *pOutCount = Nu_DataSinkGetOutCount(pDataSink); + return kNuErrNone; +} + + +/* + * =========================================================================== + * Non-archive operations + * =========================================================================== + */ + +NUFXLIB_API const char* NuStrError(NuError err) +{ + return Nu_StrError(err); +} + +NUFXLIB_API NuError NuGetVersion(int32_t* pMajorVersion, int32_t* pMinorVersion, + int32_t* pBugVersion, const char** ppBuildDate, const char** ppBuildFlags) +{ + return Nu_GetVersion(pMajorVersion, pMinorVersion, pBugVersion, + ppBuildDate, ppBuildFlags); +} + +NUFXLIB_API NuError NuTestFeature(NuFeature feature) +{ + NuError err = kNuErrUnsupFeature; + + switch (feature) { + case kNuFeatureCompressSQ: + #ifdef ENABLE_SQ + err = kNuErrNone; + #endif + break; + case kNuFeatureCompressLZW: + #ifdef ENABLE_LZW + err = kNuErrNone; + #endif + break; + case kNuFeatureCompressLZC: + #ifdef ENABLE_LZC + err = kNuErrNone; + #endif + break; + case kNuFeatureCompressDeflate: + #ifdef ENABLE_DEFLATE + err = kNuErrNone; + #endif + break; + case kNuFeatureCompressBzip2: + #ifdef ENABLE_BZIP2 + err = kNuErrNone; + #endif + break; + default: + err = kNuErrUnknownFeature; + break; + } + + return err; +} + +NUFXLIB_API void NuRecordCopyAttr(NuRecordAttr* pRecordAttr, + const NuRecord* pRecord) +{ + pRecordAttr->fileSysID = pRecord->recFileSysID; + /*pRecordAttr->fileSysInfo = pRecord->recFileSysInfo;*/ + pRecordAttr->access = pRecord->recAccess; + pRecordAttr->fileType = pRecord->recFileType; + pRecordAttr->extraType = pRecord->recExtraType; + pRecordAttr->createWhen = pRecord->recCreateWhen; + pRecordAttr->modWhen = pRecord->recModWhen; + pRecordAttr->archiveWhen = pRecord->recArchiveWhen; +} + +NUFXLIB_API NuError NuRecordCopyThreads(const NuRecord* pNuRecord, + NuThread** ppThreads) +{ + if (pNuRecord == NULL || ppThreads == NULL) + return kNuErrInvalidArg; + + Assert(pNuRecord->pThreads != NULL); + + *ppThreads = Nu_Malloc(NULL, pNuRecord->recTotalThreads * sizeof(NuThread)); + if (*ppThreads == NULL) + return kNuErrMalloc; + + memcpy(*ppThreads, pNuRecord->pThreads, + pNuRecord->recTotalThreads * sizeof(NuThread)); + + return kNuErrNone; +} + +NUFXLIB_API uint32_t NuRecordGetNumThreads(const NuRecord* pNuRecord) +{ + if (pNuRecord == NULL) + return -1; + + return pNuRecord->recTotalThreads; +} + +NUFXLIB_API const NuThread* NuThreadGetByIdx(const NuThread* pNuThread, + int32_t idx) +{ + if (pNuThread == NULL) + return NULL; + return &pNuThread[idx]; /* can't range-check here */ +} + +NUFXLIB_API short NuIsPresizedThreadID(NuThreadID threadID) +{ + return Nu_IsPresizedThreadID(threadID); +} + +NUFXLIB_API size_t NuConvertMORToUNI(const char* stringMOR, + UNICHAR* bufUNI, size_t bufSize) +{ + return Nu_ConvertMORToUNI(stringMOR, bufUNI, bufSize); +} + +NUFXLIB_API size_t NuConvertUNIToMOR(const UNICHAR* stringUNI, + char* bufMOR, size_t bufSize) +{ + return Nu_ConvertUNIToMOR(stringUNI, bufMOR, bufSize); +} + + +/* + * =========================================================================== + * Callback setters + * =========================================================================== + */ + +NUFXLIB_API NuCallback NuSetSelectionFilter(NuArchive* pArchive, + NuCallback filterFunc) +{ + NuError err; + NuCallback oldFunc = kNuInvalidCallback; + + /*Assert(!((uint32_t)filterFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + oldFunc = pArchive->selectionFilterFunc; + pArchive->selectionFilterFunc = filterFunc; + } + + return oldFunc; +} + +NUFXLIB_API NuCallback NuSetOutputPathnameFilter(NuArchive* pArchive, + NuCallback filterFunc) +{ + NuError err; + NuCallback oldFunc = kNuInvalidCallback; + + /*Assert(!((uint32_t)filterFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + oldFunc = pArchive->outputPathnameFunc; + pArchive->outputPathnameFunc = filterFunc; + } + + return oldFunc; +} + +NUFXLIB_API NuCallback NuSetProgressUpdater(NuArchive* pArchive, + NuCallback updateFunc) +{ + NuError err; + NuCallback oldFunc = kNuInvalidCallback; + + /*Assert(!((uint32_t)updateFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + oldFunc = pArchive->progressUpdaterFunc; + pArchive->progressUpdaterFunc = updateFunc; + } + + return oldFunc; +} + +NUFXLIB_API NuCallback NuSetErrorHandler(NuArchive* pArchive, + NuCallback errorFunc) +{ + NuError err; + NuCallback oldFunc = kNuInvalidCallback; + + /*Assert(!((uint32_t)errorFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + oldFunc = pArchive->errorHandlerFunc; + pArchive->errorHandlerFunc = errorFunc; + } + + return oldFunc; +} + +NUFXLIB_API NuCallback NuSetErrorMessageHandler(NuArchive* pArchive, + NuCallback messageHandlerFunc) +{ + NuError err; + NuCallback oldFunc = kNuInvalidCallback; + + /*Assert(!((uint32_t)messageHandlerFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + oldFunc = pArchive->messageHandlerFunc; + pArchive->messageHandlerFunc = messageHandlerFunc; + } + + return oldFunc; +} + +NUFXLIB_API NuCallback NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc) +{ + NuCallback oldFunc = kNuInvalidCallback; + /*Assert(!((uint32_t)messageHandlerFunc % 4));*/ + + oldFunc = gNuGlobalErrorMessageHandler; + gNuGlobalErrorMessageHandler = messageHandlerFunc; + return oldFunc; +} + diff --git a/nufxlib/Expand.c b/nufxlib/Expand.c new file mode 100644 index 0000000..76b9d2b --- /dev/null +++ b/nufxlib/Expand.c @@ -0,0 +1,228 @@ +/* + * 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. + * + * Expand a thread from an archive. + */ +#include "NufxLibPriv.h" + + +/* + * "Expand" an uncompressed thread. + */ +static NuError Nu_ExpandUncompressed(NuArchive* pArchive, + const NuRecord* pRecord, const NuThread* pThread, FILE* infp, + NuFunnel* pFunnel, uint16_t* pCrc) +{ + NuError err; + /*uint8_t* buffer = NULL;*/ + uint32_t count, getsize; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(infp != NULL); + Assert(pFunnel != NULL); + + /* doesn't have to be same size as funnel, but it's not a bad idea */ + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + /* quick assert for bad archive that should have been caught earlier */ + /* (filename threads are uncompressed, but compThreadEOF is buf len) */ + if (pThread->thThreadClass == kNuThreadClassData) + Assert(pThread->actualThreadEOF == pThread->thCompThreadEOF); + + count = pThread->actualThreadEOF; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_FRead(infp, pArchive->compBuf, getsize); + BailError(err); + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getsize); + err = Nu_FunnelWrite(pArchive, pFunnel, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + +/* + * Copy the "raw" data out of the thread. Unlike the preceeding function, + * this reads up to "thCompThreadEOF", and doesn't even try to compute a CRC. + */ +static NuError Nu_ExpandRaw(NuArchive* pArchive, const NuThread* pThread, + FILE* infp, NuFunnel* pFunnel) +{ + NuError err; + /*uint8_t* buffer = NULL;*/ + uint32_t count, getsize; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(infp != NULL); + Assert(pFunnel != NULL); + + /* doesn't have to be same size as funnel, but it's not a bad idea */ + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + count = pThread->thCompThreadEOF; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_FRead(infp, pArchive->compBuf, getsize); + BailError(err); + err = Nu_FunnelWrite(pArchive, pFunnel, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + + +/* + * Expand a thread from "infp" to "pFunnel", using the compression + * and stream length specified by "pThread". + */ +NuError Nu_ExpandStream(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel) +{ + NuError err = kNuErrNone; + uint16_t calcCrc; + uint16_t* pCalcCrc; + + if (!pThread->thThreadEOF && !pThread->thCompThreadEOF) { + /* somebody stored an empty file! */ + goto done; + } + + /* + * A brief history of the "threadCRC" field in the thread header: + * record versions 0 and 1 didn't use the threadCRC field + * record version 2 put the CRC of the compressed data in threadCRC + * record version 3 put the CRC of the uncompressed data in threadCRC + * + * P8 ShrinkIt uses v1, GSHK uses v3. If something ever shipped with + * v2, it didn't last long enough to leave an impression, so I'm not + * going to support it. BTW, P8 ShrinkIt always uses LZW/1, which + * puts a CRC in the compressed stream. Your uncompressed data is, + * unfortunately, unprotected before v3. + */ + calcCrc = kNuInitialThreadCRC; + pCalcCrc = NULL; + if (Nu_ThreadHasCRC(pRecord->recVersionNumber, NuGetThreadID(pThread)) && + !pArchive->valIgnoreCRC) + { + pCalcCrc = &calcCrc; + } + + err = Nu_ProgressDataExpandPrep(pArchive, pFunnel, pThread); + BailError(err); + + /* + * If we're not expanding the data, use a simple copier. + */ + if (!Nu_FunnelGetDoExpand(pFunnel)) { + Nu_FunnelSetProgressState(pFunnel, kNuProgressCopying); + err = Nu_ExpandRaw(pArchive, pThread, infp, pFunnel); + BailError(err); + goto done; + } + + Nu_FunnelSetProgressState(pFunnel, kNuProgressExpanding); + switch (pThread->thThreadFormat) { + case kNuThreadFormatUncompressed: + Nu_FunnelSetProgressState(pFunnel, kNuProgressCopying); + err = Nu_ExpandUncompressed(pArchive, pRecord, pThread, infp, pFunnel, + pCalcCrc); + break; + #ifdef ENABLE_SQ + case kNuThreadFormatHuffmanSQ: + err = Nu_ExpandHuffmanSQ(pArchive, pRecord, pThread, infp, pFunnel, + pCalcCrc); + break; + #endif + #ifdef ENABLE_LZW + case kNuThreadFormatLZW1: + case kNuThreadFormatLZW2: + err = Nu_ExpandLZW(pArchive, pRecord, pThread, infp, pFunnel, pCalcCrc); + break; + #endif + #ifdef ENABLE_LZC + case kNuThreadFormatLZC12: + case kNuThreadFormatLZC16: + err = Nu_ExpandLZC(pArchive, pRecord, pThread, infp, pFunnel, pCalcCrc); + break; + #endif + #ifdef ENABLE_DEFLATE + case kNuThreadFormatDeflate: + err = Nu_ExpandDeflate(pArchive, pRecord, pThread, infp, pFunnel, + pCalcCrc); + break; + #endif + #ifdef ENABLE_BZIP2 + case kNuThreadFormatBzip2: + err = Nu_ExpandBzip2(pArchive, pRecord, pThread, infp, pFunnel, + pCalcCrc); + break; + #endif + default: + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, err, + "compression format %u not supported", pThread->thThreadFormat); + break; + } + BailError(err); + + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + + /* + * If we have a CRC to check, check it. + */ + if (pCalcCrc != NULL) { + if (calcCrc != pThread->thThreadCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadThreadCRC)) { + err = kNuErrBadDataCRC; + Nu_ReportError(NU_BLOB, err, "expected 0x%04x, got 0x%04x", + pThread->thThreadCRC, calcCrc); + goto bail; + } + } else { + DBUG(("--- thread CRCs match\n")); + } + } + +done: + /* make sure we send a final "success" progress message at 100% */ + (void) Nu_FunnelSetProgressState(pFunnel, kNuProgressDone); + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel); + BailError(err); + +bail: + return err; +} + diff --git a/nufxlib/FileIO.c b/nufxlib/FileIO.c new file mode 100644 index 0000000..7e87685 --- /dev/null +++ b/nufxlib/FileIO.c @@ -0,0 +1,1480 @@ +/* + * 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. + * + * Operations on output (i.e. non-archive) files, largely system-specific. + * Portions taken from NuLib, including some code that Devin Reade worked on. + * + * It could be argued that "create file" should be a callback function, + * since it is so heavily system-specific, and most of the other + * system dependencies are handled by the application rather than the + * NuFX library. It would also provide a cleaner solution for renaming + * extracted files. However, the goal of the library is to do the work + * for the application, not the other way around; and while it might be + * nice to offload all direct file handling on the application, it + * complicates rather than simplifies the interface. + */ +#include "NufxLibPriv.h" + +#ifdef MAC_LIKE +# include +#endif + +/* + * For systems (e.g. Visual C++ 6.0) that don't have these standard values. + */ +#ifndef S_IRUSR +# define S_IRUSR 0400 +# define S_IWUSR 0200 +# define S_IXUSR 0100 +# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) +# define S_IRGRP (S_IRUSR >> 3) +# define S_IWGRP (S_IWUSR >> 3) +# define S_IXGRP (S_IXUSR >> 3) +# define S_IRWXG (S_IRWXU >> 3) +# define S_IROTH (S_IRGRP >> 3) +# define S_IWOTH (S_IWGRP >> 3) +# define S_IXOTH (S_IXGRP >> 3) +# define S_IRWXO (S_IRWXG >> 3) +#endif +#ifndef S_ISREG +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + +/* + * =========================================================================== + * DateTime conversions + * =========================================================================== + */ + +/* + * Dates and times in a NuFX archive are always considered to be in the + * local time zone. The use of GMT time stamps would have been more + * appropriate for an archive, but local time works well enough. + * + * Regarding Y2K on the Apple II: + * + * Dave says P8 drivers should return year values in the range 0..99, where + * 40..99 = 1940..1999, and 0..39 = 2000..2039. Year values 100..127 should + * never be used. For ProDOS 8, the year 2000 is "00". + * + * The IIgs ReadTimeHex call uses "year minus 1900". For GS/OS, the year + * 2000 is "100". + * + * The NuFX file type note says the archive format should work like + * The IIgs ReadTimeHex call, which uses "year minus 1900" as its + * format. GS/ShrinkIt v1.1 uses the IIgs date calls, and so stores the + * year 2000 as "100". P8 ShrinkIt v3.4 uses the P8 mapping, and stores + * it as "0". Neither really quite understands what the other is doing. + * + * For our purposes, we will follow the NuFX standard and emit "100" + * for the year 2000, but will accept and understand "0" as well. + */ + + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +/* + * Convert from local time in a NuDateTime struct to GMT seconds since 1970. + * + * If the conversion is invalid, "*pWhen" is set to zero. + */ +static void Nu_DateTimeToGMTSeconds(const NuDateTime* pDateTime, time_t* pWhen) +{ + struct tm tmbuf; + time_t when; + + Assert(pDateTime != NULL); + Assert(pWhen != NULL); + + tmbuf.tm_sec = pDateTime->second; + tmbuf.tm_min = pDateTime->minute; + tmbuf.tm_hour = pDateTime->hour; + tmbuf.tm_mday = pDateTime->day +1; + tmbuf.tm_mon = pDateTime->month; + tmbuf.tm_year = pDateTime->year; + if (pDateTime->year < 40) + tmbuf.tm_year += 100; /* P8 uses 0-39 for 2000-2039 */ + tmbuf.tm_wday = 0; + tmbuf.tm_yday = 0; + tmbuf.tm_isdst = -1; /* let it figure DST and time zone */ + + #if defined(HAVE_MKTIME) + when = mktime(&tmbuf); + #elif defined(HAVE_TIMELOCAL) + when = timelocal(&tmbuf); + #else + # error "need time converter" + #endif + + if (when == (time_t) -1) + *pWhen = 0; + else + *pWhen = when; +} + +/* + * Convert from GMT seconds since 1970 to local time in a NuDateTime struct. + */ +static void Nu_GMTSecondsToDateTime(const time_t* pWhen, NuDateTime *pDateTime) +{ + struct tm* ptm; + + Assert(pWhen != NULL); + Assert(pDateTime != NULL); + + #if defined(HAVE_LOCALTIME_R) && defined(USE_REENTRANT_CALLS) + struct tm res; + ptm = localtime_r(pWhen, &res); + #else + /* NOTE: not thread-safe */ + ptm = localtime(pWhen); + #endif + pDateTime->second = ptm->tm_sec; + pDateTime->minute = ptm->tm_min; + pDateTime->hour = ptm->tm_hour; + pDateTime->day = ptm->tm_mday -1; + pDateTime->month = ptm->tm_mon; + pDateTime->year = ptm->tm_year; + pDateTime->extra = 0; + pDateTime->weekDay = ptm->tm_wday +1; +} +#endif + + +/* + * Fill in the current time. + */ +void Nu_SetCurrentDateTime(NuDateTime* pDateTime) +{ + Assert(pDateTime != NULL); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + time_t now = time(NULL); + Nu_GMTSecondsToDateTime(&now, pDateTime); + } +#else + #error "Port this" +#endif +} + + +/* + * Returns "true" if "pWhen1" is older than "pWhen2". Returns false if + * "pWhen1" is the same age or newer than "pWhen2". + * + * On systems with mktime, it would be straightforward to convert the dates + * to time in seconds, and compare them that way. However, I don't want + * to rely on that function too heavily, so we just compare fields. + */ +Boolean Nu_IsOlder(const NuDateTime* pWhen1, const NuDateTime* pWhen2) +{ + long result, year1, year2; + + /* adjust for P8 ShrinkIt Y2K problem */ + year1 = pWhen1->year; + if (year1 < 40) + year1 += 100; + year2 = pWhen2->year; + if (year2 < 40) + year2 += 100; + + result = year1 - year2; + if (!result) + result = pWhen1->month - pWhen2->month; + if (!result) + result = pWhen1->day - pWhen2->day; + if (!result) + result = pWhen1->hour - pWhen2->hour; + if (!result) + result = pWhen1->minute - pWhen2->minute; + if (!result) + result = pWhen1->second - pWhen2->second; + + if (result < 0) + return true; + return false; +} + + +/* + * =========================================================================== + * Get/set file info + * =========================================================================== + */ + +/* + * System-independent (mostly) file info struct. + */ +typedef struct NuFileInfo { + Boolean isValid; /* init to "false", set "true" after we get data */ + + Boolean isRegularFile; /* is this a regular file? */ + Boolean isDirectory; /* is this a directory? */ + Boolean isForked; /* does file have a non-empty resource fork? */ + + uint32_t dataEof; + + NuDateTime modWhen; + mode_t unixMode; /* UNIX-style permissions */ +} NuFileInfo; + +#define kDefaultFileType 0 /* "NON" */ +#define kDefaultAuxType 0 /* $0000 */ + + +/* + * Determine whether the record has both data and resource forks. + * + * TODO: if we're not using "mask dataless", scanning threads may not + * get the right answer, because GSHK omits theads for zero-length forks. + * We could check pRecord->recStorageType, though we have to be careful + * because that's overloaded for disk images. In any event, the result + * from this method isn't relevant unless we're trying to use forked + * files on the native filesystem. + */ +static Boolean Nu_IsForkedFile(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThread* pThread; + NuThreadID threadID; + Boolean gotData, gotRsrc; + int i; + + gotData = gotRsrc = false; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + pThread = Nu_GetThread(pRecord, i); + Assert(pThread != NULL); + + threadID = NuMakeThreadID(pThread->thThreadClass,pThread->thThreadKind); + if (threadID == kNuThreadIDDataFork) + gotData = true; + else if (threadID == kNuThreadIDRsrcFork) + gotRsrc = true; + } + + if (gotData && gotRsrc) + return true; + else + return false; +} + + +#if defined(MAC_LIKE) +# if defined(HAS_RESOURCE_FORKS) +/* + * String to append to the filename to access the resource fork. + * + * This appears to be the correct way to access the resource fork, since + * at least OS X 10.1. Up until 10.7 ("Lion", July 2011), you could also + * access the fork with "/rsrc". + */ +static const char kMacRsrcPath[] = "/..namedfork/rsrc"; + +/* + * Generates the resource fork pathname from the file path. + * + * The caller must free the string returned. + */ +static UNICHAR* GetResourcePath(const UNICHAR* pathnameUNI) +{ + Assert(pathnameUNI != NULL); + + // sizeof(kMacRsrcPath) includes the string and the terminating null byte + const size_t bufLen = + strlen(pathnameUNI) * sizeof(UNICHAR) + sizeof(kMacRsrcPath); + char* buf; + + buf = (char*) malloc(bufLen); + snprintf(buf, bufLen, "%s%s", pathnameUNI, kMacRsrcPath); + return buf; +} +# endif /*HAS_RESOURCE_FORKS*/ + +/* + * Due to historical reasons, the XATTR_FINDERINFO_NAME (defined to be + * ``com.apple.FinderInfo'') extended attribute must be 32 bytes; see the + * ATTR_CMN_FNDRINFO section in getattrlist(2). + * + * The FinderInfo block is the concatenation of a FileInfo structure + * and an ExtendedFileInfo (or ExtendedFolderInfo) structure -- see + * ATTR_CMN_FNDRINFO in getattrlist(2). + * + * All we're really interested in is the file type and creator code, + * which are stored big-endian in the first 8 bytes. + */ +static const int kFinderInfoSize = 32; + +/* + * Set the file type and creator type. + */ +static NuError Nu_SetFinderInfo(NuArchive* pArchive, const NuRecord* pRecord, + const UNICHAR* pathnameUNI) +{ + uint8_t fiBuf[kFinderInfoSize]; + + size_t actual = getxattr(pathnameUNI, XATTR_FINDERINFO_NAME, + fiBuf, sizeof(fiBuf), 0, 0); + if (actual == (size_t) -1 && errno == ENOATTR) { + // doesn't yet have Finder info + memset(fiBuf, 0, sizeof(fiBuf)); + } else if (actual != kFinderInfoSize) { + Nu_ReportError(NU_BLOB, errno, + "Finder info on '%s' returned %d", pathnameUNI, (int) actual); + return kNuErrFile; + } + + uint8_t proType = (uint8_t) pRecord->recFileType; + uint16_t proAux = (uint16_t) pRecord->recExtraType; + + /* + * Attempt to use one of the convenience types. If nothing matches, + * use the generic pdos/pXYZ approach. Note that PSYS/PS16 will + * lose the file's aux type. + * + * I'm told this is from page 336 of _Programmer's Reference for + * System 6.0_. + */ + uint8_t* fileTypeBuf = fiBuf; + uint8_t* creatorBuf = fiBuf + 4; + + memcpy(creatorBuf, "pdos", 4); + if (proType == 0x00 && proAux == 0x0000) { + memcpy(fileTypeBuf, "BINA", 4); + } else if (proType == 0x04 && proAux == 0x0000) { + memcpy(fileTypeBuf, "TEXT", 4); + } else if (proType == 0xff) { + memcpy(fileTypeBuf, "PSYS", 4); + } else if (proType == 0xb3 && (proAux & 0xff00) != 0xdb00) { + memcpy(fileTypeBuf, "PS16", 4); + } else if (proType == 0xd7 && proAux == 0x0000) { + memcpy(fileTypeBuf, "MIDI", 4); + } else if (proType == 0xd8 && proAux == 0x0000) { + memcpy(fileTypeBuf, "AIFF", 4); + } else if (proType == 0xd8 && proAux == 0x0001) { + memcpy(fileTypeBuf, "AIFC", 4); + } else if (proType == 0xe0 && proAux == 0x0005) { + memcpy(creatorBuf, "dCpy", 4); + memcpy(fileTypeBuf, "dImg", 4); + } else { + fileTypeBuf[0] = 'p'; + fileTypeBuf[1] = proType; + fileTypeBuf[2] = (uint8_t) (proAux >> 8); + fileTypeBuf[3] = (uint8_t) proAux; + } + + if (setxattr(pathnameUNI, XATTR_FINDERINFO_NAME, fiBuf, sizeof(fiBuf), + 0, 0) != 0) + { + Nu_ReportError(NU_BLOB, errno, + "Unable to set Finder info on '%s'", pathnameUNI); + return kNuErrFile; + } + + return kNuErrNone; +} +#endif /*MAC_LIKE*/ + + +/* + * Get the file info into a NuFileInfo struct. Fields which are + * inappropriate for the current system are set to default values. + */ +static NuError Nu_GetFileInfo(NuArchive* pArchive, const UNICHAR* pathnameUNI, + NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + Assert(pArchive != NULL); + Assert(pathnameUNI != NULL); + Assert(pFileInfo != NULL); + + pFileInfo->isValid = false; + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + struct stat sbuf; + int cc; + + cc = stat(pathnameUNI, &sbuf); + if (cc) { + if (errno == ENOENT) + err = kNuErrFileNotFound; + else + err = kNuErrFileStat; + goto bail; + } + + pFileInfo->isRegularFile = false; + if (S_ISREG(sbuf.st_mode)) + pFileInfo->isRegularFile = true; + pFileInfo->isDirectory = false; + if (S_ISDIR(sbuf.st_mode)) + pFileInfo->isDirectory = true; + + /* BUG: should check for 32-bit overflow from 64-bit off_t */ + pFileInfo->dataEof = sbuf.st_size; + pFileInfo->isForked = false; +# if defined(MAC_LIKE) && defined(HAS_RESOURCE_FORKS) + if (!pFileInfo->isDirectory) { + /* + * Check for the presence of a resource fork. You can check + * these from a terminal with "ls -l@" -- look for the + * "com.apple.ResourceFork" attribute. + * + * We can either use getxattr() and check for the presence of + * the attribute, or get the file length with stat(). I + * don't know if xattr has always worked with resource forks, + * so we'll stick with stat for now. + */ + UNICHAR* rsrcPath = GetResourcePath(pathnameUNI); + + struct stat res_sbuf; + + if (stat(rsrcPath, &res_sbuf) == 0) { + pFileInfo->isForked = (res_sbuf.st_size != 0); + } + + free(rsrcPath); + } +# endif + Nu_GMTSecondsToDateTime(&sbuf.st_mtime, &pFileInfo->modWhen); + pFileInfo->unixMode = sbuf.st_mode; + pFileInfo->isValid = true; + } +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Determine whether a specific fork in the file exists. + * + * On systems that don't support forked files, the "checkRsrcFork" argument + * is ignored. If forked files are supported, and we are extracting a + * file with data and resource forks, we only claim it exists if it has + * nonzero length. + */ +static NuError Nu_FileForkExists(NuArchive* pArchive, + const UNICHAR* pathnameUNI, Boolean isForkedFile, Boolean checkRsrcFork, + Boolean* pExists, NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(pathnameUNI != NULL); + Assert(checkRsrcFork == true || checkRsrcFork == false); + Assert(pExists != NULL); + Assert(pFileInfo != NULL); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +# if !defined(MAC_LIKE) + /* + * On Unix and Windows we ignore "isForkedFile" and "checkRsrcFork". + * The file must not exist at all. + */ + Assert(pArchive->lastFileCreatedUNI == NULL); +# endif + + *pExists = true; + err = Nu_GetFileInfo(pArchive, pathnameUNI, pFileInfo); + if (err == kNuErrFileNotFound) { + err = kNuErrNone; + *pExists = false; + } + +# if defined(MAC_LIKE) + /* + * On Mac OS X, we'll use the resource fork, but we may not want to + * overwrite existing data. + */ + if (*pExists && checkRsrcFork) { + *pExists = pFileInfo->isForked; + } +# endif + +#elif defined(__ORCAC__) + /* + * If the file doesn't exist, great. If it does, and "lastFileCreated" + * matches up with this one, then we know that it exists because we + * created it. + * + * This is great unless the record has two data forks or something + * equally dopey, so we check to be sure that the fork we want to + * put the data into is currently empty. + * + * It is possible, though asinine, for a Mac OS or GS/OS extraction + * program to put the data and resource forks of a record into + * separate files, so we can't just assume that because we wrote + * the data fork to file A it is okay for file B to exist. That's + * why we compare the pathname instead of just remembering that + * we already created a file for this record. + */ + #error "Finish me" + +#else + #error "Port this" +#endif + + return err; +} + + +/* + * Set the dates on a file according to what's in the record. + */ +static NuError Nu_SetFileDates(NuArchive* pArchive, const NuRecord* pRecord, + const UNICHAR* pathnameUNI) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pathnameUNI != NULL); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + struct utimbuf utbuf; + + /* ignore create time, and set access time equal to mod time */ + Nu_DateTimeToGMTSeconds(&pRecord->recModWhen, &utbuf.modtime); + utbuf.actime = utbuf.modtime; + + /* only do it if the NuDateTime was valid */ + if (utbuf.modtime) { + if (utime(pathnameUNI, &utbuf) < 0) { + Nu_ReportError(NU_BLOB, errno, + "Unable to set time stamp on '%s'", pathnameUNI); + err = kNuErrFileSetDate; + goto bail; + } + } + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Returns "true" if the record is locked (in the ProDOS sense). + * + * Bits 31-8 reserved, must be zero + * Bit 7 (D) 1 = destroy enabled + * Bit 6 (R) 1 = rename enabled + * Bit 5 (B) 1 = file needs to be backed up + * Bits 4-3 reserved, must be zero + * Bit 2 (I) 1 = file is invisible + * Bit 1 (W) 1 = write enabled + * Bit 0 (R) 1 = read enabled + * + * A "locked" file would be 00?00001, "unlocked" 11?00011, with many + * possible variations. For our purposes, we treat all files as unlocked + * unless they match the classic "locked" bit pattern. + */ +static Boolean Nu_IsRecordLocked(const NuRecord* pRecord) +{ + if (pRecord->recAccess == 0x21L || pRecord->recAccess == 0x01L) + return true; + else + return false; +} + +/* + * Set the file access permissions based on what's in the record. + * + * This assumes that the file is currently writable, so we only need + * to do something if the original file was "locked". + */ +static NuError Nu_SetFileAccess(NuArchive* pArchive, const NuRecord* pRecord, + const UNICHAR* pathnameUNI) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pathnameUNI != NULL); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + /* only need to do something if the file was "locked" */ + if (Nu_IsRecordLocked(pRecord)) { + mode_t mask; + + /* set it to 444, modified by umask */ + mask = umask(0); + umask(mask); + //DBUG(("+++ chmod '%s' %03o (mask=%03o)\n", pathname, + // (S_IRUSR | S_IRGRP | S_IROTH) & ~mask, mask)); + if (chmod(pathnameUNI, (S_IRUSR | S_IRGRP | S_IROTH) & ~mask) < 0) { + Nu_ReportError(NU_BLOB, errno, + "unable to set access for '%s' to %03o", pathnameUNI, + (int) mask); + err = kNuErrFileSetAccess; + goto bail; + } + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * =========================================================================== + * Create/open an output file + * =========================================================================== + */ + +/* + * Prepare an existing file for writing. + * + * Generally this just involves ensuring that the file is writable. If + * this is a convenient place to truncate it, we should do that too. + * + * 20150103: we don't seem to be doing the truncation here, so prepRsrc + * is unused. + */ +static NuError Nu_PrepareForWriting(NuArchive* pArchive, + const UNICHAR* pathnameUNI, Boolean prepRsrc, NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(pathnameUNI != NULL); + Assert(prepRsrc == true || prepRsrc == false); + Assert(pFileInfo != NULL); + + Assert(pFileInfo->isValid == true); + + /* don't go playing with directories, pipes, etc */ + if (pFileInfo->isRegularFile != true) + return kNuErrNotRegularFile; + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + if (!(pFileInfo->unixMode & S_IWUSR)) { + /* make it writable by owner, plus whatever it was before */ + if (chmod(pathnameUNI, S_IWUSR | pFileInfo->unixMode) < 0) { + Nu_ReportError(NU_BLOB, errno, + "unable to set access for '%s'", pathnameUNI); + err = kNuErrFileSetAccess; + goto bail; + } + } + + return kNuErrNone; + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Invoke the system-dependent directory creation function. + */ +static NuError Nu_Mkdir(NuArchive* pArchive, const char* dir) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(dir != NULL); + +#if defined(UNIX_LIKE) + if (mkdir(dir, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH) < 0) { + err = errno ? errno : kNuErrDirCreate; + Nu_ReportError(NU_BLOB, err, "Unable to create dir '%s'", dir); + goto bail; + } + +#elif defined(WINDOWS_LIKE) + if (mkdir(dir) < 0) { + err = errno ? errno : kNuErrDirCreate; + Nu_ReportError(NU_BLOB, err, "Unable to create dir '%s'", dir); + goto bail; + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Create a single subdirectory if it doesn't exist. If the next-highest + * subdirectory level doesn't exist either, cut down the pathname and + * recurse. + */ +static NuError Nu_CreateSubdirIFN(NuArchive* pArchive, + const UNICHAR* pathStartUNI, const char* pathEnd, char fssep) +{ + NuError err = kNuErrNone; + NuFileInfo fileInfo; + char* tmpBuf = NULL; + + Assert(pArchive != NULL); + Assert(pathStartUNI != NULL); + Assert(pathEnd != NULL); + Assert(fssep != '\0'); + + /* pathStart might have whole path, but we only want up to "pathEnd" */ + tmpBuf = strdup(pathStartUNI); + tmpBuf[pathEnd - pathStartUNI +1] = '\0'; + + err = Nu_GetFileInfo(pArchive, tmpBuf, &fileInfo); + if (err == kNuErrFileNotFound) { + /* dir doesn't exist; move up a level and check parent */ + pathEnd = strrchr(tmpBuf, fssep); + if (pathEnd != NULL) { + pathEnd--; + Assert(pathEnd >= tmpBuf); + err = Nu_CreateSubdirIFN(pArchive, tmpBuf, pathEnd, fssep); + BailError(err); + } + + /* parent is taken care of; create this one */ + err = Nu_Mkdir(pArchive, tmpBuf); + BailError(err); + } else if (err != kNuErrNone) { + goto bail; + } else { + /* file does exist, make sure it's a directory */ + Assert(fileInfo.isValid == true); + if (!fileInfo.isDirectory) { + err = kNuErrNotDir; + Nu_ReportError(NU_BLOB, err, "Unable to create path '%s'", tmpBuf); + goto bail; + } + } + +bail: + Nu_Free(pArchive, tmpBuf); + return err; +} + +/* + * Create subdirectories, if needed. The paths leading up to the filename + * in "pathname" will be created. + * + * If "pathname" is just a filename, or the set of directories matches + * the last directory we created, we don't do anything. + */ +static NuError Nu_CreatePathIFN(NuArchive* pArchive, const UNICHAR* pathnameUNI, + UNICHAR fssep) +{ + NuError err = kNuErrNone; + const char* pathStart; + const char* pathEnd; + + Assert(pArchive != NULL); + Assert(pathnameUNI != NULL); + Assert(fssep != '\0'); + + pathStart = pathnameUNI; + +#if !defined(MAC_LIKE) /* On the Mac, if it's a full path, treat it like one */ + // 20150103: not sure what use case this is for + if (pathnameUNI[0] == fssep) + pathStart++; +#endif + + /* NOTE: not expecting names like "foo/bar/ack/", with terminating fssep */ + pathEnd = strrchr(pathStart, fssep); + if (pathEnd == NULL) { + /* no subdirectory components found */ + goto bail; + } + pathEnd--; + + Assert(pathEnd >= pathStart); + if (pathEnd - pathStart < 0) + goto bail; + + /* + * On some filesystems, strncasecmp would be appropriate here. However, + * this is meant solely as an optimization to avoid extra stat() calls, + * so we want to use the most restrictive case. + */ + if (pArchive->lastDirCreatedUNI && + strncmp(pathStart, pArchive->lastDirCreatedUNI, + pathEnd - pathStart +1) == 0) + { + /* we created this one recently, don't do it again */ + goto bail; + } + + /* + * Test to determine which directories exist. The most likely case + * is that some or all of the components have already been created, + * so we start with the last one and work backward. + */ + err = Nu_CreateSubdirIFN(pArchive, pathStart, pathEnd, fssep); + BailError(err); + +bail: + return err; +} + + +/* + * Open the file for writing, possibly truncating it. + */ +static NuError Nu_OpenFileForWrite(NuArchive* pArchive, + const UNICHAR* pathnameUNI, Boolean openRsrc, FILE** pFp) +{ +#if defined(MAC_LIKE) && defined(HAS_RESOURCE_FORKS) + if (openRsrc) { + UNICHAR* rsrcPath = GetResourcePath(pathnameUNI); + *pFp = fopen(rsrcPath, kNuFileOpenWriteTrunc); + free(rsrcPath); + } else { + *pFp = fopen(pathnameUNI, kNuFileOpenWriteTrunc); + } +#else + *pFp = fopen(pathnameUNI, kNuFileOpenWriteTrunc); +#endif + if (*pFp == NULL) + return errno ? errno : -1; + return kNuErrNone; +} + + +/* + * Open an output file and prepare it for writing. + * + * There are a number of things to take into consideration, including + * deal with "file exists" conditions, handling Mac/IIgs file types, + * coping with resource forks on extended files, and handling the + * "freshen" option that requires us to only update files that are + * older than what we have. + */ +NuError Nu_OpenOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, const UNICHAR* newPathnameUNI, UNICHAR newFssep, + FILE** pFp) +{ + NuError err = kNuErrNone; + Boolean exists, isForkedFile, extractingRsrc = false; + NuFileInfo fileInfo; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pThread != NULL); + Assert(newPathnameUNI != NULL); + Assert(pFp != NULL); + + /* set up some defaults, in case something goes wrong */ + errorStatus.operation = kNuOpExtract; + errorStatus.err = kNuErrInternal; + errorStatus.sysErr = 0; + errorStatus.message = NULL; + errorStatus.pRecord = pRecord; + errorStatus.pathnameUNI = newPathnameUNI; + errorStatus.origPathname = NULL; + errorStatus.filenameSeparator = newFssep; + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = true; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = true; + errorStatus.canOverwrite = true; + + /* decide if this is a forked file (i.e. has *both* forks) */ + isForkedFile = Nu_IsForkedFile(pArchive, pRecord); + + /* decide if we're extracting a resource fork */ + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDRsrcFork) + { + extractingRsrc = true; + } + + /* + * Determine whether the file and fork already exists. If the file + * is one we just created, and the fork we want to write to is + * empty, this will *not* set "exists". + */ + fileInfo.isValid = false; + err = Nu_FileForkExists(pArchive, newPathnameUNI, isForkedFile, + extractingRsrc, &exists, &fileInfo); + BailError(err); + + if (exists) { + Assert(fileInfo.isValid == true); + + /* + * The file exists when it shouldn't. Decide what to do, based + * on the options configured by the application. + */ + + /* + * Start by checking to see if we're willing to overwrite older files. + * If not, see if the application wants to rename the file, or force + * the overwrite. Most likely they'll just want to skip it. + */ + if ((pArchive->valOnlyUpdateOlder) && + !Nu_IsOlder(&fileInfo.modWhen, &pRecord->recModWhen)) + { + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.err = kNuErrNotNewer; + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + err = kNuErrNotNewer; + goto bail; + } + } + + /* If they "might" allow overwrites, and they have an error-handling + * callback defined, call that to find out what they want to do + * here. Options include skipping the file, overwriting the file, + * and extracting to a different file. + */ + if (pArchive->valHandleExisting == kNuMaybeOverwrite) { + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.err = kNuErrFileExists; + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, return an error to the caller */ + err = kNuErrFileExists; + goto bail; + } + } else if (pArchive->valHandleExisting == kNuNeverOverwrite) { + err = kNuErrSkipped; + goto bail; + } + } else { + /* + * The file doesn't exist. If we're doing a "freshen" from the + * archive, we don't want to create a new file, so we return an + * error to the user instead. However, we give the application + * a chance to straighten things out. Most likely they'll just + * return kNuSkip. + */ + if (pArchive->valHandleExisting == kNuMustOverwrite) { + DBUG(("+++ can't freshen nonexistent file '%s'\n", newPathnameUNI)); + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.err = kNuErrDuplicateNotFound; + + /* give them a chance to rename */ + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, return an error to the caller */ + err = kNuErrDuplicateNotFound; + goto bail; + } + } + } + + Assert(err == kNuErrNone); + + /* + * After the above, if the file exists then we need to prepare it for + * writing. On some systems -- notably those with forked files -- it + * may be easiest to delete the entire file and start over. On + * simpler systems, an (optional) chmod followed by an open that + * truncates the file should be sufficient. + * + * If the file didn't exist, we need to be sure that the path leading + * up to its eventual location exists. This might require creating + * several directories. We distinguish the case of "file isn't there" + * from "file is there but fork isn't" by seeing if we were able to + * get valid file info. + */ + if (exists) { + Assert(fileInfo.isValid == true); + err = Nu_PrepareForWriting(pArchive, newPathnameUNI, extractingRsrc, + &fileInfo); + BailError(err); + } else if (!fileInfo.isValid) { + err = Nu_CreatePathIFN(pArchive, newPathnameUNI, newFssep); + BailError(err); + } + + /* + * Open sesame. + */ + err = Nu_OpenFileForWrite(pArchive, newPathnameUNI, extractingRsrc, pFp); + BailError(err); + + +#if defined(HAS_RESOURCE_FORKS) + pArchive->lastFileCreatedUNI = newPathnameUNI; +#endif + +bail: + if (err != kNuErrNone) { + if (err != kNuErrSkipped && err != kNuErrRename && + err != kNuErrFileExists) + { + Nu_ReportError(NU_BLOB, err, "Unable to open '%s'%s", + newPathnameUNI, extractingRsrc ? " (rsrc fork)" : ""); + } + } + return err; +} + + +/* + * Close the output file, adjusting the modification date and access + * permissions as needed. + * + * On GS/OS and Mac OS, we may need to set the file type here, depending on + * how much we managed to do when the file was first created. IIRC, + * the GS/OS Open call should allow setting the file type. + * + * BUG: on GS/OS, if we set the file access after writing the data fork, + * we may not be able to open the same file for writing the rsrc fork. + * We can't suppress setting the access permissions, because we don't know + * if the application will want to write both forks to the same file, or + * for that matter will want to write the resource fork at all. Looks + * like we will have to be smart enough to reset the access permissions + * when writing a rsrc fork to a file with just a data fork. This isn't + * quite right, but it's close enough. + */ +NuError Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + FILE* fp, const UNICHAR* pathnameUNI) +{ + NuError err; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(fp != NULL); + + fclose(fp); + + err = Nu_SetFileDates(pArchive, pRecord, pathnameUNI); + BailError(err); + +#if defined(MAC_LIKE) + /* could also do this earlier and pass the fd for fsetxattr */ + /* NOTE: must do this before Nu_SetFileAccess */ + err = Nu_SetFinderInfo(pArchive, pRecord, pathnameUNI); + BailError(err); +#endif + + err = Nu_SetFileAccess(pArchive, pRecord, pathnameUNI); + BailError(err); + +bail: + return kNuErrNone; +} + +/* + * =========================================================================== + * Open an input file + * =========================================================================== + */ + +/* + * Open the file for reading, in "binary" mode when necessary. + */ +static NuError Nu_OpenFileForRead(NuArchive* pArchive, + const UNICHAR* pathnameUNI, Boolean openRsrc, FILE** pFp) +{ + *pFp = fopen(pathnameUNI, kNuFileOpenReadOnly); + if (*pFp == NULL) + return errno ? errno : -1; + return kNuErrNone; +} + + +/* + * Open an input file and prepare it for reading. + * + * If the file can't be found, we give the application an opportunity to + * skip the absent file, retry, or abort the whole thing. + */ +NuError Nu_OpenInputFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + Boolean openRsrc, FILE** pFp) +{ + NuError err = kNuErrNone; + NuError openErr = kNuErrNone; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pArchive != NULL); + Assert(pathnameUNI != NULL); + Assert(pFp != NULL); + +#if defined(MAC_LIKE) && defined(HAS_RESOURCE_FORKS) + UNICHAR* rsrcPath = NULL; + if (openRsrc) { + rsrcPath = GetResourcePath(pathnameUNI); + pathnameUNI = rsrcPath; + } +#endif + +retry: + /* + * Open sesame. + */ + err = Nu_OpenFileForRead(pArchive, pathnameUNI, openRsrc, pFp); + if (err == kNuErrNone) /* success! */ + goto bail; + + if (err == ENOENT) + openErr = kNuErrFileNotFound; + + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.operation = kNuOpAdd; + errorStatus.err = openErr; + errorStatus.sysErr = 0; + errorStatus.message = NULL; + errorStatus.pRecord = NULL; + errorStatus.pathnameUNI = pathnameUNI; + errorStatus.origPathname = NULL; + errorStatus.filenameSeparator = '\0'; + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = true; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = false; + errorStatus.canOverwrite = false; + + DBUG(("--- invoking error handler function\n")); + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + goto retry; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuRename: + case kNuOverwrite: + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + DBUG(("+++ no error callback in OpenInputFile\n")); + } + +bail: + if (err == kNuErrNone) { + Assert(*pFp != NULL); + } else { + if (err != kNuErrSkipped && err != kNuErrRename && + err != kNuErrFileExists) + { + Nu_ReportError(NU_BLOB, err, "Unable to open '%s'%s", + pathnameUNI, openRsrc ? " (rsrc fork)" : ""); + } + } +#if defined(MAC_LIKE) && defined(HAS_RESOURCE_FORKS) + free(rsrcPath); +#endif + return err; +} + + +/* + * =========================================================================== + * Delete and rename files + * =========================================================================== + */ + +/* + * Delete a file. + */ +NuError Nu_DeleteFile(const UNICHAR* pathnameUNI) +{ +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + int cc; + + DBUG(("--- Deleting '%s'\n", pathnameUNI)); + + cc = unlink(pathnameUNI); + if (cc < 0) + return errno ? errno : -1; + else + return kNuErrNone; +#else + #error "Port this" +#endif +} + +/* + * Rename a file from "fromPath" to "toPath". + */ +NuError Nu_RenameFile(const UNICHAR* fromPathUNI, const UNICHAR* toPathUNI) +{ +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + int cc; + + DBUG(("--- Renaming '%s' to '%s'\n", fromPathUNI, toPathUNI)); + + cc = rename(fromPathUNI, toPathUNI); + if (cc < 0) + return errno ? errno : -1; + else + return kNuErrNone; +#else + #error "Port this" +#endif +} + + +/* + * =========================================================================== + * NuError wrappers for std functions + * =========================================================================== + */ + +/* + * Wrapper for ftell(). + */ +NuError Nu_FTell(FILE* fp, long* pOffset) +{ + Assert(fp != NULL); + Assert(pOffset != NULL); + + errno = 0; + *pOffset = ftell(fp); + if (*pOffset < 0) { + Nu_ReportError(NU_NILBLOB, errno, "file ftell failed"); + return errno ? errno : kNuErrFileSeek; + } + return kNuErrNone; +} + +/* + * Wrapper for fseek(). + */ +NuError Nu_FSeek(FILE* fp, long offset, int ptrname) +{ + Assert(fp != NULL); + Assert(ptrname == SEEK_SET || ptrname == SEEK_CUR || ptrname == SEEK_END); + + errno = 0; + if (fseek(fp, offset, ptrname) < 0) { + Nu_ReportError(NU_NILBLOB, errno, + "file fseek(%ld, %d) failed", offset, ptrname); + return errno ? errno : kNuErrFileSeek; + } + return kNuErrNone; +} + +/* + * Wrapper for fread(). Note the arguments resemble read(2) rather than the + * slightly silly ones used by fread(3S). + */ +NuError Nu_FRead(FILE* fp, void* buf, size_t nbyte) +{ + size_t result; + + errno = 0; + result = fread(buf, nbyte, 1, fp); + if (result != 1) + return errno ? errno : kNuErrFileRead; + return kNuErrNone; +} + +/* + * Wrapper for fwrite(). Note the arguments resemble write(2) rather than the + * slightly silly ones used by fwrite(3S). + */ +NuError Nu_FWrite(FILE* fp, const void* buf, size_t nbyte) +{ + size_t result; + + errno = 0; + result = fwrite(buf, nbyte, 1, fp); + if (result != 1) + return errno ? errno : kNuErrFileWrite; + return kNuErrNone; +} + +/* + * =========================================================================== + * Misc functions + * =========================================================================== + */ + +/* + * Copy a section from one file to another. + */ +NuError Nu_CopyFileSection(NuArchive* pArchive, FILE* dstFp, FILE* srcFp, + long length) +{ + NuError err; + long readLen; + + Assert(pArchive != NULL); + Assert(dstFp != NULL); + Assert(srcFp != NULL); + Assert(length >= 0); /* can be == 0, e.g. empty data fork from HFS */ + + /* nice big buffer, for speed... could use getc/putc for simplicity */ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + DBUG(("+++ Copying %ld bytes\n", length)); + + while (length) { + readLen = length > kNuGenCompBufSize ? kNuGenCompBufSize : length; + + err = Nu_FRead(srcFp, pArchive->compBuf, readLen); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, + "Nu_FRead failed while copying file section " + "(fp=0x%08lx, readLen=%ld, length=%ld, err=%d)\n", + (long) srcFp, readLen, length, err); + goto bail; + } + err = Nu_FWrite(dstFp, pArchive->compBuf, readLen); + BailError(err); + + length -= readLen; + } + +bail: + return err; +} + + +/* + * Find the length of an open file. + * + * On UNIX it would be easier to just call fstat(), but fseek is portable. + * + * Only useful for files < 2GB in size. + * + * (pArchive is only used for BailError message reporting, so it's okay + * to call here with a NULL pointer if the archive isn't open yet.) + */ +NuError Nu_GetFileLength(NuArchive* pArchive, FILE* fp, long* pLength) +{ + NuError err; + long oldpos; + + Assert(fp != NULL); + Assert(pLength != NULL); + + err = Nu_FTell(fp, &oldpos); + BailError(err); + + err = Nu_FSeek(fp, 0, SEEK_END); + BailError(err); + + err = Nu_FTell(fp, pLength); + BailError(err); + + err = Nu_FSeek(fp, oldpos, SEEK_SET); + BailError(err); + +bail: + return err; +} + + +/* + * Truncate an open file. This differs from ftruncate() in that it takes + * a FILE* instead of an fd, and the length is a long instead of off_t. + */ +NuError Nu_TruncateOpenFile(FILE* fp, long length) +{ + #if defined(HAVE_FTRUNCATE) + if (ftruncate(fileno(fp), length) < 0) + return errno ? errno : -1; + return kNuErrNone; + #elif defined(HAVE_CHSIZE) + if (chsize(fileno(fp), length) < 0) + return errno ? errno : -1; + return kNuErrNone; + #else + /* not fatal; return this to indicate that it's an unsupported operation */ + return kNuErrInternal; + #endif +} + diff --git a/nufxlib/Funnel.c b/nufxlib/Funnel.c new file mode 100644 index 0000000..58a0797 --- /dev/null +++ b/nufxlib/Funnel.c @@ -0,0 +1,904 @@ +/* + * 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. + * + * Implementation of NuFunnel, NuStraw and ProgressUpdater. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Progress updater + * =========================================================================== + */ + +/* + * Initialize the fields in a ProgressData structure, prior to compressing + * data into a record. + * + * The same structure will be used when expanding all threads in a given + * record. + */ +NuError Nu_ProgressDataInit_Compress(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const UNICHAR* origPathnameUNI, const UNICHAR* pathnameUNI) +{ + const char* cp; + + Assert(pProgressData != NULL); + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(origPathnameUNI != NULL); + Assert(pathnameUNI != NULL); + + pProgressData->pRecord = pRecord; + + pProgressData->origPathnameUNI = origPathnameUNI; + pProgressData->pathnameUNI = pathnameUNI; + cp = strrchr(pathnameUNI, NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + if (cp == NULL || *(cp+1) == '\0') + pProgressData->filenameUNI = pProgressData->pathnameUNI; + else + pProgressData->filenameUNI = cp+1; + + pProgressData->operation = kNuOpAdd; + pProgressData->state = kNuProgressPreparing; + pProgressData->uncompressedLength = 0; + pProgressData->uncompressedProgress = 0; + + pProgressData->compress.threadFormat = (NuThreadFormat)-1; + + /* ya know... if this is NULL, none of the above matters much */ + pProgressData->progressFunc = pArchive->progressUpdaterFunc; + + return kNuErrNone; +} + + +/* + * Initialize the fields in a ProgressData structure, prior to expanding + * data from a record. + * + * The same structure will be used when expanding all threads in a given + * record. + */ +NuError Nu_ProgressDataInit_Expand(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const UNICHAR* newPathnameUNI, UNICHAR newFssep, + const UNICHAR* origPathnameUNI, NuValue convertEOL) +{ + const NuThread* pThreadIter; + const char* cp; + int i; + + Assert(pProgressData != NULL); + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(newPathnameUNI != NULL); + Assert(origPathnameUNI != NULL); + Assert(newFssep != 0); + + pProgressData->pRecord = pRecord; + pProgressData->expand.pThread = NULL; + + pProgressData->origPathnameUNI = origPathnameUNI; + pProgressData->pathnameUNI = newPathnameUNI; + cp = strrchr(newPathnameUNI, newFssep); + if (cp == NULL || *(cp+1) == '\0') + pProgressData->filenameUNI = newPathnameUNI; + else + pProgressData->filenameUNI = cp+1; + + pProgressData->expand.convertEOL = convertEOL; + + /* total up the data threads */ + pProgressData->expand.totalCompressedLength = 0; + pProgressData->expand.totalUncompressedLength = 0; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + pThreadIter = Nu_GetThread(pRecord, i); + if (pThreadIter->thThreadClass != kNuThreadClassData) + continue; + pProgressData->expand.totalCompressedLength += pThreadIter->thCompThreadEOF; + pProgressData->expand.totalUncompressedLength += pThreadIter->actualThreadEOF; + } + + pProgressData->operation = kNuOpExtract; + if (pArchive->testMode) + pProgressData->operation = kNuOpTest; + pProgressData->state = kNuProgressPreparing; + pProgressData->uncompressedLength = 0; + pProgressData->uncompressedProgress = 0; + + /* ya know... if this is NULL, none of the above matters much */ + pProgressData->progressFunc = pArchive->progressUpdaterFunc; + + return kNuErrNone; +} + + +/* + * Do the setup on a ProgressData prior to compressing a thread. + */ +NuError Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw, + NuThreadFormat threadFormat, uint32_t sourceLen) +{ + NuProgressData* pProgressData; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(sourceLen < 32767*65536); + + pProgressData = pStraw->pProgress; + if (pProgressData == NULL) + return kNuErrNone; + + pProgressData->uncompressedLength = sourceLen; + pProgressData->compress.threadFormat = threadFormat; + + return kNuErrNone; +} + +/* + * Do the setup on a ProgressData prior to expanding a thread. + * + * "pThread" is the thread being expanded. + */ +NuError Nu_ProgressDataExpandPrep(NuArchive* pArchive, NuFunnel* pFunnel, + const NuThread* pThread) +{ + NuProgressData* pProgressData; + + Assert(pArchive != NULL); + Assert(pFunnel != NULL); + Assert(pThread != NULL); + + pProgressData = pFunnel->pProgress; + if (pProgressData == NULL) + return kNuErrNone; + + /*pProgressData->compressedLength = pThread->thCompThreadEOF;*/ + pProgressData->uncompressedLength = pThread->actualThreadEOF; + pProgressData->expand.pThread = pThread; + + return kNuErrNone; +} + +/* + * Compute a completion percentage. + */ +static int Nu_ComputePercent(uint32_t total, uint32_t progress) +{ + uint32_t perc; + + if (!total) + return 0; + + if (total < 21474836) { + perc = (progress * 100 + 50) / total; + if (perc > 100) + perc = 100; + } else { + perc = progress / (total / 100); + if (perc > 100) + perc = 100; + } + + return (int) perc; +} + +/* + * Send the initial progress message, before the output file is opened + * (when extracting) or the input file is opened (when adding). + */ +NuError Nu_SendInitialProgress(NuArchive* pArchive, NuProgressData* pProgress) +{ + NuResult result; + + Assert(pArchive != NULL); + Assert(pProgress != NULL); + + if (pProgress->progressFunc == NULL) + return kNuErrNone; + + pProgress->percentComplete = Nu_ComputePercent( + pProgress->uncompressedLength, pProgress->uncompressedProgress); + + result = (*pProgress->progressFunc)(pArchive, (NuProgressData*) pProgress); + + if (result == kNuSkip) + return kNuErrSkipped; /* [dunno how well this works] */ + if (result == kNuAbort) + return kNuErrAborted; + + return kNuErrNone; +} + + +/* + * =========================================================================== + * NuFunnel object + * =========================================================================== + */ + +/* + * Allocate and initialize a Funnel. + */ +NuError Nu_FunnelNew(NuArchive* pArchive, NuDataSink* pDataSink, + NuValue convertEOL, NuValue convertEOLTo, NuProgressData* pProgress, + NuFunnel** ppFunnel) +{ + NuError err = kNuErrNone; + NuFunnel* pFunnel = NULL; + + Assert(ppFunnel != NULL); + Assert(pDataSink != NULL); + Assert(convertEOL == kNuConvertOff || + convertEOL == kNuConvertOn || + convertEOL == kNuConvertAuto); + + pFunnel = Nu_Calloc(pArchive, sizeof(*pFunnel)); + BailAlloc(pFunnel); + pFunnel->buffer = Nu_Malloc(pArchive, kNuFunnelBufSize); + BailAlloc(pFunnel->buffer); + + pFunnel->pDataSink = pDataSink; + pFunnel->convertEOL = convertEOL; + pFunnel->convertEOLTo = convertEOLTo; + pFunnel->convertEOLFrom = kNuEOLUnknown; + pFunnel->pProgress = pProgress; + + pFunnel->checkStripHighASCII = (pArchive->valStripHighASCII != 0); + pFunnel->doStripHighASCII = false; /* determined on first write */ + + pFunnel->isFirstWrite = true; + +bail: + if (err != kNuErrNone) + Nu_FunnelFree(pArchive, pFunnel); + else + *ppFunnel = pFunnel; + return err; +} + + +/* + * Free a Funnel. + * + * The data should already have been written; it's not the duty of a + * "free" function to flush data out. + */ +NuError Nu_FunnelFree(NuArchive* pArchive, NuFunnel* pFunnel) +{ + if (pFunnel == NULL) + return kNuErrNone; + +#ifdef DEBUG_MSGS + if (pFunnel->bufCount) + Nu_ReportError(NU_BLOB_DEBUG, kNuErrNone, + "freeing non-empty funnel"); +#endif + + Nu_Free(pArchive, pFunnel->buffer); + Nu_Free(pArchive, pFunnel); + + return kNuErrNone; +} + + +#if 0 +/* + * Set the maximum amount of output we're willing to push through the + * funnel. Attempts to write more than this many bytes will fail. This + * allows us to bail out as soon as it's apparent that compression is + * failing and is actually resulting in a larger file. + */ +void Nu_FunnelSetMaxOutput(NuFunnel* pFunnel, uint32_t maxBytes) +{ + Assert(pFunnel != NULL); + Assert(maxBytes > 0); + + pFunnel->outMax = maxBytes; + if (pFunnel->outCount >= pFunnel->outMax) + pFunnel->outMaxExceeded = true; + else + pFunnel->outMaxExceeded = false; +} +#endif + + +/* + * Check to see if this is a high-ASCII file. To qualify, EVERY + * character must have its high bit set, except for spaces (0x20). + * (The exception is courtesy Glen Bredon's "Merlin".) + */ +static Boolean Nu_CheckHighASCII(const NuFunnel* pFunnel, const uint8_t* buffer, + uint32_t count) +{ + Boolean isHighASCII; + + Assert(buffer != NULL); + Assert(count != 0); + Assert(pFunnel->checkStripHighASCII); + + isHighASCII = true; + while (count--) { + if ((*buffer & 0x80) == 0 && *buffer != 0x20) { + isHighASCII = false; + break; + } + + buffer++; + } + + return isHighASCII; +} + +/* + * Table determining what's a binary character and what isn't. It would + * possibly be more compact to generate this from a simple description, + * but I'm hoping static/const data will end up in the code segment and + * save space on the heap. + * + * This corresponds to less-316's ISO-latin1 "8bcccbcc18b95.33b.". This + * may be too loose by itself; we may want to require that the lower-ASCII + * values appear in higher proportions than the upper-ASCII values. + * Otherwise we run the risk of converting a binary file with specific + * properties. (Note that "upper-ASCII" refers to umlauts and other + * accented characters, not DOS 3.3 "high ASCII".) + * + * The auto-detect mechanism will never be perfect though, so there's not + * much point in tweaking it to death. + */ +static const char gNuIsBinary[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, /* ^@-^O */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P-^_ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* - / */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - ? */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ - O */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* P - _ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* p - DEL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 */ +}; + +#define kNuMaxUpperASCII 1 /* max #of binary chars per 100 bytes */ +#define kNuMinConvThreshold 40 /* min of 40 chars for auto-detect */ +/* + * Decide, based on the contents of the buffer, whether we should do an + * EOL conversion on the data. + * + * We need to decide if we are looking at text data, and if so, what kind + * of line terminator is in use. + * + * If we don't have enough data to make a determination, don't mess with it. + * (Thought for the day: add a "bias" flag, based on the NuRecord fileType, + * that causes us to handle borderline or sub-min-threshold cases more + * reasonably. If it's of type TXT, it's probably text.) + * + * We try to figure out whether it's CR, LF, or CRLF, so that we can + * skip the CPU-intensive conversion process if it isn't necessary. + * + * We will also enable a "high-ASCII" stripper if requested. This is + * only enabled when EOL conversions are enabled. + * + * Returns kConvEOLOff or kConvEOLOn, and sets pFunnel->doStripHighASCII + * if pFunnel->CheckStripHighASCII is set. + */ +static NuValue Nu_DetermineConversion(NuFunnel* pFunnel, const uint8_t* buffer, + uint32_t count) +{ + uint32_t bufCount, numBinary, numLF, numCR; + Boolean isHighASCII; + uint8_t val; + + if (count < kNuMinConvThreshold) + return kNuConvertOff; + + /* + * Check to see if the buffer is all high-ASCII characters. If it is, + * we want to strip characters before we test them below. + */ + if (pFunnel->checkStripHighASCII) { + isHighASCII = Nu_CheckHighASCII(pFunnel, buffer, count); + DBUG(("+++ determined isHighASCII=%d\n", isHighASCII)); + } else { + isHighASCII = false; + DBUG(("+++ not even checking isHighASCII\n")); + } + + bufCount = count; + numBinary = numLF = numCR = 0; + while (bufCount--) { + val = *buffer++; + if (isHighASCII) + val &= 0x7f; + if (gNuIsBinary[val]) + numBinary++; + if (val == kNuCharLF) + numLF++; + if (val == kNuCharCR) + numCR++; + } + + /* if #found is > #allowed, it's a binary file */ + if (count < 100) { + /* use simplified check on files between kNuMinConvThreshold and 100 */ + if (numBinary > kNuMaxUpperASCII) + return kNuConvertOff; + } else if (numBinary > (count / 100) * kNuMaxUpperASCII) + return kNuConvertOff; + + /* + * If our "convert to" setting is the same as what we're converting + * from, we can turn off the converter and speed things up. + * + * These are simplistic, but this is intended as an optimization. We + * will blow it if the input has lots of CRs and LFs scattered about, + * and they just happen to be in equal amounts, but it's not clear + * to me that an automatic EOL conversion makes sense on that sort + * of file anyway. + * + * None of this applies if we also need to do a high-ASCII conversion. + */ + if (isHighASCII) { + pFunnel->doStripHighASCII = true; + } else { + if (numLF && !numCR) + pFunnel->convertEOLFrom = kNuEOLLF; + else if (!numLF && numCR) + pFunnel->convertEOLFrom = kNuEOLCR; + else if (numLF && numLF == numCR) + pFunnel->convertEOLFrom = kNuEOLCRLF; + else + pFunnel->convertEOLFrom = kNuEOLUnknown; + } + + return kNuConvertOn; +} + +/* + * Write a block of data to the appropriate output device. Test for + * excessive data, and raise "outMaxExceeded" if we overrun. + * + * This is either a Funnel function or a DataSink function, depending on + * your perspective. + */ +static inline void Nu_FunnelPutBlock(NuFunnel* pFunnel, const uint8_t* buf, + uint32_t len) +{ + Assert(pFunnel != NULL); + Assert(pFunnel->pDataSink != NULL); + Assert(buf != NULL); + Assert(len > 0); + +#if 0 + if (pFunnel->outMax) { + if (pFunnel->outMaxExceeded) + return; + if (pFunnel->outCount + len > pFunnel->outMax) { + pFunnel->outMaxExceeded = true; + return; + } + } + pFunnel->outCount += len; +#endif + + Nu_DataSinkPutBlock(pFunnel->pDataSink, buf, len); +} + + +/* + * Output the EOL marker requested for this system. + */ +static inline void Nu_PutEOL(NuFunnel* pFunnel) +{ + uint8_t ch; + + if (pFunnel->convertEOLTo == kNuEOLCR) { + ch = kNuCharCR; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else if (pFunnel->convertEOLTo == kNuEOLLF) { + ch = kNuCharLF; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else if (pFunnel->convertEOLTo == kNuEOLCRLF) { + ch = kNuCharCR; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + ch = kNuCharLF; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else { + Assert(0); + } +} + +/* + * Write a buffer of data, using the EOL conversion associated with the + * funnel (if any). + * + * When converting to the system's EOL convention, we take anything + * that looks like an EOL mark and convert it. Doesn't matter if it's + * CR, LF, or CRLF; all three get converted to whatever the system uses. + */ +static NuError Nu_FunnelWriteConvert(NuFunnel* pFunnel, const uint8_t* buffer, + uint32_t count) +{ + NuError err = kNuErrNone; + uint32_t progressCount = count; + + /*if (pFunnel->outMaxExceeded) + return kNuErrOutMax;*/ + + if (pFunnel->isFirstWrite) { + /* + * This is the first write/flush we've done on this Funnel. + * Check the data we have buffered to decide whether or not + * we want to do text conversions. + */ + if (pFunnel->convertEOL == kNuConvertAuto) { + pFunnel->convertEOL = Nu_DetermineConversion(pFunnel, buffer,count); + DBUG(("+++ DetermineConversion --> %ld / %ld (%d)\n", + pFunnel->convertEOL, pFunnel->convertEOLFrom, + pFunnel->doStripHighASCII)); + + if (pFunnel->convertEOLFrom == pFunnel->convertEOLTo) { + DBUG(("+++ Switching redundant converter off\n")); + pFunnel->convertEOL = kNuConvertOff; + } + /* put it where the progress meter can see it */ + if (pFunnel->pProgress != NULL) + pFunnel->pProgress->expand.convertEOL = pFunnel->convertEOL; + } else if (pFunnel->convertEOL == kNuConvertOn) { + if (pFunnel->checkStripHighASCII) { + /* assume this part of the buffer is representative */ + pFunnel->doStripHighASCII = Nu_CheckHighASCII(pFunnel, + buffer, count); + } else { + Assert(!pFunnel->doStripHighASCII); + } + DBUG(("+++ Converter is on, convHighASCII=%d\n", + pFunnel->doStripHighASCII)); + } + } + Assert(pFunnel->convertEOL != kNuConvertAuto); /* on or off now */ + pFunnel->isFirstWrite = false; + + if (pFunnel->convertEOL == kNuConvertOff) { + /* write it straight */ + Nu_FunnelPutBlock(pFunnel, buffer, count); + } else { + /* do the EOL conversion and optional high-bit stripping */ + Boolean lastCR = pFunnel->lastCR; /* make local copy */ + uint8_t uch; + int mask; + + if (pFunnel->doStripHighASCII) + mask = 0x7f; + else + mask = 0xff; + + /* + * We could get a significant speed improvement here by writing + * non-EOL chars as a larger block instead of single bytes. + */ + while (count--) { + uch = (*buffer) & mask; + + if (uch == kNuCharCR) { + Nu_PutEOL(pFunnel); + lastCR = true; + } else if (uch == kNuCharLF) { + if (!lastCR) + Nu_PutEOL(pFunnel); + lastCR = false; + } else { + Nu_FunnelPutBlock(pFunnel, &uch, 1); + lastCR = false; + } + buffer++; + } + pFunnel->lastCR = lastCR; /* save copy */ + + } + + /*if (pFunnel->outMaxExceeded) + err = kNuErrOutMax;*/ + + err = Nu_DataSinkGetError(pFunnel->pDataSink); + + /* update progress counter with pre-LFCR count */ + if (err == kNuErrNone && pFunnel->pProgress != NULL) + pFunnel->pProgress->uncompressedProgress += progressCount; + + return err; +} + + +/* + * Flush any data currently in the funnel. + */ +NuError Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel) +{ + NuError err = kNuErrNone; + + if (!pFunnel->bufCount) + goto bail; + + err = Nu_FunnelWriteConvert(pFunnel, pFunnel->buffer, pFunnel->bufCount); + BailError(err); + + pFunnel->bufCount = 0; + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel); + /* fall through with error */ + +bail: + return err; +} + + +/* + * Write a bunch of bytes into a funnel. They will be held in the buffer + * if they fit, or flushed out the bottom if not. + */ +NuError Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel, + const uint8_t* buffer, uint32_t count) +{ + NuError err = kNuErrNone; + + /*pFunnel->inCount += count;*/ + + /* + * If it will fit into the buffer, just copy it in. + */ + if (pFunnel->bufCount + count < kNuFunnelBufSize) { + if (count == 1) /* minor optimization */ + *(pFunnel->buffer + pFunnel->bufCount) = *buffer; + else + memcpy(pFunnel->buffer + pFunnel->bufCount, buffer, count); + pFunnel->bufCount += count; + goto bail; + } else { + /* + * Won't fit. We have to flush what we have, and we can either + * blow out what we were just given or put it at the start of + * the buffer. + */ + if (pFunnel->bufCount) { + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + } else { + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel); + BailError(err); + } + + Assert(pFunnel->bufCount == 0); + + if (count >= kNuFunnelBufSize / 4) { + /* it's more than 25% of the buffer, just write it now */ + err = Nu_FunnelWriteConvert(pFunnel, buffer, count); + BailError(err); + } else { + memcpy(pFunnel->buffer, buffer, count); + pFunnel->bufCount = count; + } + goto bail; + } + +bail: + return err; +} + + +/* + * Set the Funnel's progress state. + */ +NuError Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state) +{ + Assert(pFunnel != NULL); + + if (pFunnel->pProgress == NULL) + return kNuErrNone; + + pFunnel->pProgress->state = state; + + return kNuErrNone; +} + + +/* + * Send a progress update to the application, if they're interested. + */ +NuError Nu_FunnelSendProgressUpdate(NuArchive* pArchive, NuFunnel* pFunnel) +{ + NuProgressData* pProgress; + + Assert(pArchive != NULL); + Assert(pFunnel != NULL); + + pProgress = pFunnel->pProgress; + if (pProgress == NULL) + return kNuErrNone; /* no progress meter attached */ + + /* don't continue if they're not accepting progress messages */ + if (pProgress->progressFunc == NULL) + return kNuErrNone; + + /* other than the choice of arguments, it's pretty much the same story */ + return Nu_SendInitialProgress(pArchive, pProgress); +} + + +/* + * Pull the "doExpand" parameter out of the data source. + */ +Boolean Nu_FunnelGetDoExpand(NuFunnel* pFunnel) +{ + Assert(pFunnel != NULL); + Assert(pFunnel->pDataSink != NULL); + + return Nu_DataSinkGetDoExpand(pFunnel->pDataSink); +} + + +/* + * =========================================================================== + * NuStraw object + * =========================================================================== + */ + +/* + * Allocate and initialize a Straw. + */ +NuError Nu_StrawNew(NuArchive* pArchive, NuDataSource* pDataSource, + NuProgressData* pProgress, NuStraw** ppStraw) +{ + NuError err = kNuErrNone; + NuStraw* pStraw = NULL; + + Assert(ppStraw != NULL); + Assert(pDataSource != NULL); + + pStraw = Nu_Calloc(pArchive, sizeof(*pStraw)); + BailAlloc(pStraw); + pStraw->pDataSource = pDataSource; + pStraw->pProgress = pProgress; + pStraw->lastProgress = 0; + pStraw->lastDisplayed = 0; + +bail: + if (err != kNuErrNone) + Nu_StrawFree(pArchive, pStraw); + else + *ppStraw = pStraw; + return err; +} + +/* + * Free a Straw. + */ +NuError Nu_StrawFree(NuArchive* pArchive, NuStraw* pStraw) +{ + if (pStraw == NULL) + return kNuErrNone; + + /* we don't own the data source or progress meter */ + Nu_Free(pArchive, pStraw); + + return kNuErrNone; +} + + +/* + * Set the Straw's progress state. + */ +NuError Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state) +{ + Assert(pStraw != NULL); + Assert(pStraw->pProgress != NULL); + + pStraw->pProgress->state = state; + + return kNuErrNone; +} + +/* + * Send a progress update to the application, if they're interested. + */ +NuError Nu_StrawSendProgressUpdate(NuArchive* pArchive, NuStraw* pStraw) +{ + NuProgressData* pProgress; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + + pProgress = pStraw->pProgress; + if (pProgress == NULL) + return kNuErrNone; /* no progress meter attached */ + + /* don't continue if they're not accepting progress messages */ + if (pProgress->progressFunc == NULL) + return kNuErrNone; + + /* other than the choice of arguments, it's pretty much the same story */ + return Nu_SendInitialProgress(pArchive, pProgress); +} + + +/* + * Read data from a straw. + */ +NuError Nu_StrawRead(NuArchive* pArchive, NuStraw* pStraw, uint8_t* buffer, + long len) +{ + NuError err; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(buffer != NULL); + Assert(len > 0); + + /* + * No buffering going on, so this is straightforward. + */ + + err = Nu_DataSourceGetBlock(pStraw->pDataSource, buffer, len); + BailError(err); + + /* + * Progress updating for adding is a little more complicated than + * for extracting. When extracting, the funnel controls the size + * of the output buffer, and only pushes an update when the output + * buffer fills. Here, we don't know how much will be asked for at + * a time, so we have to pace the updates or we risk flooding the + * application. + * + * We also have another problem: we want to indicate how much data + * has been processed, not how much data is *about* to be processed. + * So we have to set the percentage based on how much was requested + * on the previous call. (This assumes that whatever they asked for + * last time has already been fully processed.) + */ + if (pStraw->pProgress != NULL) { + pStraw->pProgress->uncompressedProgress = pStraw->lastProgress; + pStraw->lastProgress += len; + + if (!pStraw->pProgress->uncompressedProgress || + (pStraw->pProgress->uncompressedProgress - pStraw->lastDisplayed + > (kNuFunnelBufSize * 3 / 4))) + { + err = Nu_StrawSendProgressUpdate(pArchive, pStraw); + pStraw->lastDisplayed = pStraw->pProgress->uncompressedProgress; + BailError(err); + } + + } + +bail: + return err; +} + + +/* + * Rewind a straw. This rewinds the underlying data source, and resets + * some progress counters. + */ +NuError Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw) +{ + Assert(pStraw != NULL); + Assert(pStraw->pDataSource != NULL); + + pStraw->lastProgress = 0; + pStraw->lastDisplayed = 0; + + return Nu_DataSourceRewind(pStraw->pDataSource); +} + diff --git a/nufxlib/INSTALL b/nufxlib/INSTALL new file mode 100644 index 0000000..50dbe43 --- /dev/null +++ b/nufxlib/INSTALL @@ -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. + diff --git a/nufxlib/Lzc.c b/nufxlib/Lzc.c new file mode 100644 index 0000000..5798ba3 --- /dev/null +++ b/nufxlib/Lzc.c @@ -0,0 +1,1094 @@ +/* + * 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. + * + * This is the LZW implementation found in the UNIX "compress" command, + * sometimes referred to as "LZC". GS/ShrinkIt v1.1 can unpack threads + * in LZC format, P8 ShrinkIt cannot. The only other application that + * is known to create LZC threads is the original NuLib. + * + * There's a lot of junk in here for the sake of smaller systems (e.g. MSDOS) + * and pre-ANSI compilers. For the most part it has been left unchanged. + * I have done some minor reformatting, and have undone the authors' + * penchant for assigning variables inside function call statements, but + * for the most part it is as it was. (A much cleaner implementation + * could probably be derived by adapting the NufxLib Lzw.c code...) + */ +#include "NufxLibPriv.h" + +#ifdef ENABLE_LZC + +/*#define DEBUG_LZC*/ + +/* + * Selected definitions from compress.h. + */ +typedef uint16_t CODE; +typedef uint8_t UCHAR; +typedef uint32_t INTCODE; +typedef uint32_t HASH; +typedef int FLAG; + +#ifndef FALSE /* let's get some sense to this */ +#define FALSE 0 +#define TRUE !FALSE +#endif + +#define CONST const +#ifndef FAR +# define FAR +#endif +#define NULLPTR(type) ((type FAR *) NULL) +#define ALLOCTYPE void + +#define INITBITS 9 +#define MINBITS 12 +#define MAXMAXBITS 16 +#define MAXBITS MAXMAXBITS +#define DFLTBITS MAXBITS + +#define UNUSED ((CODE)0) /* Indicates hash table value unused */ +#define CLEAR ((CODE)256) /* Code requesting table to be cleared */ +#define FIRSTFREE ((CODE)257) /* First free code for token encoding */ +#define MAXTOKLEN 512 /* Max chars in token; size of buffer */ +#define OK kNuErrNone /* Result codes from functions: */ + +#define BIT_MASK 0x1f +#define BLOCK_MASK 0x80 + +#define CHECK_GAP 10000L /* ratio check interval, for COMP40 */ + +static UCHAR gNu_magic_header[] = { 0x1F,0x9D }; + +/* don't need these */ +/*#define SPLIT_HT 1*/ +/*#define SPLIT_PFX 1*/ +/*#define COMP40 1*/ + +#define NOMEM kNuErrMalloc /* Ran out of memory */ +#define TOKTOOBIG kNuErrBadData /* Token longer than MAXTOKLEN chars */ +#define READERR kNuErrFileRead /* I/O error on input */ +#define WRITEERR kNuErrFileWrite /* I/O error on output */ +#define CODEBAD kNuErrBadData /* Infile contained a bad token code */ +#define TABLEBAD kNuErrInternal /* The tables got corrupted (!) */ +#define NOSAVING kNuErrNone /* no saving in file size */ + + +/* + * Normally in COMPUSI.UNI. + */ +static inline ALLOCTYPE FAR * +Nu_LZC_emalloc(NuArchive* pArchive, uint32_t x, int y) +{ + return Nu_Malloc(pArchive, x*y); +} +static inline void +Nu_LZC_efree(NuArchive* pArchive, ALLOCTYPE FAR * ptr) +{ + Nu_Free(pArchive, ptr); +} + +/*@H************************ < COMPRESS API > **************************** +* $@(#) compapi.c,v 4.3d 90/01/18 03:00:00 don Release ^ * +* * +* compress : compapi.c * +* * +* port by : Donald J. Gloistein * +* * +* Source, Documentation, Object Code: * +* released to Public Domain. This code is based on code as documented * +* below in release notes. * +* * +*--------------------------- Module Description --------------------------* +* Contains source code for modified Lempel-Ziv method (LZW) compression * +* and decompression. * +* * +* This code module can be maintained to keep current on releases on the * +* Unix system. The command shell and dos modules can remain the same. * +* * +*--------------------------- Implementation Notes --------------------------* +* * +* compiled with : compress.h compress.fns compress.c * +* linked with : compress.obj compusi.obj * +* * +* problems: * +* * +* * +* CAUTION: Uses a number of defines for access and speed. If you change * +* anything, make sure about side effects. * +* * +* Compression: * +* Algorithm: use open addressing double hashing (no chaining) on the * +* prefix code / next character combination. We do a variant of Knuth's * +* algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime * +* secondary probe. Here, the modular division first probe is gives way * +* to a faster exclusive-or manipulation. * +* Also block compression with an adaptive reset was used in original code, * +* whereby the code table is cleared when the compression ration decreases * +* but after the table fills. This was removed from this edition. The table * +* is re-sized at this point when it is filled , and a special CLEAR code is * +* generated for the decompressor. This results in some size difference from * +* straight version 4.0 joe Release. But it is fully compatible in both v4.0 * +* and v4.01 * +* * +* Decompression: * +* This routine adapts to the codes in the file building the "string" table * +* on-the-fly; requiring no table to be stored in the compressed file. The * +* tables used herein are shared with those of the compress() routine. * +* * +* Initials ---- Name --------------------------------- * +* DjG Donald J. Gloistein, current port to MsDos 16 bit * +* Plus many others, see rev.hst file for full list * +* LvR Lyle V. Rains, many thanks for improved implementation * +* of the compression and decompression routines. * +*************************************************************************@H*/ + +#include + +/* + * LZC state, largely variables with non-local scope. + */ +typedef struct LZCState { + NuArchive* pArchive; + int doCalcCRC; + uint16_t crc; + + /* compression */ + NuStraw* pStraw; + FILE* outfp; + long uncompRemaining; + + /* expansion */ + FILE* infp; + NuFunnel* pFunnel; + uint16_t* pCrc; + long compRemaining; + + + /* + * Globals from Compress sources. + */ + int offset; + long int in_count ; /* length of input */ + long int bytes_out; /* length of compressed output */ + + INTCODE prefxcode, nextfree; + INTCODE highcode; + INTCODE maxcode; + HASH hashsize; + int bits; + + char FAR *sfx; + + #if (SPLIT_PFX) + CODE FAR *pfx[2]; + #else + CODE FAR *pfx; + #endif + + #if (SPLIT_HT) + CODE FAR *ht[2]; + #else + CODE FAR *ht; + #endif + + #ifdef COMP40 + long int ratio; + long checkpoint; /* initialized to CHECK_GAP */ + #endif + + #ifdef DEBUG_LZC + int debug; /* initialized to FALSE */ + #endif + + NuError exit_stat; + + int maxbits; /* initialized to DFLTBITS */ + int block_compress; /* initialized to BLOCK_MASK */ + + /* + * Static local variables. Some of these were explicitly initialized + * to zero. + */ + INTCODE oldmaxcode; /* alloc_tables */ + HASH oldhashsize; /* alloc_tables */ + int oldbits; /* putcode */ + UCHAR outbuf[MAXBITS]; /* putcode */ + int prevbits; /* nextcode */ + int size; /* nextcode */ + UCHAR inbuf[MAXBITS]; /* nextcode */ +} LZCState; + + +/* + * The following two parameter tables are the hash table sizes and + * maximum code values for various code bit-lengths. The requirements + * are that Hashsize[n] must be a prime number and Maxcode[n] must be less + * than Maxhash[n]. Table occupancy factor is (Maxcode - 256)/Maxhash. + * Note: I am using a lower Maxcode for 16-bit codes in order to + * keep the hash table size less than 64k entries. + */ +static CONST HASH gNu_hs[] = { + 0x13FF, /* 12-bit codes, 75% occupancy */ + 0x26C3, /* 13-bit codes, 80% occupancy */ + 0x4A1D, /* 14-bit codes, 85% occupancy */ + 0x8D0D, /* 15-bit codes, 90% occupancy */ + 0xFFD9 /* 16-bit codes, 94% occupancy, 6% of code values unused */ +}; +#define Hashsize(maxb) (gNu_hs[(maxb) -MINBITS]) + +static CONST INTCODE gNu_mc[] = { + 0x0FFF, /* 12-bit codes */ + 0x1FFF, /* 13-bit codes */ + 0x3FFF, /* 14-bit codes */ + 0x7FFF, /* 15-bit codes */ + 0xEFFF /* 16-bit codes, 6% of code values unused */ +}; +#define Maxcode(maxb) (gNu_mc[(maxb) -MINBITS]) + +#ifdef __STDC__ +#ifdef DEBUG_LZC +#define allocx(type, ptr, size) \ + (((ptr) = (type FAR *) Nu_LZC_emalloc(pArchive, (uint32_t)(size),sizeof(type))) == NULLPTR(type) \ + ? (DBUG(("%s: "#ptr" -- ", "LZC")), NOMEM) : OK \ + ) +#else +#define allocx(type,ptr,size) \ + (((ptr) = (type FAR *) Nu_LZC_emalloc(pArchive, (uint32_t)(size),sizeof(type))) == NULLPTR(type) \ + ? NOMEM : OK \ + ) +#endif +#else +#define allocx(type,ptr,size) \ + (((ptr) = (type FAR *) Nu_LZC_emalloc(pArchive, (uint32_t)(size),sizeof(type))) == NULLPTR(type) \ + ? NOMEM : OK \ + ) +#endif + +#define free_array(type,ptr,offset) \ + if (ptr != NULLPTR(type)) { \ + Nu_LZC_efree(pArchive, (ALLOCTYPE FAR *)((ptr) + (offset))); \ + (ptr) = NULLPTR(type); \ + } + + /* + * Macro to allocate new memory to a pointer with an offset value. + */ +#define alloc_array(type, ptr, size, offset) \ + ( allocx(type, ptr, (size) - (offset)) != OK \ + ? NOMEM \ + : (((ptr) -= (offset)), OK) \ + ) + +/*static char FAR *sfx = NULLPTR(char) ;*/ +#define suffix(code) pLzcState->sfx[code] + + +#if (SPLIT_PFX) + /*static CODE FAR *pfx[2] = {NULLPTR(CODE), NULLPTR(CODE)};*/ +#else + /*static CODE FAR *pfx = NULLPTR(CODE);*/ +#endif + + +#if (SPLIT_HT) + /*static CODE FAR *ht[2] = {NULLPTR(CODE),NULLPTR(CODE)};*/ +#else + /*static CODE FAR *ht = NULLPTR(CODE);*/ +#endif + + +static int Nu_LZC_alloc_tables(LZCState* pLzcState, INTCODE newmaxcode, + HASH newhashsize) +{ + NuArchive* pArchive = pLzcState->pArchive; + /*static INTCODE oldmaxcode = 0;*/ + /*static HASH oldhashsize = 0;*/ + + if (newhashsize > pLzcState->oldhashsize) { +#if (SPLIT_HT) + free_array(CODE,pLzcState->ht[1], 0); + free_array(CODE,pLzcState->ht[0], 0); +#else + free_array(CODE,pLzcState->ht, 0); +#endif + pLzcState->oldhashsize = 0; + } + + if (newmaxcode > pLzcState->oldmaxcode) { +#if (SPLIT_PFX) + free_array(CODE,pLzcState->pfx[1], 128); + free_array(CODE,pLzcState->pfx[0], 128); +#else + free_array(CODE,pLzcState->pfx, 256); +#endif + free_array(char,pLzcState->sfx, 256); + + if ( alloc_array(char, pLzcState->sfx, newmaxcode + 1, 256) +#if (SPLIT_PFX) + || alloc_array(CODE, pLzcState->pfx[0], (newmaxcode + 1) / 2, 128) + || alloc_array(CODE, pLzcState->pfx[1], (newmaxcode + 1) / 2, 128) +#else + || alloc_array(CODE, pLzcState->pfx, (newmaxcode + 1), 256) +#endif + ) { + pLzcState->oldmaxcode = 0; + pLzcState->exit_stat = NOMEM; + return(NOMEM); + } + pLzcState->oldmaxcode = newmaxcode; + } + if (newhashsize > pLzcState->oldhashsize) { + if ( +#if (SPLIT_HT) + alloc_array(CODE, pLzcState->ht[0], (newhashsize / 2) + 1, 0) + || alloc_array(CODE, pLzcState->ht[1], newhashsize / 2, 0) +#else + alloc_array(CODE, pLzcState->ht, newhashsize, 0) +#endif + ) { + pLzcState->oldhashsize = 0; + pLzcState->exit_stat = NOMEM; + return(NOMEM); + } + pLzcState->oldhashsize = newhashsize; + } + return (OK); +} + +# if (SPLIT_PFX) + /* + * We have to split pfx[] table in half, + * because it's potentially larger than 64k bytes. + */ +# define prefix(code) (pLzcState->pfx[(code) & 1][(code) >> 1]) +# else + /* + * Then pfx[] can't be larger than 64k bytes, + * or we don't care if it is, so we don't split. + */ +# define prefix(code) (pLzcState->pfx[code]) +# endif + + +/* The initializing of the tables can be done quicker with memset() */ +/* but this way is portable through out the memory models. */ +/* If you use Microsoft halloc() to allocate the arrays, then */ +/* include the pragma #pragma function(memset) and make sure that */ +/* the length of the memory block is not greater than 64K. */ +/* This also means that you MUST compile in a model that makes the */ +/* default pointers to be far pointers (compact or large models). */ +/* See the file COMPUSI.DOS to modify function emalloc(). */ + +# if (SPLIT_HT) + /* + * We have to split ht[] hash table in half, + * because it's potentially larger than 64k bytes. + */ +# define probe(hash) (pLzcState->ht[(hash) & 1][(hash) >> 1]) +# define init_tables() \ + { \ + hash = pLzcState->hashsize >> 1; \ + pLzcState->ht[0][hash] = 0; \ + while (hash--) pLzcState->ht[0][hash] = pLzcState->ht[1][hash] = 0; \ + pLzcState->highcode = ~(~(INTCODE)0 << (pLzcState->bits = INITBITS)); \ + pLzcState->nextfree = (pLzcState->block_compress ? FIRSTFREE : 256); \ + } + +# else + + /* + * Then ht[] can't be larger than 64k bytes, + * or we don't care if it is, so we don't split. + */ +# define probe(hash) (pLzcState->ht[hash]) +# define init_tables() \ + { \ + hash = pLzcState->hashsize; \ + while (hash--) pLzcState->ht[hash] = 0; \ + pLzcState->highcode = ~(~(INTCODE)0 << (pLzcState->bits = INITBITS)); \ + pLzcState->nextfree = (pLzcState->block_compress ? FIRSTFREE : 256); \ + } + +# endif + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +static void Nu_prratio(long int num, long int den) +{ + register int q; /* Doesn't need to be long */ + + if(num > 214748L) { /* 2147483647/10000 */ + q = (int) (num / (den / 10000L)); + } + else { + q = (int) (10000L * num / den); /* Long calculations, though */ + } + if (q < 0) { + DBUG(("-")); + q = -q; + } + DBUG(("%d.%02d%%", q / 100, q % 100)); +} + +#ifdef COMP40 +/* table clear for block compress */ +/* this is for adaptive reset present in version 4.0 joe release */ +/* DjG, sets it up and returns TRUE to compress and FALSE to not compress */ +static int Nu_LZC_cl_block(LZCState* pLzcState) +{ + register long int rat; + + pLzcState->checkpoint = pLzcState->in_count + CHECK_GAP; +#ifdef DEBUG_LZC + if ( pLzcState->debug ) { + DBUG(( "count: %ld, ratio: ", pLzcState->in_count )); + Nu_prratio ( pLzcState->in_count, pLzcState->bytes_out ); + DBUG(( "\n")); + } +#endif + + if(pLzcState->in_count > 0x007fffff) { /* shift will overflow */ + rat = pLzcState->bytes_out >> 8; + if(rat == 0) /* Don't divide by zero */ + rat = 0x7fffffff; + else + rat = pLzcState->in_count / rat; + } + else + rat = (pLzcState->in_count << 8) / pLzcState->bytes_out; /* 8 fractional bits */ + + if ( rat > pLzcState->ratio ){ + pLzcState->ratio = rat; + return FALSE; + } + else { + pLzcState->ratio = 0; +#ifdef DEBUG_LZC + if(pLzcState->debug) { + DBUG(( "clear\n" )); + } +#endif + return TRUE; /* clear the table */ + } + return FALSE; /* don't clear the table */ +} +#endif + +static CONST UCHAR gNu_rmask[9] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + +static void Nu_LZC_putcode(LZCState* pLzcState, INTCODE code, register int bits) +{ + /*static int oldbits = 0;*/ + /*static UCHAR outbuf[MAXBITS];*/ + register UCHAR *buf; + register int shift; + + if (bits != pLzcState->oldbits) { + if (bits == 0) { + /* bits == 0 means EOF, write the rest of the buffer. */ + if (pLzcState->offset > 0) { + fwrite(pLzcState->outbuf,1,(pLzcState->offset +7) >> 3, pLzcState->outfp); + pLzcState->bytes_out += ((pLzcState->offset +7) >> 3); + } + pLzcState->offset = 0; + pLzcState->oldbits = 0; + fflush(pLzcState->outfp); + return; + } + else { + /* Change the code size. We must write the whole buffer, + * because the expand side won't discover the size change + * until after it has read a buffer full. + */ + if (pLzcState->offset > 0) { + fwrite(pLzcState->outbuf, 1, pLzcState->oldbits, pLzcState->outfp); + pLzcState->bytes_out += pLzcState->oldbits; + pLzcState->offset = 0; + } + pLzcState->oldbits = bits; + #ifdef DEBUG_LZC + if ( pLzcState->debug ) { + DBUG(( "\nChange to %d bits\n", bits )); + } + #endif /* DEBUG_LZC */ + } + } + /* Get to the first byte. */ + buf = pLzcState->outbuf + ((shift = pLzcState->offset) >> 3); + if ((shift &= 7) != 0) { + *(buf) |= (*buf & gNu_rmask[shift]) | (UCHAR)(code << shift); + *(++buf) = (UCHAR)(code >> (8 - shift)); + if (bits + shift > 16) + *(++buf) = (UCHAR)(code >> (16 - shift)); + } + else { + /* Special case for fast execution */ + *(buf) = (UCHAR)code; + *(++buf) = (UCHAR)(code >> 8); + } + if ((pLzcState->offset += bits) == (bits << 3)) { + pLzcState->bytes_out += bits; + fwrite(pLzcState->outbuf,1,bits,pLzcState->outfp); + pLzcState->offset = 0; + } + return; +} + + +#define kNuLZCEOF (-1) + +/* + * Get the next byte from the input straw. Also updates the CRC + * if "doCalcCRC" is set to true. + * + * Returns kNuLZCEOF as the value when we're out of data. + */ +static NuError Nu_LZCGetcCRC(LZCState* pLzcState, int* pSym) +{ + NuError err; + uint8_t c; + + if (!pLzcState->uncompRemaining) { + *pSym = kNuLZCEOF; + return kNuErrNone; + } + + err = Nu_StrawRead(pLzcState->pArchive, pLzcState->pStraw, &c, 1); + if (err == kNuErrNone) { + if (pLzcState->doCalcCRC) + pLzcState->crc = Nu_CalcCRC16(pLzcState->crc, &c, 1); + *pSym = c; + pLzcState->uncompRemaining--; + } + + return err; +} + +/* + * compress stdin to stdout + */ +static void Nu_LZC_compress(LZCState* pLzcState, uint32_t* pDstLen) +{ + int c,adjbits; + register HASH hash; + register INTCODE code; + HASH hashf[256]; + + Assert(pLzcState->outfp != NULL); + + pLzcState->maxcode = Maxcode(pLzcState->maxbits); + pLzcState->hashsize = Hashsize(pLzcState->maxbits); + +#ifdef COMP40 +/* Only needed for adaptive reset */ + pLzcState->checkpoint = CHECK_GAP; + pLzcState->ratio = 0; +#endif + + adjbits = pLzcState->maxbits -10; + for (c = 256; --c >= 0; ){ + hashf[c] = ((( c &0x7) << 7) ^ c) << adjbits; + } + pLzcState->exit_stat = OK; + if (Nu_LZC_alloc_tables(pLzcState, pLzcState->maxcode, pLzcState->hashsize)) /* exit_stat already set */ + return; + init_tables(); + + #if 0 + /* if not zcat or filter */ + if(is_list && !zcat_flg) { /* Open output file */ + if (freopen(ofname, WRITE_FILE_TYPE, pLzcState->outfp) == NULL) { + pLzcState->exit_stat = NOTOPENED; + return; + } + if (!quiet) + fprintf(stderr, "%s: ",ifname); /*#if 0*/ + setvbuf(Xstdout,zbuf,_IOFBF,ZBUFSIZE); + } + #endif + + /* + * Check the input stream for previously seen strings. We keep + * adding characters to the previously seen prefix string until we + * get a character which forms a new (unseen) string. We then send + * the code for the previously seen prefix string, and add the new + * string to our tables. The check for previous strings is done by + * hashing. If the code for the hash value is unused, then we have + * a new string. If the code is used, we check to see if the prefix + * and suffix values match the current input; if so, we have found + * a previously seen string. Otherwise, we have a hash collision, + * and we try secondary hash probes until we either find the current + * string, or we find an unused entry (which indicates a new string). + */ + if (1 /*!nomagic*/) { + putc(gNu_magic_header[0], pLzcState->outfp); + putc(gNu_magic_header[1], pLzcState->outfp); + putc((char)(pLzcState->maxbits | pLzcState->block_compress), pLzcState->outfp); + if(ferror(pLzcState->outfp)){ /* check it on entry */ + pLzcState->exit_stat = WRITEERR; + return; + } + pLzcState->bytes_out = 3L; /* includes 3-byte header mojo */ + } + else + pLzcState->bytes_out = 0L; /* no 3-byte header mojo */ + pLzcState->in_count = 1L; + pLzcState->offset = 0; + + pLzcState->exit_stat = Nu_LZCGetcCRC(pLzcState, &c); + if (pLzcState->exit_stat != kNuErrNone) + return; + pLzcState->prefxcode = (INTCODE)c; + + while (1) { + pLzcState->exit_stat = Nu_LZCGetcCRC(pLzcState, &c); + if (pLzcState->exit_stat != kNuErrNone) + return; + if (c == kNuLZCEOF) + break; + + pLzcState->in_count++; + hash = pLzcState->prefxcode ^ hashf[c]; + /* I need to check that my hash value is within range + * because my 16-bit hash table is smaller than 64k. + */ + if (hash >= pLzcState->hashsize) + hash -= pLzcState->hashsize; + if ((code = (INTCODE)probe(hash)) != UNUSED) { + if (suffix(code) != (char)c || (INTCODE)prefix(code) != pLzcState->prefxcode) { + /* hashdelta is subtracted from hash on each iteration of + * the following hash table search loop. I compute it once + * here to remove it from the loop. + */ + HASH hashdelta = (0x120 - c) << (adjbits); + do { + /* rehash and keep looking */ + Assert(code >= FIRSTFREE && code <= pLzcState->maxcode); + if (hash >= hashdelta) hash -= hashdelta; + else hash += (pLzcState->hashsize - hashdelta); + Assert(hash < pLzcState->hashsize); + if ((code = (INTCODE)probe(hash)) == UNUSED) + goto newcode; + } while (suffix(code) != (char)c || (INTCODE)prefix(code) != pLzcState->prefxcode); + } + pLzcState->prefxcode = code; + } + else { + newcode: { + Nu_LZC_putcode(pLzcState, pLzcState->prefxcode, pLzcState->bits); + code = pLzcState->nextfree; + Assert(hash < pLzcState->hashsize); + Assert(code >= FIRSTFREE); + Assert(code <= pLzcState->maxcode + 1); + if (code <= pLzcState->maxcode) { + probe(hash) = (CODE)code; + prefix(code) = (CODE)pLzcState->prefxcode; + suffix(code) = (char)c; + if (code > pLzcState->highcode) { + pLzcState->highcode += code; + ++pLzcState->bits; + } + pLzcState->nextfree = code + 1; + } +#ifdef COMP40 + else if (pLzcState->in_count >= pLzcState->checkpoint && pLzcState->block_compress ) { + if (Nu_LZC_cl_block(pLzcState)){ +#else + else if (pLzcState->block_compress){ +#endif + Nu_LZC_putcode(pLzcState, (INTCODE)c, pLzcState->bits); + Nu_LZC_putcode(pLzcState, CLEAR, pLzcState->bits); + init_tables(); + pLzcState->exit_stat = Nu_LZCGetcCRC(pLzcState, &c); + if (pLzcState->exit_stat != kNuErrNone) + return; + if (c == kNuLZCEOF) + break; + pLzcState->in_count++; +#ifdef COMP40 + } +#endif + } + pLzcState->prefxcode = (INTCODE)c; + } + } + } + Nu_LZC_putcode(pLzcState, pLzcState->prefxcode, pLzcState->bits); + Nu_LZC_putcode(pLzcState, CLEAR, 0); + /* + * Print out stats on stderr + */ + if(1 /*zcat_flg == 0 && !quiet*/) { +#ifdef DEBUG_LZC + DBUG(( + "%ld chars in, (%ld bytes) out, compression factor: ", + pLzcState->in_count, pLzcState->bytes_out )); + Nu_prratio( pLzcState->in_count, pLzcState->bytes_out ); + DBUG(( "\n")); + DBUG(( "\tCompression as in compact: " )); + Nu_prratio( pLzcState->in_count-pLzcState->bytes_out, pLzcState->in_count ); + DBUG(( "\n")); + DBUG(( "\tLargest code (of last block) was %d (%d bits)\n", + pLzcState->prefxcode - 1, pLzcState->bits )); +#else + DBUG(( "Compression: " )); + Nu_prratio( pLzcState->in_count-pLzcState->bytes_out, pLzcState->in_count ); +#endif /* DEBUG_LZC */ + } + if(pLzcState->bytes_out > pLzcState->in_count) /* if no savings */ + pLzcState->exit_stat = NOSAVING; + *pDstLen = pLzcState->bytes_out; + return ; +} + + +/* + * NufxLib interface to LZC compression. + */ +static NuError Nu_CompressLZC(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc, int maxbits) +{ + NuError err = kNuErrNone; + LZCState lzcState; + + memset(&lzcState, 0, sizeof(lzcState)); + lzcState.pArchive = pArchive; + lzcState.pStraw = pStraw; + lzcState.outfp = fp; + lzcState.uncompRemaining = srcLen; + + if (pCrc == NULL) { + lzcState.doCalcCRC = false; + } else { + lzcState.doCalcCRC = true; + lzcState.crc = *pCrc; + } + + lzcState.maxbits = maxbits; + lzcState.block_compress = BLOCK_MASK; /* enabled */ + + Nu_LZC_compress(&lzcState, pDstLen); + err = lzcState.exit_stat; + DBUG(("+++ LZC_compress returned with %d\n", err)); + +#if (SPLIT_HT) + free_array(CODE,lzcState.ht[1], 0); + free_array(CODE,lzcState.ht[0], 0); +#else + free_array(CODE,lzcState.ht, 0); +#endif + +#if (SPLIT_PFX) + free_array(CODE,lzcState.pfx[1], 128); + free_array(CODE,lzcState.pfx[0], 128); +#else + free_array(CODE,lzcState.pfx, 256); +#endif + free_array(char,lzcState.sfx, 256); + + if (pCrc != NULL) + *pCrc = lzcState.crc; + + return err; +} + +NuError Nu_CompressLZC12(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + return Nu_CompressLZC(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, 12); +} + +NuError Nu_CompressLZC16(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + return Nu_CompressLZC(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, 16); +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* + * Write the next byte to the output funnel. Also updates the CRC + * if "doCalcCRC" is set to true. + * + * Returns kNuLZCEOF as the value when we're out of data. + */ +static NuError Nu_LZCPutcCRC(LZCState* pLzcState, char c) +{ + NuError err; + + err = Nu_FunnelWrite(pLzcState->pArchive, pLzcState->pFunnel, + (uint8_t*) &c, 1); + if (pLzcState->doCalcCRC) + pLzcState->crc = Nu_CalcCRC16(pLzcState->crc, (uint8_t*) &c, 1); + + return err; +} + + +static int Nu_LZC_nextcode(LZCState* pLzcState, INTCODE* codeptr) +/* Get the next code from input and put it in *codeptr. + * Return (TRUE) on success, or return (FALSE) on end-of-file. + * Adapted from COMPRESS V4.0. + */ +{ + /*static int prevbits = 0;*/ + register INTCODE code; + /*static int size;*/ + /*static UCHAR inbuf[MAXBITS];*/ + register int shift; + UCHAR *bp; + + /* If the next entry is a different bit-size than the preceeding one + * then we must adjust the size and scrap the old buffer. + */ + if (pLzcState->prevbits != pLzcState->bits) { + pLzcState->prevbits = pLzcState->bits; + pLzcState->size = 0; + } + /* If we can't read another code from the buffer, then refill it. + */ + shift = pLzcState->offset; + if (pLzcState->size - shift < pLzcState->bits) { + /* Read more input and convert size from # of bytes to # of bits */ + long getSize; + + getSize = pLzcState->bits; + if (getSize > pLzcState->compRemaining) + getSize = pLzcState->compRemaining; + if (!getSize) /* act like EOF */ + return FALSE; + pLzcState->size = fread(pLzcState->inbuf, 1, getSize, pLzcState->infp) << 3; + if (pLzcState->size <= 0 || ferror(pLzcState->infp)) + return(FALSE); + pLzcState->compRemaining -= getSize; + pLzcState->offset = shift = 0; + } + /* Get to the first byte. */ + bp = pLzcState->inbuf + (shift >> 3); + /* Get first part (low order bits) */ + code = (*bp++ >> (shift &= 7)); + /* high order bits. */ + code |= *bp++ << (shift = 8 - shift); + if ((shift += 8) < pLzcState->bits) code |= *bp << shift; + *codeptr = code & pLzcState->highcode; + pLzcState->offset += pLzcState->bits; + return (TRUE); +} + +static void Nu_LZC_decompress(LZCState* pLzcState, uint32_t compressedLen) +{ + NuArchive* pArchive = pLzcState->pArchive; + register int i; + register INTCODE code; + char sufxchar = 0; + INTCODE savecode; + FLAG fulltable = FALSE, cleartable; + /*static*/ char *token= NULL; /* String buffer to build token */ + /*static*/ int maxtoklen = MAXTOKLEN; + int flags; + + Assert(pLzcState->infp != NULL); + + pLzcState->exit_stat = OK; + + if (compressedLen < 3) { + /* not long enough to be valid! */ + pLzcState->exit_stat = kNuErrBadData; + Nu_ReportError(NU_BLOB, pLzcState->exit_stat, "thread too short to be valid LZC"); + return; + } + pLzcState->compRemaining = compressedLen; + + /* + * This comes out of "compress.c" rather than "compapi.c". + */ + if ((getc(pLzcState->infp)!=(gNu_magic_header[0] & 0xFF)) + || (getc(pLzcState->infp)!=(gNu_magic_header[1] & 0xFF))) + { + DBUG(("not in compressed format\n")); + pLzcState->exit_stat = kNuErrBadData; + return; + } + flags = getc(pLzcState->infp); /* set -b from file */ + pLzcState->block_compress = flags & BLOCK_MASK; + pLzcState->maxbits = flags & BIT_MASK; + if(pLzcState->maxbits > MAXBITS) { + DBUG(("compressed with %d bits, can only handle %d bits\n", + pLzcState->maxbits, MAXBITS)); + pLzcState->exit_stat = kNuErrBadData; + return; + } + + pLzcState->compRemaining -= 3; + + /* Initialze the token buffer. */ + token = (char*)Nu_Malloc(pArchive, maxtoklen); + if (token == NULL) { + pLzcState->exit_stat = NOMEM; + return; + } + + if (Nu_LZC_alloc_tables(pLzcState, pLzcState->maxcode = ~(~(INTCODE)0 << pLzcState->maxbits),0)) /* exit_stat already set */ + return; + + #if 0 + /* if not zcat or filter */ + if(is_list && !zcat_flg) { /* Open output file */ + if (freopen(ofname, WRITE_FILE_TYPE, stdout) == NULL) { + pLzcState->exit_stat = NOTOPENED; + return; + } + if (!quiet) + fprintf(stderr, "%s: ",ifname); /*#if 0*/ + setvbuf(stdout,xbuf,_IOFBF,XBUFSIZE); + } + #endif + + cleartable = TRUE; + savecode = CLEAR; + pLzcState->offset = 0; + do { + if ((code = savecode) == CLEAR && cleartable) { + pLzcState->highcode = ~(~(INTCODE)0 << (pLzcState->bits = INITBITS)); + fulltable = FALSE; + pLzcState->nextfree = (cleartable = pLzcState->block_compress) == FALSE ? 256 : FIRSTFREE; + if (!Nu_LZC_nextcode(pLzcState, &pLzcState->prefxcode)) + break; + /*putc((*/sufxchar = (char)pLzcState->prefxcode/*), stdout)*/; + pLzcState->exit_stat = Nu_LZCPutcCRC(pLzcState, sufxchar); + if (pLzcState->exit_stat != kNuErrNone) + return; + continue; + } + i = 0; + if (code >= pLzcState->nextfree && !fulltable) { + if (code != pLzcState->nextfree){ + DBUG(("ERROR: code (0x%x) != nextfree (0x%x)\n", + code, pLzcState->nextfree)); + pLzcState->exit_stat = CODEBAD; + return ; /* Non-existant code */ + } + /* Special case for sequence KwKwK (see text of article) */ + code = pLzcState->prefxcode; + token[i++] = sufxchar; + } + /* Build the token string in reverse order by chasing down through + * successive prefix tokens of the current token. Then output it. + */ + while (code >= 256) { + #ifdef DEBUG_LZC + /* These are checks to ease paranoia. Prefix codes must decrease + * monotonically, otherwise we must have corrupt tables. We can + * also check that we haven't overrun the token buffer. + */ + if (code <= (INTCODE)prefix(code)){ + pLzcState->exit_stat= TABLEBAD; + return; + } + #endif + if (i >= maxtoklen) { + maxtoklen *= 2; /* double the size of the token buffer */ + if ((token = Nu_Realloc(pArchive, token, maxtoklen)) == NULL) { + pLzcState->exit_stat = TOKTOOBIG; + return; + } + } + token[i++] = suffix(code); + code = (INTCODE)prefix(code); + } + /*putc(*/sufxchar = (char)code/*, stdout)*/; + pLzcState->exit_stat = Nu_LZCPutcCRC(pLzcState, sufxchar); + while (--i >= 0) { + /*putc(token[i], stdout);*/ + pLzcState->exit_stat = Nu_LZCPutcCRC(pLzcState, token[i]); + } + if (pLzcState->exit_stat != kNuErrNone) + return; + /* If table isn't full, add new token code to the table with + * codeprefix and codesuffix, and remember current code. + */ + if (!fulltable) { + code = pLzcState->nextfree; + Assert(256 <= code && code <= pLzcState->maxcode); + prefix(code) = (CODE)pLzcState->prefxcode; + suffix(code) = sufxchar; + pLzcState->prefxcode = savecode; + if (code++ == pLzcState->highcode) { + if (pLzcState->highcode >= pLzcState->maxcode) { + fulltable = TRUE; + --code; + } + else { + ++pLzcState->bits; + pLzcState->highcode += code; /* nextfree == highcode + 1 */ + } + } + pLzcState->nextfree = code; + } + } while (Nu_LZC_nextcode(pLzcState, &savecode)); + pLzcState->exit_stat = (ferror(pLzcState->infp))? READERR : OK; + + Nu_Free(pArchive, token); + return ; +} + + +/* + * NufxLib interface to LZC expansion. + */ +NuError Nu_ExpandLZC(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + LZCState lzcState; + + memset(&lzcState, 0, sizeof(lzcState)); + lzcState.pArchive = pArchive; + lzcState.infp = infp; + lzcState.pFunnel = pFunnel; + + if (pCrc == NULL) { + lzcState.doCalcCRC = false; + } else { + lzcState.doCalcCRC = true; + lzcState.crc = *pCrc; + } + + Nu_LZC_decompress(&lzcState, pThread->thCompThreadEOF); + err = lzcState.exit_stat; + DBUG(("+++ LZC_decompress returned with %d\n", err)); + +#if (SPLIT_HT) + free_array(CODE,lzcState.ht[1], 0); + free_array(CODE,lzcState.ht[0], 0); +#else + free_array(CODE,lzcState.ht, 0); +#endif + +#if (SPLIT_PFX) + free_array(CODE,lzcState.pfx[1], 128); + free_array(CODE,lzcState.pfx[0], 128); +#else + free_array(CODE,lzcState.pfx, 256); +#endif + free_array(char,lzcState.sfx, 256); + + if (pCrc != NULL) + *pCrc = lzcState.crc; + return err; +} + +#endif /*ENABLE_LZC*/ diff --git a/nufxlib/Lzw.c b/nufxlib/Lzw.c new file mode 100644 index 0000000..69ff120 --- /dev/null +++ b/nufxlib/Lzw.c @@ -0,0 +1,1622 @@ +/* + * 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. + * + * ShrinkIt LZW functions. The original code was developed by Kent Dickey + * and Andy Nicholas. + * + * Unisys holds US patent #4,558,302 (filed June 20, 1983 and issued December + * 10, 1985). A policy set in 1995 specifies the lifetime of a patent as + * the longer of 20 years from the date of application or 17 years from the + * date of grant, so the Unisys LZW patent expired on June 20, 2003 in the + * USA. Patents in some other countries expire after July 7, 2004. + * + * An older note: + * + * The Unisys patent is one of many that covers LZW compression, but Unisys + * is the only company actively attacking anyone who uses it. The statement + * Unisys made regarding LZW (and, specifically, GIF and TIFF-LZW) says: + * + * Q: I use LZW in my programs, but not for GIF or TIFF graphics. What should + * I do? + * A: If you are not a business, and the programs are for your own personal + * non-commercial or not-for-profit use, Unisys does not require you to + * obtain a license. If they are used as part of a business and/or you sell + * the programs for commercial or for-profit purposes, then you must contact + * the Welch Patent Licensing Department at Unisys and explain your + * circumstances. They will have a license agreement for your application of + * their LZW algorithm. + * + * According to this, the use of LZW in NufxLib has never required a license. + */ +#include "NufxLibPriv.h" + +#ifdef ENABLE_LZW + +/* the LZW algorithms operate on 4K chunks */ +#define kNuLZWBlockSize 4096 + +/* a little padding to avoid mysterious crashes on bad data */ +#define kNuSafetyPadding 64 + +#define kNuLZWClearCode 0x0100 +#define kNuLZWFirstCode 0x0101 + + +/* sometimes we want to get *really* verbose rather late in a large archive */ +#ifdef DEBUG_LZW + static Boolean gNuDebugVerbose = true; + #define DBUG_LZW(x) { if (gNuDebugVerbose) { DBUG(x); } } +#else + #define DBUG_LZW ((void)0) +#endif + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +/* + * We use a hash function borrowed from UNIX compress, which is described + * in the v4.3 sources as: + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. + * + * The function used to generate it is: + * + * int c, hashf[256]; + * for (c = 256; --c >= 0; ) { + * hashf[c] = (((c & 0x7) << 7) ^ c) << (maxbits-10); + * } + * + * It is used with: + * + * hash = prefixcode ^ hashf[c]; \* c is char from getchar() *\ + * + * The value for kNuLZWHashSize determines the size of the hash table and + * the % occupancy. We want a fair number of vacancies because we probe + * when we collide. Using 5119 (0x13ff) with 12-bit codes yields 75% + * occupancy. + */ + +#define kNuLZWHashSize 5119 /* must be prime */ +#define kNuLZWEntryUnused 0 /* indicates an unused hash entry */ +#define kNuLZWHashFuncTblSize 256 /* one entry per char value */ +#define kNuLZWDefaultVol 0xfe /* use this as volume number */ +#define kNuLZWHashDelta 0x120 /* used in secondary hashing */ +#define kNuLZWMinCode kNuLZWClearCode /* smallest 12-bit LZW code */ +#define kNuLZWMaxCode 0x0fff /* largest 12-bit LZW code */ +#define kNuLZW2StopCode 0x0ffd /* LZW/2 stops here */ + +/* + * Mask of bits, from 0 to 8. + */ +static const int gNuBitMask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; + +#define kNuRLEDefaultEscape 0xdb /* ShrinkIt standard */ + +/* + * This holds all of the "big" dynamic state, plus a few things that I + * don't want to pass around. It's allocated once for each instance of + * an open archive, and re-used. + * + * The hash table consists of three parts. We have a choice for some of + * them, "ushort" or "uint". With "ushort" it uses less memory and is + * more likely to fit in a CPU cache, but on some processors you have to + * add instructions to manipulate 16-bit values in a 32-bit word. I'm + * guessing "ushort" is better overall. + */ +typedef struct LZWCompressState { + NuArchive* pArchive; + + uint16_t entry[kNuLZWHashSize]; /* uint or ushort */ + uint16_t prefix[kNuLZWMaxCode+1]; /* uint or ushort */ + uint8_t suffix[kNuLZWMaxCode+1]; + + uint16_t hashFunc[kNuLZWHashFuncTblSize]; /* uint or ushort */ + + uint8_t inputBuf[kNuLZWBlockSize]; /* 4K of raw input */ + uint8_t rleBuf[kNuLZWBlockSize*2 + kNuSafetyPadding]; + uint8_t lzwBuf[(kNuLZWBlockSize * 3) / 2 + kNuSafetyPadding]; + + uint16_t chunkCrc; /* CRC for LZW/1 */ + + /* LZW/2 state variables */ + int nextFree; + int codeBits; + int highCode; + Boolean initialClear; +} LZWCompressState; + + +/* + * Allocate some "reusable" state for LZW compression. + * + * The only thing that really needs to be retained across calls is + * the hash function. This way we don't have to re-create it for + * every file, or store it statically in the binary. + */ +static NuError Nu_AllocLZWCompressState(NuArchive* pArchive) +{ + NuError err; + LZWCompressState* lzwState; + int ic; + + Assert(pArchive != NULL); + Assert(pArchive->lzwCompressState == NULL); + + /* allocate the general-purpose compression buffer, if needed */ + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + pArchive->lzwCompressState = Nu_Malloc(pArchive, sizeof(LZWCompressState)); + if (pArchive->lzwCompressState == NULL) + return kNuErrMalloc; + + /* + * The "hashFunc" table only needs to be set up once. + */ + lzwState = pArchive->lzwCompressState; + for (ic = 256; --ic >= 0; ) + lzwState->hashFunc[ic] = (((ic & 0x7) << 7) ^ ic) << 2; + + return kNuErrNone; +} + + +/* + * Compress a block of input from lzwState->inputBuf to lzwState->rleBuf. + * The size of the output is returned in "*pRLESize" (will be zero if the + * block expanded instead of compressing). + * + * The maximum possible size of the output is 2x the original, which can + * only occur if the input is an alternating sequence of RLE delimiters + * and non-delimiters. It requires 3 bytes to encode a solitary 0xdb, + * so you get (4096 / 2) non-delimiters plus (4096 / 2) * 3 RLE-encoded + * delimiters. We deal with this by using an 8K output buffer, so we + * don't have to watch for overflow in the inner loop. + * + * The RLE format is " ", where count is zero-based + * (i.e. for three bytes we encode "2", allowing us to express 1-256). + */ +static NuError Nu_CompressBlockRLE(LZWCompressState* lzwState, int* pRLESize) +{ + const uint8_t* inPtr = lzwState->inputBuf; + const uint8_t* endPtr = inPtr + kNuLZWBlockSize; + uint8_t* outPtr = lzwState->rleBuf; + uint8_t matchChar; + int matchCount; + + while (inPtr < endPtr) { + matchChar = *inPtr; + matchCount = 1; + + /* count up the matching chars */ + while (*++inPtr == matchChar && inPtr < endPtr) + matchCount++; + + if (matchCount > 3) { + if (matchCount > 256) { + /* rare case - really long match */ + while (matchCount > 256) { + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = 255; + matchCount -= 256; + } + + /* take care of the odd bits -- which might not form a run! */ + if (matchCount > 3) { + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = matchCount -1; + } else { + while (matchCount--) + *outPtr++ = matchChar; + } + + } else { + /* common case */ + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = matchCount -1; + } + + } else { + if (matchChar == kNuRLEDefaultEscape) { + /* encode 1-3 0xDBs */ + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchCount -1; + } else { + while (matchCount--) + *outPtr++ = matchChar; + } + } + } + + *pRLESize = outPtr - lzwState->rleBuf; + Assert(*pRLESize > 0 && *pRLESize < sizeof(lzwState->rleBuf)); + + return kNuErrNone; +} + + +/* + * Clear the LZW table. Also resets the LZW/2 state. + */ +static void Nu_ClearLZWTable(LZWCompressState* lzwState) +{ + Assert(lzwState != NULL); + + /*DBUG_LZW(("### clear table\n"));*/ + + /* reset table entries */ + Assert(kNuLZWEntryUnused == 0); /* make sure this is okay */ + memset(lzwState->entry, 0, sizeof(lzwState->entry)); + + /* reset state variables */ + lzwState->nextFree = kNuLZWFirstCode; + lzwState->codeBits = 9; + lzwState->highCode = ~(~0 << lzwState->codeBits); /* a/k/a 0x01ff */ + lzwState->initialClear = false; +} + + +/* + * Write a variable-width LZW code to the output. "prefixCode" has the + * value to write, and "codeBits" is the width. + * + * Data is written in little-endian order (lowest byte first). The + * putcode function in LZC is probably faster, but the format isn't + * compatible with SHK. + * + * The worst conceivable expansion for LZW is 12 bits of output for every + * byte of input. Because we're using variable-width codes and LZW is + * reasonably effective at finding matches, the actual expansion will + * certainly be less. Throwing the extra 2K onto the end of the buffer + * saves us from having to check for a buffer overflow here. + * + * On exit, "*pOutBuf" will point PAST the last byte we wrote (even if + * it's a partial byte), and "*pAtBit" will contain the bit offset. + * + * (Turning this into a macro might speed things up.) + */ +static inline void Nu_LZWPutCode(uint8_t** pOutBuf, uint32_t prefixCode, + int codeBits, int* pAtBit) +{ + int atBit = *pAtBit; + uint8_t* outBuf = *pOutBuf; + + /*DBUG_LZW(("### PUT: prefixCode=0x%04lx, codeBits=%d, atBit=%d\n", + prefixCode, codeBits, atBit));*/ + + Assert(atBit >= 0 && atBit < sizeof(gNuBitMask)); + + if (atBit) { + /* align the prefix code with the existing byte */ + prefixCode <<= atBit; + + /* merge it with the buffer contents (if necessary) and write lo bits */ + outBuf--; + *outBuf = (uint8_t)((*outBuf & gNuBitMask[atBit]) | prefixCode); + outBuf++; + } else { + /* nothing to merge with; write lo byte at next posn and advance */ + *outBuf++ = (uint8_t)prefixCode; + } + + /* codes are at least 9 bits, so we know we have to write one more */ + *outBuf++ = (uint8_t)(prefixCode >> 8); + + /* in some cases, we may have to write yet another */ + atBit += codeBits; + if (atBit > 16) + *outBuf++ = (uint8_t)(prefixCode >> 16); + + *pAtBit = atBit & 0x07; + *pOutBuf = outBuf; +} + + +/* + * Compress a block of data with LZW, from "inputBuf" to lzwState->lzwBuf. + * + * LZW/1 is just like LZW/2, except that for the former the table is + * always cleared before this function is called. Because of this, the + * table never fills completely, so none of the table-overflow code + * ever happens. + * + * This function is patterned after the LZC compress function, rather + * than the NuLib LZW code, because the NuLib code was abysmal (a rather + * straight translation from 6502 assembly). This function differs from LZC + * in a few areas in order to make the output match GS/ShrinkIt. + * + * There is a (deliberate) minor bug here: if a table clear is emitted + * when there is only one character left in the input, nothing will be + * added to the hash table (as there is nothing to add) but "nextFree" + * will be advanced. This mimics GSHK's behavior, and accounts for the + * "resetFix" logic in the expansion functions. Code 0x0101 is essentially + * lost in this situation. + */ +static NuError Nu_CompressLZWBlock(LZWCompressState* lzwState, + const uint8_t* inputBuf, int inputCount, int* pOutputCount) +{ + int nextFree, ic, atBit, codeBits; + int hash, hashDelta; + int prefixCode, code, highCode; + const uint8_t* inputEnd = inputBuf + inputCount; + /* local copies of lzwState members, for speed */ + const uint16_t* pHashFunc = lzwState->hashFunc; + uint16_t* pEntry = lzwState->entry; + uint16_t* pPrefix = lzwState->prefix; + uint8_t* pSuffix = lzwState->suffix; + uint8_t* outBuf = lzwState->lzwBuf; + + Assert(lzwState != NULL); + Assert(inputBuf != NULL); + Assert(inputCount > 0 && inputCount <= kNuLZWBlockSize); + /* make sure nobody has been messing with the types */ + Assert(sizeof(pHashFunc[0]) == sizeof(lzwState->hashFunc[0])); + Assert(sizeof(pEntry[0]) == sizeof(lzwState->entry[0])); + Assert(sizeof(pPrefix[0]) == sizeof(lzwState->prefix[0])); + Assert(sizeof(pSuffix[0]) == sizeof(lzwState->suffix[0])); + + /*DBUG_LZW(("### START LZW (nextFree=0x%04x)\n", lzwState->nextFree));*/ + + atBit = 0; + + if (lzwState->initialClear) { + /*DBUG_LZW(("### initialClear set\n"));*/ + codeBits = lzwState->codeBits; + Nu_LZWPutCode(&outBuf, kNuLZWClearCode, codeBits, &atBit); + Nu_ClearLZWTable(lzwState); + } + + table_cleared: + /* recover our state (or get newly-cleared state) */ + nextFree = lzwState->nextFree; + codeBits = lzwState->codeBits; + highCode = lzwState->highCode; + + prefixCode = *inputBuf++; + + /*DBUG_LZW(("### fchar=0x%02x\n", prefixCode));*/ + + while (inputBuf < inputEnd) { + ic = *inputBuf++; + /*DBUG_LZW(("### char=0x%02x\n", ic));*/ + + hash = prefixCode ^ pHashFunc[ic]; + code = pEntry[hash]; + + if (code != kNuLZWEntryUnused) { + /* something is here, either our prefix or a hash collision */ + if (pSuffix[code] != ic || pPrefix[code] != prefixCode) { + /* we've collided; do the secondary probe */ + hashDelta = (kNuLZWHashDelta - ic) << 2; + do { + /* rehash and keep looking */ + Assert(code >= kNuLZWMinCode && code <= kNuLZWMaxCode); + if (hash >= hashDelta) + hash -= hashDelta; + else + hash += kNuLZWHashSize - hashDelta; + Assert(hash >= 0 && hash < kNuLZWHashSize); + + if ((code = pEntry[hash]) == kNuLZWEntryUnused) + goto new_code; + } while (pSuffix[code] != ic || pPrefix[code] != prefixCode); + } + + /* else we found a matching string, and can keep searching */ + prefixCode = code; + + } else { + /* found an empty entry, add the prefix+suffix to the table */ + new_code: + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + Assert(outBuf < lzwState->lzwBuf + sizeof(lzwState->lzwBuf)); + /*DBUG_LZW(("### outBuf now at +%d\n",outBuf - lzwState->lzwBuf));*/ + + code = nextFree; + Assert(hash < kNuLZWHashSize); + Assert(code >= kNuLZWMinCode); + Assert(code <= kNuLZWMaxCode); + + /* + * GSHK accepts 0x0ffd, and then sends the table clear + * immediately. We could improve on GSHK's compression slightly + * by using the entire table, but I want to generate the exact + * same output as GSHK. (The decoder believes the table clear + * is entry 0xffe, so we've got one more coming, and possibly + * two if we tweak getcode slightly.) + * + * Experiments show that switching to 0xffe increases the size + * of files that don't compress well, and decreases the size + * of files that do. In both cases, the difference in size + * is very small. + */ + Assert(code <= kNuLZW2StopCode); + /*if (code <= kNuLZW2StopCode) {*/ + /*DBUG_LZW(("### added new code 0x%04x prefix=0x%04x ch=0x%02x\n", + code, prefixCode, ic));*/ + + pEntry[hash] = code; + pPrefix[code] = prefixCode; + pSuffix[code] = ic; + + /* + * Check and see if it's time to increase the code size (note + * we flip earlier than LZC by one here). + */ + if (code >= highCode) { + highCode += code +1; + codeBits++; + } + + nextFree++; + + /*}*/ + + prefixCode = ic; + + /* if the table is full, clear it (only for LZW/2) */ + if (code == kNuLZW2StopCode) { + /* output last code */ + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + + if (inputBuf < inputEnd) { + /* still have data, keep going */ + Nu_LZWPutCode(&outBuf, kNuLZWClearCode, codeBits, &atBit); + Nu_ClearLZWTable(lzwState); + goto table_cleared; + } else { + /* no more input, hold table clear for next block */ + DBUG(("--- RARE: block-end clear\n")); + lzwState->initialClear = true; + goto table_clear_finish; + } + } + + Assert(nextFree <= kNuLZW2StopCode); + } + } + + /* + * Output the last code. Since there's no following character, we don't + * need to add an entry to the table... whatever we've found is already + * in there. + */ + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + + /* + * Update the counters so LZW/2 has continuity. + */ + Assert(nextFree <= kNuLZW2StopCode); + if (nextFree >= highCode) { + highCode += nextFree +1; + codeBits++; + } + nextFree++; /* make room for the code we just wrote */ + + if (nextFree > kNuLZW2StopCode) { + /* + * The code we just wrote, which was part of a longer string already + * in the tree, took the last entry in the table. We need to clear + * the table, but we can't do it in this block. We will have to + * emit a table clear as the very first thing in the next block. + */ + DBUG(("--- RARE: block-end inter clear\n")); + lzwState->initialClear = true; + } + table_clear_finish: + + /* save state for next pass through */ + lzwState->nextFree = nextFree; + lzwState->codeBits = codeBits; + lzwState->highCode = highCode; + + Assert(inputBuf == inputEnd); + + *pOutputCount = outBuf - lzwState->lzwBuf; + + /* + if (*pOutputCount < inputCount) { + DBUG_LZW(("### compressed from %d to %d\n", inputCount, *pOutputCount)); + } else { + DBUG_LZW(("### NO compression (%d to %d)\n", inputCount,*pOutputCount)); + } + */ + + return kNuErrNone; +} + +/* + * Compress ShrinkIt-style "LZW/1" and "LZW/2". + * + * "*pThreadCrc" should already be set to its initial value. On exit it + * will contain the CRC of the uncompressed data. + * + * On exit, the output file will be positioned past the last byte written. + */ +static NuError Nu_CompressLZW(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pThreadCrc, Boolean isType2) +{ + NuError err = kNuErrNone; + LZWCompressState* lzwState; + long initialOffset; + const uint8_t* lzwInputBuf; + uint32_t blockSize, rleSize, lzwSize; + long compressedLen; + Boolean keepLzw; + + Assert(pArchive != NULL); + Assert(pStraw != NULL); + Assert(fp != NULL); + Assert(srcLen > 0); + Assert(pDstLen != NULL); + Assert(pThreadCrc != NULL); + Assert(isType2 == true || isType2 == false); + + /* + * Do some initialization and set-up. + */ + if (pArchive->lzwCompressState == NULL) { + err = Nu_AllocLZWCompressState(pArchive); + BailError(err); + } + Assert(pArchive->lzwCompressState != NULL); + Assert(pArchive->compBuf != NULL); + + lzwState = pArchive->lzwCompressState; + lzwState->pArchive = pArchive; + compressedLen = 0; + + /* + * And now for something ugly: for LZW/1 we have to compute the CRC + * twice. Old versions of ShrinkIt used LZW/1 and put the CRC in + * the compressed block while newer versions used LZW/2 and put the + * CRC in the thread header. We're using LZW/1 with the newer record + * format, so we need two CRCs. For some odd reason Andy N. decided + * to use 0xffff as the initial value for the thread one, so we can't + * just store the same thing in two places. + * + * Of course, this also means that an LZW/2 chunk stored in an old + * pre-v3 record wouldn't have a CRC at all... + * + * LZW/1 is included here for completeness. I can't think of a reason + * why you'd want to use it, really. + */ + lzwState->chunkCrc = kNuInitialChunkCRC; /* 0x0000 */ + + /* + * An LZW/1 file starts off with a CRC of the data, which means we + * have to compress the whole thing, then seek back afterward and + * write the value. This annoyance went away in LZW/2. + */ + err = Nu_FTell(fp, &initialOffset); + BailError(err); + + if (!isType2) { + putc(0, fp); /* leave space for CRC */ + putc(0, fp); + compressedLen += 2; + } + putc(kNuLZWDefaultVol, fp); + putc(kNuRLEDefaultEscape, fp); + compressedLen += 2; + + if (isType2) + Nu_ClearLZWTable(lzwState); + + while (srcLen) { + /* + * Fill up the input buffer. + */ + blockSize = (srcLen > kNuLZWBlockSize) ? kNuLZWBlockSize : srcLen; + + err = Nu_StrawRead(pArchive, pStraw, lzwState->inputBuf, blockSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "compression read failed"); + goto bail; + } + + /* + * ShrinkIt was originally just going to be a 5.25" disk compressor, + * so the compression functions were organized around 4K blocks (the + * size of one track on a 5.25" disk). The block passed into the + * RLE function is always 4K, so we zero out any extra space. + */ + if (blockSize < kNuLZWBlockSize) { + memset(lzwState->inputBuf + blockSize, 0, + kNuLZWBlockSize - blockSize); + } + + /* + * Compute the CRC. For LZW/1 this is on the entire 4K block, for + * the "version 3" thread header CRC this is on just the "real" data. + */ + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, lzwState->inputBuf, blockSize); + if (!isType2) { + lzwState->chunkCrc = Nu_CalcCRC16(lzwState->chunkCrc, + lzwState->inputBuf, kNuLZWBlockSize); + } + + /* + * Try to compress with RLE, from inputBuf to rleBuf. + */ + err = Nu_CompressBlockRLE(lzwState, (int*) &rleSize); + BailError(err); + + if (rleSize < kNuLZWBlockSize) { + lzwInputBuf = lzwState->rleBuf; + } else { + lzwInputBuf = lzwState->inputBuf; + rleSize = kNuLZWBlockSize; + } + + /* + * Compress with LZW, into lzwBuf. + */ + if (!isType2) + Nu_ClearLZWTable(lzwState); + err = Nu_CompressLZWBlock(lzwState, lzwInputBuf, rleSize, + (int*) &lzwSize); + BailError(err); + + /* decide if we want to keep it, bearing in mind the LZW/2 header */ + if (pArchive->valMimicSHK) { + /* GSHK doesn't factor in header -- and *sometimes* uses "<=" !! */ + keepLzw = (lzwSize < rleSize); + } else { + if (isType2) + keepLzw = (lzwSize +2 < rleSize); + else + keepLzw = (lzwSize < rleSize); + } + + /* + * Write the compressed (or not) chunk. + */ + if (keepLzw) { + /* + * LZW succeeded. + */ + if (isType2) + rleSize |= 0x8000; /* for LZW/2, set "LZW used" flag */ + + putc(rleSize & 0xff, fp); /* size after RLE */ + putc(rleSize >> 8, fp); + compressedLen += 2; + + if (isType2) { + /* write compressed LZW len (+4 for header bytes) */ + putc((lzwSize+4) & 0xff, fp); + putc((lzwSize+4) >> 8, fp); + compressedLen += 2; + } else { + /* set LZW/1 "LZW used" flag */ + putc(1, fp); + compressedLen++; + } + + /* write data from LZW buffer */ + err = Nu_FWrite(fp, lzwState->lzwBuf, lzwSize); + BailError(err); + compressedLen += lzwSize; + } else { + /* + * LZW failed. + */ + putc(rleSize & 0xff, fp); /* size after RLE */ + putc(rleSize >> 8, fp); + compressedLen += 2; + + if (isType2) { + /* clear LZW/2 table; we can't use it next time */ + Nu_ClearLZWTable(lzwState); + } else { + /* set LZW/1 "LZW not used" flag */ + putc(0, fp); + compressedLen++; + } + + /* write data from RLE or plain-input buffer */ + err = Nu_FWrite(fp, lzwInputBuf, rleSize); + BailError(err); + compressedLen += rleSize; + } + + + /* + * Update the counter and continue. + */ + srcLen -= blockSize; + } + + /* + * For LZW/1, go back and write the CRC. + */ + if (!isType2) { + long curOffset; + + err = Nu_FTell(fp, &curOffset); + BailError(err); + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + BailError(err); + putc(lzwState->chunkCrc & 0xff, fp); + putc(lzwState->chunkCrc >> 8, fp); + err = Nu_FSeek(fp, curOffset, SEEK_SET); + BailError(err); + } + + /* P8SHK and GSHK add an extra byte to LZW-compressed threads */ + if (pArchive->valMimicSHK) { + putc(0, fp); + compressedLen++; + } + + *pDstLen = compressedLen; + +bail: + return err; +} + +/* + * Compress ShrinkIt-style "LZW/1". + */ +NuError Nu_CompressLZW1(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + return Nu_CompressLZW(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, false); +} + +/* + * Compress ShrinkIt-style "LZW/2". + */ +NuError Nu_CompressLZW2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + return Nu_CompressLZW(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, true); +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* if we don't have at least this much data, we try to read more */ +/* (the "+3" is for the chunk header bytes) */ +#define kNuLZWDesiredChunk (kNuLZWBlockSize + 3) + +/* + * Static tables useful for bit manipulation. + */ +static const uint32_t gNuMaskTable[17] = { + 0x0000, 0x01ff, 0x03ff, 0x03ff, 0x07ff, 0x07ff, 0x07ff, 0x07ff, + 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, + 0x0fff +}; +/* convert high byte of "entry" into a bit width */ +static const uint32_t gNuBitWidth[17] = { + 8,9,10,10,11,11,11,11,12,12,12,12,12,12,12,12,12 +}; + + +/* entry in the trie */ +typedef struct TableEntry { + uint8_t ch; + uint32_t prefix; +} TableEntry; + +/* + * This holds all of the "big" dynamic state, plus a few things that I + * don't want to pass around. It's allocated once for each instance of + * an open archive, and re-used. + */ +typedef struct LZWExpandState { + NuArchive* pArchive; + + TableEntry trie[4096-256]; /* holds from 9 bits to 12 bits */ + uint8_t stack[kNuLZWBlockSize]; + + // some of these don't need to be 32 bits; they were "uint" before + uint32_t entry; /* 16-bit index into table */ + uint32_t oldcode; /* carryover state for LZW/2 */ + uint32_t incode; /* carryover state for LZW/2 */ + uint32_t finalc; /* carryover state for LZW/2 */ + Boolean resetFix; /* work around an LZW/2 bug */ + + uint16_t chunkCrc; /* CRC we calculate for LZW/1 */ + uint16_t fileCrc; /* CRC stored with file */ + + uint8_t diskVol; /* disk volume # */ + uint8_t rleEscape; /* RLE escape char, usually 0xdb */ + + uint32_t dataInBuffer; /* #of bytes in compBuf */ + uint8_t* dataPtr; /* current data offset */ + + uint8_t lzwOutBuf[kNuLZWBlockSize + kNuSafetyPadding]; + uint8_t rleOutBuf[kNuLZWBlockSize + kNuSafetyPadding]; +} LZWExpandState; + + +/* + * Allocate some "reusable" state for LZW expansion. + */ +static NuError Nu_AllocLZWExpandState(NuArchive* pArchive) +{ + NuError err; + + Assert(pArchive != NULL); + Assert(pArchive->lzwExpandState == NULL); + + /* allocate the general-purpose compression buffer, if needed */ + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + pArchive->lzwExpandState = Nu_Malloc(pArchive, sizeof(LZWExpandState)); + if (pArchive->lzwExpandState == NULL) + return kNuErrMalloc; + return kNuErrNone; +} + + +#ifdef NDEBUG +# define Nu_LZWPush(uch) ( *stackPtr++ = (uch) ) +# define Nu_LZWPop() ( *(--stackPtr) ) +# define Nu_LZWStackEmpty() ( stackPtr == lzwState->stack ) + +#else +# define Nu_LZWPush(uch) \ + ( Nu_LZWPushCheck(uch, lzwState, stackPtr), *stackPtr++ = (uch) ) +# define Nu_LZWPop() \ + ( Nu_LZWPopCheck(lzwState, stackPtr), *(--stackPtr) ) +# define Nu_LZWStackEmpty() ( stackPtr == lzwState->stack ) + +static inline void Nu_LZWPushCheck(uint8_t uch, const LZWExpandState* lzwState, + const uint8_t* stackPtr) +{ + if (stackPtr >= lzwState->stack + sizeof(lzwState->stack)) { + Nu_ReportError(lzwState->NU_BLOB, kNuErrBadData, "stack overflow"); + abort(); + } +} + +static inline void Nu_LZWPopCheck(const LZWExpandState* lzwState, + const uint8_t* stackPtr) +{ + if (stackPtr == lzwState->stack) { + Nu_ReportError(lzwState->NU_BLOB, kNuErrBadData, "stack underflow"); + abort(); + } +} + +#endif + +/* + * Get the next LZW code from the input, advancing pointers as needed. + * + * This would be faster as a macro and less ugly with pass-by-reference. + * Resorting to globals is unacceptable. Might be less ugly if we clumped + * some stuff into a struct. Should be good enough as-is. + * + * Returns an integer up to 12 bits long. + * + * (Turning this into a macro might speed things up.) + */ +static inline uint32_t Nu_LZWGetCode(const uint8_t** pInBuf, uint32_t entry, + int* pAtBit, uint32_t* pLastByte) +{ + uint32_t numBits, startBit, lastBit; + uint32_t value; + + numBits = (entry +1) >> 8; /* bit-width of next code */ + startBit = *pAtBit; + lastBit = startBit + gNuBitWidth[numBits]; + + /* + * We need one or two bytes from the input. These have to be shifted + * around and merged with the bits we already have (if any). + */ + if (!startBit) + value = *(*pInBuf)++; + else + value = *pLastByte; + + if (lastBit > 16) { + /* need two more bytes */ + value |= *(*pInBuf)++ << 8; + *pLastByte = *(*pInBuf)++; + value |= (uint32_t) *pLastByte << 16; + } else { + /* only need one more byte */ + *pLastByte = *(*pInBuf)++; + value |= *pLastByte << 8; + } + + *pAtBit = lastBit & 0x07; + + /*printf("| EX: value=$%06lx mask=$%04x return=$%03lx\n", + value,gNuMaskTable[numBits], (value >> startBit) & gNuMaskTable[numBits]);*/ + + /*DBUG_LZW(("### getcode 0x%04lx\n", + (value >> startBit) & gNuMaskTable[numBits]));*/ + + /* I believe ANSI allows shifting by zero bits, so don't test "!startBit" */ + return (value >> startBit) & gNuMaskTable[numBits]; +} + + +/* + * Expand an LZW/1 chunk. + * + * Reads from lzwState->dataPtr, writes to lzwState->lzwOutBuf. + */ +static NuError Nu_ExpandLZW1(LZWExpandState* lzwState, uint32_t expectedLen) +{ + NuError err = kNuErrNone; + TableEntry* tablePtr; + int atBit; + uint32_t entry, oldcode, incode, ptr; + uint32_t lastByte, finalc; + const uint8_t* inbuf; + uint8_t* outbuf; + uint8_t* outbufend; + uint8_t* stackPtr; + + Assert(lzwState != NULL); + Assert(expectedLen > 0 && expectedLen <= kNuLZWBlockSize); + + inbuf = lzwState->dataPtr; + outbuf = lzwState->lzwOutBuf; + outbufend = outbuf + expectedLen; + tablePtr = lzwState->trie - 256; /* don't store 256 empties */ + stackPtr = lzwState->stack; + + atBit = 0; + lastByte = 0; + + entry = kNuLZWFirstCode; /* 0x101 */ + finalc = oldcode = incode = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + *outbuf++ = incode; + Assert(incode <= 0xff); + if (incode > 0xff) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "invalid initial LZW symbol"); + goto bail; + } + + while (outbuf < outbufend) { + incode = ptr = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + + /* handle KwKwK case */ + if (ptr >= entry) { + //DBUG_LZW(("### KwKwK (ptr=%d entry=%d)\n", ptr, entry)); + if (ptr != entry) { + /* bad code -- this would make us read uninitialized data */ + DBUG(("--- bad code (ptr=%d entry=%d)\n", ptr, entry)); + err = kNuErrBadData; + return err; + } + Nu_LZWPush((uint8_t)finalc); + ptr = oldcode; + } + + /* fill the stack by chasing up the trie */ + while (ptr > 0xff) { + Nu_LZWPush(tablePtr[ptr].ch); + ptr = tablePtr[ptr].prefix; + Assert(ptr < 4096); + } + + /* done chasing up, now dump the stack, starting with ptr */ + finalc = ptr; + *outbuf++ = ptr; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + while (!Nu_LZWStackEmpty()) { + *outbuf++ = Nu_LZWPop(); + /*printf("POP/PUT 0x%02x\n", *(outbuf-1));*/ + } + + /* add the new prefix to the trie -- last string plus new char */ + Assert(finalc <= 0xff); + tablePtr[entry].ch = finalc; + tablePtr[entry].prefix = oldcode; + entry++; + oldcode = incode; + } + +bail: + if (outbuf != outbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "LZW expansion failed"); + return err; + } + + /* adjust input buffer */ + lzwState->dataInBuffer -= (inbuf - lzwState->dataPtr); + Assert(lzwState->dataInBuffer < 32767*65536); + lzwState->dataPtr = (uint8_t*)inbuf; + + return err; +} + +/* + * Expand an LZW/2 chunk. Main difference from LZW/1 is that the state + * is carried over from the previous block in most cases, and the table + * is cleared explicitly. + * + * Reads from lzwState->dataPtr, writes to lzwState->lzwOutBuf. + * + * In some cases, "expectedInputUsed" will be -1 to indicate that the + * value is not known. + */ +static NuError Nu_ExpandLZW2(LZWExpandState* lzwState, uint32_t expectedLen, + uint32_t expectedInputUsed) +{ + NuError err = kNuErrNone; + TableEntry* tablePtr; + int atBit; + uint32_t entry, oldcode, incode, ptr; + uint32_t lastByte, finalc; + const uint8_t* inbuf; + const uint8_t* inbufend; + uint8_t* outbuf; + uint8_t* outbufend; + uint8_t* stackPtr; + + /*DBUG_LZW(("### LZW/2 block start (compIn=%d, rleOut=%d, entry=0x%04x)\n", + expectedInputUsed, expectedLen, lzwState->entry));*/ + Assert(lzwState != NULL); + Assert(expectedLen > 0 && expectedLen <= kNuLZWBlockSize); + + inbuf = lzwState->dataPtr; + inbufend = lzwState->dataPtr + expectedInputUsed; + outbuf = lzwState->lzwOutBuf; + outbufend = outbuf + expectedLen; + entry = lzwState->entry; + tablePtr = lzwState->trie - 256; /* don't store 256 empties */ + stackPtr = lzwState->stack; + + atBit = 0; + lastByte = 0; + + /* + * If the table isn't empty, initialize from the saved state and + * jump straight into the main loop. + * + * There's a funny situation that arises when a table clear is the + * second-to-last code in the previous chunk. After we see the + * table clear, we get the next code and use it to initialize "oldcode" + * and "incode" -- but we don't advance "entry" yet. The way that + * ShrinkIt originally worked, the next time we came through we'd + * see what we thought was an empty table and we'd reinitialize. So + * we use "resetFix" to keep track of this situation. + */ + if (entry != kNuLZWFirstCode || lzwState->resetFix) { + /* table not empty */ + oldcode = lzwState->oldcode; + incode = lzwState->incode; + finalc = lzwState->finalc; + lzwState->resetFix = false; + goto main_loop; + } + +clear_table: + /* table is either empty or was just explicitly cleared; reset */ + entry = kNuLZWFirstCode; /* 0x0101 */ + if (outbuf == outbufend) { + /* block must've ended on a table clear */ + DBUG(("--- RARE: ending clear\n")); + /* reset values, mostly to quiet gcc's "used before init" warnings */ + oldcode = incode = finalc = 0; + goto main_loop; /* the while condition will fall through */ + } + finalc = oldcode = incode = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + *outbuf++ = incode; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + if (incode > 0xff) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "invalid initial LZW symbol"); + goto bail; + } + + if (outbuf == outbufend) { + /* if we're out of data, raise the "reset fix" flag */ + DBUG(("--- RARE: resetFix!\n")); + lzwState->resetFix = true; + /* fall through; the while condition will let us slip past */ + } + +main_loop: + while (outbuf < outbufend) { + incode = ptr = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + //DBUG_LZW(("### read incode=0x%04x\n", incode)); + if (incode == kNuLZWClearCode) /* table clear - 0x0100 */ + goto clear_table; + + /* handle KwKwK case */ + if (ptr >= entry) { + //DBUG_LZW(("### KwKwK (ptr=%d entry=%d)\n", ptr, entry)); + if (ptr != entry) { + /* bad code -- this would make us read uninitialized data */ + DBUG(("--- bad code (ptr=%d entry=%d)\n", ptr, entry)); + err = kNuErrBadData; + return err; + } + Nu_LZWPush((uint8_t)finalc); + ptr = oldcode; + } + + /* fill the stack by chasing up the trie */ + while (ptr > 0xff) { + Nu_LZWPush(tablePtr[ptr].ch); + ptr = tablePtr[ptr].prefix; + Assert(ptr < 4096); + } + + /* done chasing up, now dump the stack, starting with ptr */ + finalc = ptr; + *outbuf++ = ptr; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + while (!Nu_LZWStackEmpty()) { + *outbuf++ = Nu_LZWPop(); + /*printf("POP/PUT 0x%02x\n", *(outbuf-1));*/ + } + + /* add the new prefix to the trie -- last string plus new char */ + /*DBUG_LZW(("### entry 0x%04x gets prefix=0x%04x and ch=0x%02x\n", + entry, oldcode, finalc));*/ + Assert(finalc <= 0xff); + tablePtr[entry].ch = finalc; + tablePtr[entry].prefix = oldcode; + entry++; + oldcode = incode; + } + +bail: + /*DBUG_LZW(("### end of block\n"));*/ + if (expectedInputUsed != (uint32_t) -1 && inbuf != inbufend) { + /* data was corrupted; if we keep going this will get worse */ + DBUG(("--- inbuf != inbufend in ExpandLZW2 (diff=%d)\n", + inbufend - inbuf)); + err = kNuErrBadData; + return err; + } + Assert(outbuf == outbufend); + + /* adjust input buffer */ + lzwState->dataInBuffer -= (inbuf - lzwState->dataPtr); + Assert(lzwState->dataInBuffer < 32767*65536); + lzwState->dataPtr = (uint8_t*)inbuf; + + /* save off local copies of stuff */ + lzwState->entry = entry; + lzwState->oldcode = oldcode; + lzwState->incode = incode; + lzwState->finalc = finalc; + + return err; +} + + +/* + * Expands a chunk of RLEd data into 4K of output. + */ +static NuError Nu_ExpandRLE(LZWExpandState* lzwState, const uint8_t* inbuf, + uint32_t expectedInputUsed) +{ + NuError err = kNuErrNone; + uint8_t *outbuf; + uint8_t *outbufend; + const uint8_t *inbufend; + uint8_t uch, rleEscape; + int count; + + outbuf = lzwState->rleOutBuf; + outbufend = outbuf + kNuLZWBlockSize; + inbufend = inbuf + expectedInputUsed; + rleEscape = lzwState->rleEscape; + + while (outbuf < outbufend) { + uch = *inbuf++; + if (uch == rleEscape) { + uch = *inbuf++; + count = *inbuf++; + if (outbuf + count >= outbufend) { + /* don't overrun buffer */ + Assert(outbuf != outbufend); + break; + } + while (count-- >= 0) + *outbuf++ = uch; + } else { + *outbuf++ = uch; + } + } + + if (outbuf != outbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, + "RLE output glitch (off by %d)", (int)(outbufend-outbuf)); + goto bail; + } + if (inbuf != inbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, + "RLE input glitch (off by %d)", (int)(inbufend-inbuf)); + goto bail; + } + +bail: + return err; +} + + +/* + * Utility function to get a byte from the input buffer. + */ +static inline uint8_t Nu_GetHeaderByte(LZWExpandState* lzwState) +{ + lzwState->dataInBuffer--; + Assert(lzwState->dataInBuffer > 0); + return *lzwState->dataPtr++; +} + +/* + * Expand ShrinkIt-style "LZW/1" and "LZW/2". + * + * This manages the input data buffer, passing chunks of compressed data + * into the appropriate expansion function. + * + * Pass in NULL for "pThreadCrc" if no thread CRC is desired. Otherwise, + * "*pThreadCrc" should already be set to its initial value. On exit it + * will contain the CRC of the uncompressed data. + */ +NuError Nu_ExpandLZW(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, + uint16_t* pThreadCrc) +{ + NuError err = kNuErrNone; + Boolean isType2; + LZWExpandState* lzwState; + uint32_t compRemaining, uncompRemaining, minSize; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(infp != NULL); + Assert(pFunnel != NULL); + + /* + * Do some initialization and set-up. + */ + if (pArchive->lzwExpandState == NULL) { + err = Nu_AllocLZWExpandState(pArchive); + BailError(err); + } + Assert(pArchive->lzwExpandState != NULL); + Assert(pArchive->compBuf != NULL); + + lzwState = pArchive->lzwExpandState; + lzwState->pArchive = pArchive; + + if (pThread->thThreadFormat == kNuThreadFormatLZW1) { + isType2 = false; + minSize = 7; /* crc-lo,crc-hi,vol,rle-delim,len-lo,len-hi,lzw-used */ + lzwState->chunkCrc = kNuInitialChunkCRC; /* 0x0000 */ + } else if (pThread->thThreadFormat == kNuThreadFormatLZW2) { + isType2 = true; + minSize = 4; /* vol,rle-delim,len-lo,len-hi */ + } else { + err = kNuErrBadFormat; + goto bail; + } + + uncompRemaining = pThread->actualThreadEOF; + compRemaining = pThread->thCompThreadEOF; + if (compRemaining < minSize) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "thread too short to be valid LZW"); + goto bail; + } + if (compRemaining && !uncompRemaining) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "compressed data but no uncompressed data??"); + goto bail; + } + + /* + * Read the LZW header out of the data stream. + */ + if (!isType2) { + lzwState->fileCrc = getc(infp); + lzwState->fileCrc |= getc(infp) << 8; + compRemaining -= 2; + } + lzwState->diskVol = getc(infp); /* disk volume #; not really used */ + lzwState->rleEscape = getc(infp); /* RLE escape char for this thread */ + compRemaining -= 2; + + lzwState->dataInBuffer = 0; + lzwState->dataPtr = NULL; + + /* reset pointers */ + lzwState->entry = kNuLZWFirstCode; /* 0x0101 */ + lzwState->resetFix = false; + + /*DBUG_LZW(("### LZW%d block, vol=0x%02x, rleEsc=0x%02x\n", + isType2 +1, lzwState->diskVol, lzwState->rleEscape));*/ + + /* + * Read large blocks of the source file into compBuf, taking care not + * to read past the end of the thread data. + * + * The motivation for doing it this way rather than just reading the + * next compressed chunk are (1) compBuf is considerably larger than + * stdio BUFSIZ on most systems, and (2) for LZW/1 we don't know the + * size of the compressed data anyway. + * + * We need to ensure that we have at least one full compressed chunk + * in the buffer. Since the compressor will refuse to store the + * compressed data if it grows, we know that we need 4K plus the + * chunk header. + * + * Once we have what looks like a full chunk, invoke the LZW decoder. + */ + while (uncompRemaining) { + Boolean rleUsed; + Boolean lzwUsed; + uint32_t getSize; + uint32_t rleLen; /* length after RLE; 4096 if no RLE */ + uint32_t lzwLen = 0; /* type 2 only */ + uint32_t writeLen, inCount; + const uint8_t* writeBuf; + + /* if we're low, and there's more data available, read more */ + if (lzwState->dataInBuffer < kNuLZWDesiredChunk && compRemaining) { + /* + * First thing we do is slide the old data to the start of + * the buffer. + */ + if (lzwState->dataInBuffer) { + Assert(lzwState->dataPtr != NULL); + Assert(pArchive->compBuf != lzwState->dataPtr); + memmove(pArchive->compBuf, lzwState->dataPtr, + lzwState->dataInBuffer); + } + lzwState->dataPtr = pArchive->compBuf; + + /* + * Next we read as much as we can. + */ + if (kNuGenCompBufSize - lzwState->dataInBuffer < compRemaining) + getSize = kNuGenCompBufSize - lzwState->dataInBuffer; + else + getSize = compRemaining; + + /*printf("+++ READING %ld\n", getSize);*/ + err = Nu_FRead(infp, lzwState->dataPtr + lzwState->dataInBuffer, + getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, + "failed reading compressed data (%u bytes)", getSize); + goto bail; + } + lzwState->dataInBuffer += getSize; + compRemaining -= getSize; + + Assert(compRemaining < 32767*65536); + Assert(lzwState->dataInBuffer <= kNuGenCompBufSize); + } + Assert(lzwState->dataInBuffer); + + /* + * Read the LZW block header. + */ + if (isType2) { + rleLen = Nu_GetHeaderByte(lzwState); + rleLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwUsed = rleLen & 0x8000 ? true : false; + rleLen &= 0x1fff; + rleUsed = (rleLen != kNuLZWBlockSize); + + if (lzwUsed) { + lzwLen = Nu_GetHeaderByte(lzwState); + lzwLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwLen -= 4; /* don't include header bytes */ + } + } else { + rleLen = Nu_GetHeaderByte(lzwState); + rleLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwUsed = Nu_GetHeaderByte(lzwState); + if (lzwUsed != 0 && lzwUsed != 1) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "garbled LZW header"); + goto bail; + } + rleUsed = (rleLen != kNuLZWBlockSize); + } + + /*DBUG_LZW(("### CHUNK rleLen=%d(%d) lzwLen=%d(%d) uncompRem=%ld\n", + rleLen, rleUsed, lzwLen, lzwUsed, uncompRemaining));*/ + + if (uncompRemaining <= kNuLZWBlockSize) + writeLen = uncompRemaining; /* last block */ + else + writeLen = kNuLZWBlockSize; + + #ifndef NDEBUG + writeBuf = NULL; + #endif + + /* + * Decode the chunk, and point "writeBuf" at the uncompressed data. + * + * LZW always expands from the read buffer into lzwState->lzwOutBuf. + * RLE expands from a specific buffer to lzwState->rleOutBuf. + */ + if (lzwUsed) { + if (!isType2) { + err = Nu_ExpandLZW1(lzwState, rleLen); + } else { + if (pRecord->isBadMac || pArchive->valIgnoreLZW2Len) { + /* might be big-endian, might be okay; just ignore it */ + lzwLen = (uint32_t) -1; + } else if (lzwState->dataInBuffer < lzwLen) { + /* rare -- GSHK will do this if you don't let it finish */ + err = kNuErrBufferUnderrun; + Nu_ReportError(NU_BLOB, err, "not enough compressed data " + "-- archive truncated during creation?"); + goto bail; + } + err = Nu_ExpandLZW2(lzwState, rleLen, lzwLen); + } + + BailError(err); + + if (rleUsed) { + err = Nu_ExpandRLE(lzwState, lzwState->lzwOutBuf, rleLen); + BailError(err); + writeBuf = lzwState->rleOutBuf; + } else { + writeBuf = lzwState->lzwOutBuf; + } + + } else { + if (rleUsed) { + err = Nu_ExpandRLE(lzwState, lzwState->dataPtr, rleLen); + BailError(err); + writeBuf = lzwState->rleOutBuf; + inCount = rleLen; + } else { + writeBuf = lzwState->dataPtr; + inCount = writeLen; + } + + /* + * Advance the input buffer data pointers to consume the input. + * The LZW expansion functions do this for us, but we're not + * using LZW. + */ + lzwState->dataPtr += inCount; + lzwState->dataInBuffer -= inCount; + Assert(lzwState->dataInBuffer < 32767*65536); + + /* no LZW used, reset pointers */ + lzwState->entry = kNuLZWFirstCode; /* 0x0101 */ + lzwState->resetFix = false; + } + + Assert(writeBuf != NULL); + + /* + * Compute the CRC of the uncompressed data, and write it. For + * LZW/1, the CRC of the last block includes the zeros that pad + * it out to 4096 bytes. + * + * See commentary in the compression code for why we have to + * compute two CRCs for LZW/1. + */ + if (pThreadCrc != NULL) { + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, writeBuf, writeLen); + } + if (!isType2) { + lzwState->chunkCrc = Nu_CalcCRC16(lzwState->chunkCrc, + writeBuf, kNuLZWBlockSize); + } + + /* write the data, possibly doing an EOL conversion */ + err = Nu_FunnelWrite(pArchive, pFunnel, writeBuf, writeLen); + if (err != kNuErrNone) { + if (err != kNuErrAborted) + Nu_ReportError(NU_BLOB, err, "unable to write output"); + goto bail; + } + + uncompRemaining -= writeLen; + Assert(uncompRemaining < 32767*65536); + } + + /* + * It appears that ShrinkIt appends an extra byte after the last + * LZW block. The byte is included in the compThreadEOF, but isn't + * consumed by the LZW expansion routine, so it's usually harmless. + * + * It is *possible* for extra bytes to be here legitimately, but very + * unlikely. The very last block is always padded out to 4K with + * zeros. If you found a situation where that last block failed + * to compress with RLE and LZW (perhaps the last block filled up + * all but the last 2 or 3 bytes with uncompressible data), but + * earlier data made the overall file compressible, you would have + * a few stray bytes in the archive. + * + * This is a little easier to do if the last block has lots of single + * 0xdb characters in it, since that requires RLE to escape them. + * + * Whatever the case, issue a warning if it looks like there's too + * many of them. + */ + if (lzwState->dataInBuffer > 1) { + DBUG(("--- Found %ld bytes following compressed data (compRem=%ld)\n", + lzwState->dataInBuffer, compRemaining)); + if (lzwState->dataInBuffer > 32) { + Nu_ReportError(NU_BLOB, kNuErrNone, "(Warning) lots of fluff (%u)", + lzwState->dataInBuffer); + } + } + + /* + * We might be okay with stray bytes in the thread, but we're definitely + * not okay with anything identified as compressed data being unused. + */ + if (compRemaining) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "not all compressed data was used (%u/%u)", + compRemaining, lzwState->dataInBuffer); + goto bail; + } + + /* + * ShrinkIt used to put the CRC in the stream and not in the thread + * header. For LZW/1, we check the CRC here; for LZW/2, we hope it's + * in the thread header. (As noted in the compression code, it's + * possible to end up with two CRCs or no CRCs.) + */ + if (!isType2 && !pArchive->valIgnoreCRC) { + if (lzwState->chunkCrc != lzwState->fileCrc) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadDataCRC)) { + err = kNuErrBadDataCRC; + Nu_ReportError(NU_BLOB, err, + "expected 0x%04x, got 0x%04x (LZW/1)", + lzwState->fileCrc, lzwState->chunkCrc); + (void) Nu_FunnelFlush(pArchive, pFunnel); + goto bail; + } + } else { + DBUG(("--- LZW/1 CRCs match (0x%04x)\n", lzwState->chunkCrc)); + } + } + +bail: + return err; +} + +#endif /*ENABLE_LZW*/ diff --git a/nufxlib/Makefile.in b/nufxlib/Makefile.in new file mode 100644 index 0000000..8326fc9 --- /dev/null +++ b/nufxlib/Makefile.in @@ -0,0 +1,142 @@ +# +# 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) + +.NOTPARALLEL: + +# +# Build stuff +# + +all: $(PRODUCT) + @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 + diff --git a/nufxlib/Makefile.msc b/nufxlib/Makefile.msc new file mode 100644 index 0000000..8c2bb90 --- /dev/null +++ b/nufxlib/Makefile.msc @@ -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) + diff --git a/nufxlib/MiscStuff.c b/nufxlib/MiscStuff.c new file mode 100644 index 0000000..ff92c2a --- /dev/null +++ b/nufxlib/MiscStuff.c @@ -0,0 +1,115 @@ +/* + * 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. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of standard functions that aren't available in libc on this system. + */ +#include "SysDefs.h" +#include "MiscStuff.h" +#include + + +#ifndef HAVE_STRERROR +/* + * Return a pointer to the appropriate string in the system table, or NULL + * if the value is out of bounds. + */ +const char* Nu_strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if (errnum < 0 || errnum > sys_nerr) + return NULL; + + return sys_errlist[errnum]; +} +#endif + +#ifndef HAVE_MEMMOVE +/* + * Move a block of memory. Unlike memcpy, this is expected to work + * correctly with overlapping blocks. + * + * This is a straightforward implementation. A much faster implementation, + * from BSD, is available in the PGP 2.6.2 distribution, but this should + * suffice for those few systems that don't have memmove. + */ +void* Nu_memmove(void* dst, const void* src, size_t n) +{ + void* retval = dst; + char* srcp = (char*)src; + char* dstp = (char*)dst; + + /* you can normally get away with this if n==0 */ + Assert(dst != NULL); + Assert(src != NULL); + + if (dstp == srcp || !n) { + /* nothing to do */ + } else if (dstp > srcp) { + /* start from the end */ + (char*)dstp += n-1; + (char*)srcp += n-1; + while (n--) + *dstp-- = *srcp--; + } else { + /* start from the front */ + while (n--) + *dstp++ = *srcp++; + } + + return retval; +} +#endif + +#ifndef HAVE_STRTOUL +/* + * Perform strtol, but on an unsigned long. + * + * On systems that have strtol but don't have strtoul, the strtol + * function doesn't clamp the return value, making it similar in + * function to strtoul. The comparison is not exact, however, + * because strtoul is expected to lots of fancy things (like set + * errno to ERANGE). + * + * For our purposes here, strtol does all we need it to. Someday + * we should replace this with a "real" version. + */ +unsigned long Nu_strtoul(const char *nptr, char **endptr, int base) +{ + return strtol(nptr, endptr, base); +} +#endif + +#ifndef HAVE_STRCASECMP +/* + * Compare two strings, case-insensitive. + */ +int Nu_strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} + +#endif + +#ifndef HAVE_STRNCASECMP +/* + * Compare two strings, case-insensitive, stopping after "n" chars. + */ +int Nu_strncasecmp(const char *str1, const char *str2, size_t n) +{ + while (n && *str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++, n--; + + if (n) + return (toupper(*str1) - toupper(*str2)); + else + return 0; /* no mismatch in first n chars */ +} +#endif + diff --git a/nufxlib/MiscStuff.h b/nufxlib/MiscStuff.h new file mode 100644 index 0000000..493985c --- /dev/null +++ b/nufxlib/MiscStuff.h @@ -0,0 +1,105 @@ +/* + * 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. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of miscellaneous types and macros that I find generally useful. + */ +#ifndef NUFXLIB_MISCSTUFF_H +#define NUFXLIB_MISCSTUFF_H + +#define VALGRIND /* assume we're using it */ + +#include "SysDefs.h" + +/* + * Use our versions of functions if they don't exist locally. + */ +#ifndef HAVE_STRERROR +#define strerror Nu_strerror +const char* Nu_strerror(int errnum); +#endif +#ifndef HAVE_MEMMOVE +#define memmove Nu_memmove +void* Nu_memmove(void *dest, const void *src, size_t n); +#endif +#ifndef HAVE_STRTOUL +#define strtoul Nu_strtoul +unsigned long Nu_strtoul(const char *nptr, char **endptr, int base); +#endif +#ifndef HAVE_STRCASECMP +#define strcasecmp Nu_strcasecmp +int Nu_strcasecmp(const char *s1, const char *s2); +#endif +#ifndef HAVE_STRNCASECMP +#define strncasecmp Nu_strncasecmp +int Nu_strncasecmp(const char *s1, const char *s2, size_t n); +#endif + + +/* + * Misc types. + */ + +typedef unsigned char Boolean; +#define false (0) +#define true (!false) + + +/* + * Handy macros. + */ + +/* compute #of elements in a static array */ +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +/* convert single hex digit char to number */ +#define HexDigit(x) ( !isxdigit((int)(x)) ? -1 : \ + (x) <= '9' ? (x) - '0' : toupper(x) +10 - 'A' ) + +/* convert number from 0-15 to hex digit */ +#define HexConv(x) ( ((unsigned int)(x)) <= 15 ? \ + ( (x) <= 9 ? (x) + '0' : (x) -10 + 'A') : -1 ) + + +/* + * Debug stuff. + */ + +/* + * Redefine this if you want assertions to do something other than default. + * Changing the definition of assert is tough, because assert.h redefines + * it every time it's included. On a Solaris 2.7 system I was using, gcc + * pulled assert.h in with some of the system headers, and their definition + * resulted in corrupted core dumps. + */ +#define Assert assert + +#if defined(DEBUG_VERBOSE) + /* quick debug printf macro */ + #define DBUG(args) printf args +#else + #define DBUG(args) ((void)0) +#endif + + +#if defined(NDEBUG) + #define DebugFill(addr, len) ((void)0) + + #define DebugAbort() ((void)0) + +#else + /* when debugging, fill Malloc blocks with junk, unless we're using Purify */ + #if !defined(PURIFY) && !defined(VALGRIND) + #define DebugFill(addr, len) memset(addr, 0xa3, len) + #else + #define DebugFill(addr, len) ((void)0) + #endif + + #define DebugAbort() abort() +#endif + +#define kInvalidPtr ((void*)0xa3a3a3a3) + +#endif /*NUFXLIB_MISCSTUFF_H*/ diff --git a/nufxlib/MiscUtils.c b/nufxlib/MiscUtils.c new file mode 100644 index 0000000..a3efd53 --- /dev/null +++ b/nufxlib/MiscUtils.c @@ -0,0 +1,377 @@ +/* + * 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. + * + * Miscellaneous NufxLib utility functions. + */ +#include "NufxLibPriv.h" + +/* + * Big fat hairy global. Unfortunately this is unavoidable. + */ +NuCallback gNuGlobalErrorMessageHandler = NULL; + + +static const char* kNufxLibName = "nufxlib"; + + +/* + * strerror() equivalent for NufxLib errors. + */ +const char* Nu_StrError(NuError err) +{ + /* + * BUG: this should be set up as per-thread storage in an MT environment. + * I would be more inclined to worry about this if I was expecting + * it to be used. So long as valid values are passed in, and the + * switch statement is kept up to date, we should never have cause + * to return this. + * + * An easier solution, should this present a problem for someone, would + * be to have the function return NULL or "unknown error" when the + * error value isn't recognized. I'd recommend leaving it as-is for + * debug builds, though, as it's helpful to know *which* error is not + * recognized. + */ + static char defaultMsg[32]; + + switch (err) { + case kNuErrNone: + return "(no error)"; + + case kNuErrGeneric: + return "NufxLib generic error"; + case kNuErrInternal: + return "NufxLib internal error"; + case kNuErrUsage: + return "NufxLib usage error"; + case kNuErrSyntax: + return "NufxLib syntax error"; + case kNuErrMalloc: + return "NufxLib malloc error"; + case kNuErrInvalidArg: + return "Invalid arguments to NufxLib"; + case kNuErrBadStruct: + return "Bad NuArchive structure passed to NufxLib"; + case kNuErrBusy: + return "Attempted invalid reentrant call"; + + case kNuErrSkipped: + return "Skipped by user"; + case kNuErrAborted: + return "Processing aborted"; + case kNuErrRename: + return "User wants to rename file"; + + case kNuErrFile: + return "NufxLib trouble with a file"; + case kNuErrFileOpen: + return "NufxLib unable to open file"; + case kNuErrFileClose: + return "NufxLib unable to close file"; + case kNuErrFileRead: + return "NufxLib unable to read file"; + case kNuErrFileWrite: + return "NufxLib unable to write file"; + case kNuErrFileSeek: + return "NufxLib unable to seek file"; + case kNuErrFileExists: + return "File already exists"; + case kNuErrFileNotFound: + return "No such file or directory"; + case kNuErrFileStat: + return "Couldn't get file info"; + case kNuErrFileNotReadable: + return "Read access denied"; + + case kNuErrDirExists: + return "Directory already exists"; + case kNuErrNotDir: + return "Not a directory"; + case kNuErrNotRegularFile: + return "Not a regular file"; + case kNuErrDirCreate: + return "Unable to create directory"; + case kNuErrOpenDir: + return "Unable to open directory"; + case kNuErrReadDir: + return "Unable to read directory"; + case kNuErrFileSetDate: + return "Unable to set file date"; + case kNuErrFileSetAccess: + return "Unable to set file access"; + case kNuErrFileAccessDenied: + return "Access denied"; + + case kNuErrNotNuFX: + return "Input is not a NuFX archive"; + case kNuErrBadMHVersion: + return "Unrecognized Master Header version"; + case kNuErrRecHdrNotFound: + return "Next record not found"; + case kNuErrNoRecords: + return "No records in archive"; + case kNuErrBadRecord: + return "Bad data in record"; + case kNuErrBadMHCRC: + return "Bad Master Header CRC"; + case kNuErrBadRHCRC: + return "Bad Record header CRC"; + case kNuErrBadThreadCRC: + return "Bad Thread header CRC"; + case kNuErrBadDataCRC: + return "Data CRC mismatch"; + + case kNuErrBadFormat: + return "Thread compression format unsupported"; + case kNuErrBadData: + return "Bad data found"; + case kNuErrBufferOverrun: + return "Buffer overrun"; + case kNuErrBufferUnderrun: + return "Buffer underrun"; + case kNuErrOutMax: + return "Output limit exceeded"; + + case kNuErrNotFound: + return "Not found"; + case kNuErrRecordNotFound: + return "Record not found"; + case kNuErrRecIdxNotFound: + return "RecordIdx not found"; + case kNuErrThreadIdxNotFound: + return "ThreadIdx not found"; + case kNuErrThreadIDNotFound: + return "ThreadID not found"; + case kNuErrRecNameNotFound: + return "Record name not found"; + case kNuErrRecordExists: + return "Record already exists"; + + case kNuErrAllDeleted: + return "Tried to delete all files"; + case kNuErrArchiveRO: + return "Archive is in read-only mode"; + case kNuErrModRecChange: + return "Attempt to alter a modified record"; + case kNuErrModThreadChange: + return "Attempt to alter a modified thread"; + case kNuErrThreadAdd: + return "Can't add conflicting threadID"; + case kNuErrNotPreSized: + return "Operation only permitted on pre-sized threads"; + case kNuErrPreSizeOverflow: + return "Data exceeds pre-sized thread size"; + case kNuErrInvalidFilename: + return "Invalid filename"; + + case kNuErrLeadingFssep: + return "Storage name started with fssep char"; + case kNuErrNotNewer: + return "New item wasn't newer than existing"; + case kNuErrDuplicateNotFound: + return "Can only update an existing item"; + case kNuErrDamaged: + return "Original archive may have been damaged"; + + case kNuErrIsBinary2: + return "This is a Binary II archive"; + + case kNuErrUnknownFeature: + return "Unknown feature"; + case kNuErrUnsupFeature: + return "Feature not supported"; + + default: + sprintf(defaultMsg, "(error=%d)", err); + return defaultMsg; + } +} + + +#define kNuHeftyBufSize 256 /* all error messages should fit in this */ +#define kNuExtraGoodies 8 /* leave room for "\0" and other trivial chars*/ + +/* + * Similar to perror(), but takes the error as an argument, and knows + * about NufxLib errors as well as system errors. + * + * Depending on the compiler, "file", "line", and "function" may be NULL/zero. + * + * Calling here with "pArchive"==NULL is allowed, but should only be done + * if the archive is inaccessible (perhaps because it failed to open). We + * can't invoke the error message callback if the pointer is NULL. + */ +void Nu_ReportError(NuArchive* pArchive, const char* file, int line, + const char* function, Boolean isDebug, NuError err, + const UNICHAR* format, ...) +{ + NuErrorMessage errorMessage; + const char* msg; + va_list args; + char buf[kNuHeftyBufSize]; + int count; + #if !defined(HAVE_SNPRINTF) && defined(SPRINTF_RETURNS_INT) + int cc; + #endif + + Assert(format != NULL); + + + va_start(args, format); + + #if defined(HAVE_VSNPRINTF) && defined(VSNPRINTF_DECLARED) + count = vsnprintf(buf, sizeof(buf)-kNuExtraGoodies, format, args); + #else + #ifdef SPRINTF_RETURNS_INT + count = vsprintf(buf, format, args); + #else + vsprintf(buf, format, args); + count = strlen(buf); + #endif + #endif + + va_end(args); + + Assert(count > 0); + if (count < 0) + goto bail; + + /* print the error code data, if any */ + if (err != kNuErrNone) { + /* we know we have room for ": ", because of kNuExtraGoodies */ + strcpy(buf+count, ": "); + count += 2; + + msg = NULL; + if (err >= 0) + msg = strerror(err); + if (msg == NULL) + msg = Nu_StrError(err); + + #if defined(HAVE_SNPRINTF) && defined(SNPRINTF_DECLARED) + if (msg == NULL) + snprintf(buf+count, sizeof(buf) - count, + "(unknown err=%d)", err); + else + snprintf(buf+count, sizeof(buf) - count, "%s", msg); + #else + #ifdef SPRINTF_RETURNS_INT + if (msg == NULL) + cc = sprintf(buf+count, "(unknown err=%d)", err); + else + cc = sprintf(buf+count, "%s", msg); + Assert(cc > 0); + count += cc; + #else + if (msg == NULL) + sprintf(buf+count, "(unknown err=%d)", err); + else + sprintf(buf+count, "%s", msg); + count += strlen(buf + count); + #endif + #endif + + } + + #if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) || \ + !defined(SNPRINTF_DELCARED) || !defined(VSNPRINTF_DECLARED) + /* couldn't do it right, so check for overflow */ + Assert(count <= kNuHeftyBufSize); + #endif + + if ((pArchive != NULL && pArchive->messageHandlerFunc == NULL) || + (pArchive == NULL && gNuGlobalErrorMessageHandler == NULL)) + { + if (isDebug) { + fprintf(stderr, "%s: [%s:%d %s] %s\n", kNufxLibName, + file, line, function, buf); + } else { + fprintf(stderr, "%s: ERROR: %s\n", kNufxLibName, buf); + } + } else { + errorMessage.message = buf; + errorMessage.err = err; + errorMessage.isDebug = isDebug; + errorMessage.file = file; + errorMessage.line = line; + errorMessage.function = function; + + if (pArchive == NULL) + (void) (*gNuGlobalErrorMessageHandler)(pArchive, &errorMessage); + else + (void) (*pArchive->messageHandlerFunc)(pArchive, &errorMessage); + } + +bail: + return; +} + + +/* + * Memory allocation wrappers. + * + * Under gcc these would be macros, but not all compilers can handle that. + * + * [ It should be possible to use mmalloc instead of malloc. Just tuck the + * mmalloc descriptor into the NuArchive struct. ] + */ + +#ifndef USE_DMALLOC +void* Nu_Malloc(NuArchive* pArchive, size_t size) +{ + void* _result; + + Assert(size > 0); + _result = malloc(size); + if (_result == NULL) { + Nu_ReportError(NU_BLOB, kNuErrMalloc, + "malloc(%u) failed", (unsigned int) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + DebugFill(_result, size); + return _result; +} + +void* Nu_Calloc(NuArchive* pArchive, size_t size) +{ + void* _cresult = Nu_Malloc(pArchive, size); + memset(_cresult, 0, size); + return _cresult; +} + +void* Nu_Realloc(NuArchive* pArchive, void* ptr, size_t size) +{ + void* _result; + + Assert(ptr != NULL); /* disallow this usage */ + Assert(size > 0); /* disallow this usage */ + _result = realloc(ptr, size); + if (_result == NULL) { + Nu_ReportError(NU_BLOB, kNuErrMalloc, + "realloc(%u) failed", (unsigned int) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + return _result; +} + +void Nu_Free(NuArchive* pArchive, void* ptr) +{ + if (ptr != NULL) + free(ptr); +} +#endif + +/* + * If somebody internal wants to set doClose on a buffer DataSource + * (looks like "Rename" does), we need to supply a "free" callback. + */ +NuResult Nu_InternalFreeCallback(NuArchive* pArchive, void* args) +{ + DBUG(("+++ internal free callback 0x%08lx\n", (long) args)); + Nu_Free(NULL, args); + return kNuOK; +} + diff --git a/nufxlib/NOTES.md b/nufxlib/NOTES.md new file mode 100644 index 0000000..caccd89 --- /dev/null +++ b/nufxlib/NOTES.md @@ -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. + diff --git a/nufxlib/NufxLib.h b/nufxlib/NufxLib.h new file mode 100644 index 0000000..349917c --- /dev/null +++ b/nufxlib/NufxLib.h @@ -0,0 +1,890 @@ +/* + * 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. + * + * External interface (types, defines, and function prototypes). + */ +#ifndef NUFXLIB_NUFXLIB_H +#define NUFXLIB_NUFXLIB_H + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * NufxLib version number. Compare these values (which represent the + * version against which your application was compiled) to the values + * returned by NuGetVersion (representing the version against which + * your application is statically or dynamically linked). If the major + * number doesn't match exactly, an existing interface has changed and you + * should halt immediately. If the minor number from NuGetVersion is + * less, there may be new interfaces, new features, or bug fixes missing + * upon which your application depends, so you should halt immediately. + * (If the minor number is greater, there are new features, but your + * application will not be affected by them.) + * + * The "bug" version can usually be ignored, since it represents minor + * fixes. Unless, of course, your code depends upon that fix. + */ +#define kNuVersionMajor 3 +#define kNuVersionMinor 1 +#define kNuVersionBug 0 + + +/* + * =========================================================================== + * Types + * =========================================================================== + */ + +/* + * Unicode character type. For Linux and Mac OS X, filenames use "narrow" + * characters and UTF-8 encoding, which allows them to use standard file I/O + * functions like fopen(). Windows uses UTF-16, which requires a different + * character type and an alternative set of I/O functions like _wfopen(). + * + * The idea is that NufxLib API functions will operate on filenames with + * the OS dominant method, so on Windows the API accepts UTF-16. This + * definition is a bit like Windows TCHAR, but it's dependent on the OS, not + * on whether _MBCS or _UNICODE is defined. + * + * The app can include "Unichar.h" to get definitions for functions that + * switch between narrow and wide functions (e.g. "unistrlen()" becomes + * strlen() or wcslen() as appropriate). + * + * We switch based on _WIN32, because we're not really switching on + * filename-character size; the key issue is all the pesky wide I/O calls. + */ +#if defined(_WIN32) +// TODO: complete this +//# include +//# define UNICHAR wchar_t +# define UNICHAR char +#else +# define UNICHAR char +#endif + +/* + * Error values returned from functions. + * + * These are negative so that they don't conflict with system-defined + * errors (like ENOENT). A NuError can hold either. + */ +typedef enum NuError { + kNuErrNone = 0, + + kNuErrGeneric = -1, + kNuErrInternal = -2, + kNuErrUsage = -3, + kNuErrSyntax = -4, + kNuErrMalloc = -5, + kNuErrInvalidArg = -6, + kNuErrBadStruct = -7, + kNuErrUnexpectedNil = -8, + kNuErrBusy = -9, + + kNuErrSkipped = -10, /* processing skipped by request */ + kNuErrAborted = -11, /* processing aborted by request */ + kNuErrRename = -12, /* user wants to rename before extracting */ + + kNuErrFile = -20, + kNuErrFileOpen = -21, + kNuErrFileClose = -22, + kNuErrFileRead = -23, + kNuErrFileWrite = -24, + kNuErrFileSeek = -25, + kNuErrFileExists = -26, /* existed when it shouldn't */ + kNuErrFileNotFound = -27, /* didn't exist when it should have */ + kNuErrFileStat = -28, /* some sort of GetFileInfo failure */ + kNuErrFileNotReadable = -29, /* bad access permissions */ + + kNuErrDirExists = -30, /* dir exists, don't need to create it */ + kNuErrNotDir = -31, /* expected a dir, got a regular file */ + kNuErrNotRegularFile = -32, /* expected regular file, got weirdness */ + kNuErrDirCreate = -33, /* unable to create a directory */ + kNuErrOpenDir = -34, /* error opening directory */ + kNuErrReadDir = -35, /* error reading directory */ + kNuErrFileSetDate = -36, /* unable to set file date */ + kNuErrFileSetAccess = -37, /* unable to set file access permissions */ + kNuErrFileAccessDenied = -38, /* equivalent to EACCES */ + + kNuErrNotNuFX = -40, /* 'NuFile' missing; not a NuFX archive? */ + kNuErrBadMHVersion = -41, /* bad master header version */ + kNuErrRecHdrNotFound = -42, /* 'NuFX' missing; corrupted archive? */ + kNuErrNoRecords = -43, /* archive doesn't have any records */ + kNuErrBadRecord = -44, /* something about the record looked bad */ + kNuErrBadMHCRC = -45, /* bad master header CRC */ + kNuErrBadRHCRC = -46, /* bad record header CRC */ + kNuErrBadThreadCRC = -47, /* bad thread header CRC */ + kNuErrBadDataCRC = -48, /* bad CRC detected in the data */ + + kNuErrBadFormat = -50, /* compression type not supported */ + kNuErrBadData = -51, /* expansion func didn't like input */ + kNuErrBufferOverrun = -52, /* overflowed a user buffer */ + kNuErrBufferUnderrun = -53, /* underflowed a user buffer */ + kNuErrOutMax = -54, /* output limit exceeded */ + + kNuErrNotFound = -60, /* (generic) search unsuccessful */ + kNuErrRecordNotFound = -61, /* search for specific record failed */ + kNuErrRecIdxNotFound = -62, /* search by NuRecordIdx failed */ + kNuErrThreadIdxNotFound = -63, /* search by NuThreadIdx failed */ + kNuErrThreadIDNotFound = -64, /* search by NuThreadID failed */ + kNuErrRecNameNotFound = -65, /* search by storageName failed */ + kNuErrRecordExists = -66, /* found existing record with same name */ + + kNuErrAllDeleted = -70, /* attempt to delete everything */ + kNuErrArchiveRO = -71, /* archive is open in read-only mode */ + kNuErrModRecChange = -72, /* tried to change modified record */ + kNuErrModThreadChange = -73, /* tried to change modified thread */ + kNuErrThreadAdd = -74, /* adding that thread creates a conflict */ + kNuErrNotPreSized = -75, /* tried to update a non-pre-sized thread */ + kNuErrPreSizeOverflow = -76, /* too much data */ + kNuErrInvalidFilename = -77, /* invalid filename */ + + kNuErrLeadingFssep = -80, /* names in archives must not start w/sep */ + kNuErrNotNewer = -81, /* item same age or older than existing */ + kNuErrDuplicateNotFound = -82, /* "must overwrite" was set, but item DNE */ + kNuErrDamaged = -83, /* original archive may have been damaged */ + + kNuErrIsBinary2 = -90, /* this looks like a Binary II archive */ + + kNuErrUnknownFeature =-100, /* attempt to test unknown feature */ + kNuErrUnsupFeature = -101, /* feature not supported */ +} NuError; + +/* + * Return values from callback functions. + */ +typedef enum NuResult { + kNuOK = 0, + kNuSkip = 1, + kNuAbort = 2, + /*kNuAbortAll = 3,*/ + kNuRetry = 4, + kNuIgnore = 5, + kNuRename = 6, + kNuOverwrite = 7 +} NuResult; + +/* + * NuRecordIdxs are assigned to records in an archive. You may assume that + * the values are unique, but that is all. + */ +typedef uint32_t NuRecordIdx; + +/* + * NuThreadIdxs are assigned to threads within a record. Again, you may + * assume that the values are unique within a record, but that is all. + */ +typedef uint32_t NuThreadIdx; + +/* + * Thread ID, a combination of thread_class and thread_kind. Standard + * values have explicit identifiers. + */ +typedef uint32_t NuThreadID; +#define NuMakeThreadID(class, kind) /* construct a NuThreadID */ \ + ((uint32_t)(class) << 16 | (uint32_t)(kind)) +#define NuGetThreadID(pThread) /* pull NuThreadID out of NuThread */ \ + (NuMakeThreadID((pThread)->thThreadClass, (pThread)->thThreadKind)) +#define NuThreadIDGetClass(threadID) /* get threadClass from NuThreadID */ \ + ((uint16_t) ((uint32_t)(threadID) >> 16)) +#define NuThreadIDGetKind(threadID) /* get threadKind from NuThreadID */ \ + ((uint16_t) ((threadID) & 0xffff)) +#define kNuThreadClassMessage 0x0000 +#define kNuThreadClassControl 0x0001 +#define kNuThreadClassData 0x0002 +#define kNuThreadClassFilename 0x0003 +#define kNuThreadKindDataFork 0x0000 /* when class=data */ +#define kNuThreadKindDiskImage 0x0001 /* when class=data */ +#define kNuThreadKindRsrcFork 0x0002 /* when class=data */ +#define kNuThreadIDOldComment NuMakeThreadID(kNuThreadClassMessage, 0x0000) +#define kNuThreadIDComment NuMakeThreadID(kNuThreadClassMessage, 0x0001) +#define kNuThreadIDIcon NuMakeThreadID(kNuThreadClassMessage, 0x0002) +#define kNuThreadIDMkdir NuMakeThreadID(kNuThreadClassControl, 0x0000) +#define kNuThreadIDDataFork NuMakeThreadID(kNuThreadClassData, kNuThreadKindDataFork) +#define kNuThreadIDDiskImage NuMakeThreadID(kNuThreadClassData, kNuThreadKindDiskImage) +#define kNuThreadIDRsrcFork NuMakeThreadID(kNuThreadClassData, kNuThreadKindRsrcFork) +#define kNuThreadIDFilename NuMakeThreadID(kNuThreadClassFilename, 0x0000) +#define kNuThreadIDWildcard NuMakeThreadID(0xffff, 0xffff) + +/* enumerate the possible values for thThreadFormat */ +typedef enum NuThreadFormat { + kNuThreadFormatUncompressed = 0x0000, + kNuThreadFormatHuffmanSQ = 0x0001, + kNuThreadFormatLZW1 = 0x0002, + kNuThreadFormatLZW2 = 0x0003, + kNuThreadFormatLZC12 = 0x0004, + kNuThreadFormatLZC16 = 0x0005, + kNuThreadFormatDeflate = 0x0006, /* NOTE: not in NuFX standard */ + kNuThreadFormatBzip2 = 0x0007, /* NOTE: not in NuFX standard */ +} NuThreadFormat; + + +/* extract the filesystem separator char from the "file_sys_info" field */ +#define NuGetSepFromSysInfo(sysInfo) \ + ((UNICHAR) ((sysInfo) & 0xff)) +/* return a file_sys_info with a replaced filesystem separator */ +#define NuSetSepInSysInfo(sysInfo, newSep) \ + ((uint16_t) (((sysInfo) & 0xff00) | ((newSep) & 0xff)) ) + +/* GS/OS-defined file system identifiers; sadly, UNIX is not among them */ +typedef enum NuFileSysID { + kNuFileSysUnknown = 0, /* NuFX spec says use this */ + kNuFileSysProDOS = 1, + kNuFileSysDOS33 = 2, + kNuFileSysDOS32 = 3, + kNuFileSysPascal = 4, + kNuFileSysMacHFS = 5, + kNuFileSysMacMFS = 6, + kNuFileSysLisa = 7, + kNuFileSysCPM = 8, + kNuFileSysCharFST = 9, + kNuFileSysMSDOS = 10, + kNuFileSysHighSierra = 11, + kNuFileSysISO9660 = 12, + kNuFileSysAppleShare = 13 +} NuFileSysID; + +/* simplified definition of storage types */ +typedef enum NuStorageType { + kNuStorageUnknown = 0, /* (used by ProDOS for deleted files) */ + kNuStorageSeedling = 1, /* <= 512 bytes */ + kNuStorageSapling = 2, /* < 128KB */ + kNuStorageTree = 3, /* < 16MB */ + kNuStoragePascalVol = 4, /* (embedded pascal volume; rare) */ + kNuStorageExtended = 5, /* forked (any size) */ + kNuStorageDirectory = 13, /* directory */ + kNuStorageSubdirHeader = 14, /* (only used in subdir headers) */ + kNuStorageVolumeHeader = 15, /* (only used in volume dir header) */ +} NuStorageType; + +/* bit flags for NuOpenRW */ +enum { + kNuOpenCreat = 0x0001, + kNuOpenExcl = 0x0002 +}; + + +/* + * The actual NuArchive structure is opaque, and should only be visible + * to the library. We define it here as an ambiguous struct. + */ +typedef struct NuArchive NuArchive; + +/* + * Generic callback prototype. + */ +typedef NuResult (*NuCallback)(NuArchive* pArchive, void* args); + +/* + * Parameters that affect archive operations. + */ +typedef enum NuValueID { + kNuValueInvalid = 0, + kNuValueIgnoreCRC = 1, + kNuValueDataCompression = 2, + kNuValueDiscardWrapper = 3, + kNuValueEOL = 4, + kNuValueConvertExtractedEOL = 5, + kNuValueOnlyUpdateOlder = 6, + kNuValueAllowDuplicates = 7, + kNuValueHandleExisting = 8, + kNuValueModifyOrig = 9, + kNuValueMimicSHK = 10, + kNuValueMaskDataless = 11, + kNuValueStripHighASCII = 12, + kNuValueJunkSkipMax = 13, + kNuValueIgnoreLZW2Len = 14, + kNuValueHandleBadMac = 15 +} NuValueID; +typedef uint32_t NuValue; + +/* + * Enumerated values for things you pass in a NuValue. + */ +enum NuValueValue { + /* for the truly retentive */ + kNuValueFalse = 0, + kNuValueTrue = 1, + + /* for kNuValueDataCompression */ + kNuCompressNone = 10, + kNuCompressSQ = 11, + kNuCompressLZW1 = 12, + kNuCompressLZW2 = 13, + kNuCompressLZC12 = 14, + kNuCompressLZC16 = 15, + kNuCompressDeflate = 16, + kNuCompressBzip2 = 17, + + /* for kNuValueEOL */ + kNuEOLUnknown = 50, + kNuEOLCR = 51, + kNuEOLLF = 52, + kNuEOLCRLF = 53, + + /* for kNuValueConvertExtractedEOL */ + kNuConvertOff = 60, + kNuConvertOn = 61, + kNuConvertAuto = 62, + + /* for kNuValueHandleExisting */ + kNuMaybeOverwrite = 90, + kNuNeverOverwrite = 91, + kNuAlwaysOverwrite = 93, + kNuMustOverwrite = 94 +}; + + +/* + * Pull out archive attributes. + */ +typedef enum NuAttrID { + kNuAttrInvalid = 0, + kNuAttrArchiveType = 1, + kNuAttrNumRecords = 2, + kNuAttrHeaderOffset = 3, + kNuAttrJunkOffset = 4, +} NuAttrID; +typedef uint32_t NuAttr; + +/* + * Archive types. + */ +typedef enum NuArchiveType { + kNuArchiveUnknown, /* .??? */ + kNuArchiveNuFX, /* .SHK (sometimes .SDK) */ + kNuArchiveNuFXInBNY, /* .BXY */ + kNuArchiveNuFXSelfEx, /* .SEA */ + kNuArchiveNuFXSelfExInBNY, /* .BSE */ + + kNuArchiveBNY /* .BNY, .BQY - not supported */ +} NuArchiveType; + + +/* + * Some common values for "locked" and "unlocked". Under ProDOS each bit + * can be set independently, so don't use these defines to *interpret* + * what you see. They're reasonable things to *set* the access field to. + * + * The defined bits are: + * 0x80 'D' destroy enabled + * 0x40 'N' rename enabled + * 0x20 'B' file needs to be backed up + * 0x10 (reserved, must be zero) + * 0x08 (reserved, must be zero) + * 0x04 'I' file is invisible + * 0x02 'W' write enabled + * 0x01 'R' read enabled + */ +#define kNuAccessLocked 0x21 +#define kNuAccessUnlocked 0xe3 + + +/* + * NuFlush result flags. + */ +#define kNuFlushSucceeded (1L) +#define kNuFlushAborted (1L << 1) +#define kNuFlushCorrupted (1L << 2) +#define kNuFlushReadOnly (1L << 3) +#define kNuFlushInaccessible (1L << 4) + + +/* + * =========================================================================== + * NuFX archive defintions + * =========================================================================== + */ + +typedef struct NuThreadMod NuThreadMod; /* dummy def for internal struct */ +typedef union NuDataSource NuDataSource; /* dummy def for internal struct */ +typedef union NuDataSink NuDataSink; /* dummy def for internal struct */ + +/* + * NuFX Date/Time structure; same as TimeRec from IIgs "misctool.h". + */ +typedef struct NuDateTime { + uint8_t second; /* 0-59 */ + uint8_t minute; /* 0-59 */ + uint8_t hour; /* 0-23 */ + uint8_t year; /* year - 1900 */ + uint8_t day; /* 0-30 */ + uint8_t month; /* 0-11 */ + uint8_t extra; /* (must be zero) */ + uint8_t weekDay; /* 1-7 (1=sunday) */ +} NuDateTime; + +/* + * NuFX "thread" definition. + * + * Guaranteed not to have pointers in it. Can be copied with memcpy or + * assignment. + */ +typedef struct NuThread { + /* from the archive */ + uint16_t thThreadClass; + NuThreadFormat thThreadFormat; + uint16_t thThreadKind; + uint16_t thThreadCRC; /* comp or uncomp data; see rec vers */ + uint32_t thThreadEOF; + uint32_t thCompThreadEOF; + + /* extra goodies */ + NuThreadIdx threadIdx; + uint32_t actualThreadEOF; /* disk images might be off */ + long fileOffset; /* fseek offset to data in shk */ + + /* internal use only */ + uint16_t used; /* mark as uninteresting */ +} NuThread; + +/* + * NuFX "record" definition. + * + * (Note to developers: update Nu_AddRecord if this changes.) + * + * The filenames are in Mac OS Roman format. It's arguable whether MOR + * strings should be part of the interface at all. However, the API + * pre-dates the inclusion of Unicode support, and I'm leaving it alone. + */ +#define kNufxIDLen 4 /* len of 'NuFX' with funky MSBs */ +#define kNuReasonableAttribCount 256 +#define kNuReasonableFilenameLen 1024 +#define kNuReasonableTotalThreads 16 +#define kNuMaxRecordVersion 3 /* max we can handle */ +#define kNuOurRecordVersion 3 /* what we write */ +typedef struct NuRecord { + /* version 0+ */ + uint8_t recNufxID[kNufxIDLen]; + uint16_t recHeaderCRC; + uint16_t recAttribCount; + uint16_t recVersionNumber; + uint32_t recTotalThreads; + NuFileSysID recFileSysID; + uint16_t recFileSysInfo; + uint32_t recAccess; + uint32_t recFileType; + uint32_t recExtraType; + uint16_t recStorageType; /* NuStorage*,file_sys_block_size */ + NuDateTime recCreateWhen; + NuDateTime recModWhen; + NuDateTime recArchiveWhen; + + /* option lists only in version 1+ */ + uint16_t recOptionSize; + uint8_t* recOptionList; /* NULL if v0 or recOptionSize==0 */ + + /* data specified by recAttribCount, not accounted for by option list */ + int32_t extraCount; + uint8_t* extraBytes; + + uint16_t recFilenameLength; /* usually zero */ + char* recFilenameMOR; /* doubles as disk volume_name */ + + /* extra goodies; "dirtyHeader" does not apply to anything below */ + NuRecordIdx recordIdx; /* session-unique record index */ + char* threadFilenameMOR; /* extracted from filename thread */ + char* newFilenameMOR; /* memorized during "add file" call */ + const char* filenameMOR; /* points at recFilen or threadFilen */ + uint32_t recHeaderLength; /* size of rec hdr, incl thread hdrs */ + uint32_t totalCompLength; /* total len of data in archive file */ + uint32_t fakeThreads; /* used by "MaskDataless" */ + int isBadMac; /* malformed "bad mac" header */ + + long fileOffset; /* file offset of record header */ + + /* use provided interface to access this */ + struct NuThread* pThreads; /* ptr to thread array */ + + /* private -- things the application shouldn't look at */ + struct NuRecord* pNext; /* used internally */ + NuThreadMod* pThreadMods; /* used internally */ + short dirtyHeader; /* set in "copy" when hdr fields uptd */ + short dropRecFilename; /* if set, we're dropping this name */ +} NuRecord; + +/* + * NuFX "master header" definition. + * + * The "mhReserved2" entry doesn't appear in my copy of the $e0/8002 File + * Type Note, but as best as I can recall the MH block must be 48 bytes. + */ +#define kNufileIDLen 6 /* length of 'NuFile' with funky MSBs */ +#define kNufileMasterReserved1Len 8 +#define kNufileMasterReserved2Len 6 +#define kNuMaxMHVersion 2 /* max we can handle */ +#define kNuOurMHVersion 2 /* what we write */ +typedef struct NuMasterHeader { + uint8_t mhNufileID[kNufileIDLen]; + uint16_t mhMasterCRC; + uint32_t mhTotalRecords; + NuDateTime mhArchiveCreateWhen; + NuDateTime mhArchiveModWhen; + uint16_t mhMasterVersion; + uint8_t mhReserved1[kNufileMasterReserved1Len]; + uint32_t mhMasterEOF; + uint8_t mhReserved2[kNufileMasterReserved2Len]; + + /* private -- internal use only */ + short isValid; +} NuMasterHeader; + + +/* + * =========================================================================== + * Misc declarations + * =========================================================================== + */ + +/* + * Record attributes that can be changed with NuSetRecordAttr. This is + * a small subset of the full record. + */ +typedef struct NuRecordAttr { + NuFileSysID fileSysID; + /*uint16_t fileSysInfo;*/ + uint32_t access; + uint32_t fileType; + uint32_t extraType; + NuDateTime createWhen; + NuDateTime modWhen; + NuDateTime archiveWhen; +} NuRecordAttr; + +/* + * Some additional details about a file. + * + * Ideally (from an API cleanliness perspective) the storage name would + * be passed around as UTF-8 and converted internally. Passing it as + * MOR required fewer changes to the library, and allows us to avoid + * having to deal with illegal characters. + */ +typedef struct NuFileDetails { + /* used during AddFile call */ + NuThreadID threadID; /* data, rsrc, disk img? */ + const void* origName; /* arbitrary pointer, usually a string */ + + /* these go straight into the NuRecord */ + const char* storageNameMOR; + NuFileSysID fileSysID; + uint16_t fileSysInfo; + uint32_t access; + uint32_t fileType; + uint32_t extraType; + uint16_t storageType; /* use Unknown, or disk block size */ + NuDateTime createWhen; + NuDateTime modWhen; + NuDateTime archiveWhen; +} NuFileDetails; + + +/* + * Passed into the SelectionFilter callback. + */ +typedef struct NuSelectionProposal { + const NuRecord* pRecord; + const NuThread* pThread; +} NuSelectionProposal; + +/* + * Passed into the OutputPathnameFilter callback. + */ +typedef struct NuPathnameProposal { + const UNICHAR* pathnameUNI; + UNICHAR filenameSeparator; + const NuRecord* pRecord; + const NuThread* pThread; + + const UNICHAR* newPathnameUNI; + UNICHAR newFilenameSeparator; + /*NuThreadID newStorage;*/ + NuDataSink* newDataSink; +} NuPathnameProposal; + + +/* used by error handler and progress updater to indicate what we're doing */ +typedef enum NuOperation { + kNuOpUnknown = 0, + kNuOpAdd, + kNuOpExtract, + kNuOpTest, + kNuOpDelete, /* not used for progress updates */ + kNuOpContents /* not used for progress updates */ +} NuOperation; + +/* state of progress when adding or extracting */ +typedef enum NuProgressState { + kNuProgressPreparing, /* not started yet */ + kNuProgressOpening, /* opening files */ + + kNuProgressAnalyzing, /* analyzing data */ + kNuProgressCompressing, /* compressing data */ + kNuProgressStoring, /* storing (no compression) data */ + kNuProgressExpanding, /* expanding data */ + kNuProgressCopying, /* copying data (in or out) */ + + kNuProgressDone, /* all done, success */ + kNuProgressSkipped, /* all done, we skipped this one */ + kNuProgressAborted, /* all done, user cancelled the operation */ + kNuProgressFailed /* all done, failure */ +} NuProgressState; + +/* + * Passed into the ProgressUpdater callback. All pointers become + * invalid when the callback returns. + * + * [ Thought for the day: add an optional flag that causes us to only + * call the progressFunc when the "percentComplete" changes by more + * than a specified amount. ] + */ +typedef struct NuProgressData { + /* what are we doing */ + NuOperation operation; + /* what specifically are we doing */ + NuProgressState state; + /* how far along are we */ + short percentComplete; /* 0-100 */ + + /* original pathname (in archive for expand, on disk for compress) */ + const UNICHAR* origPathnameUNI; + /* processed pathname (PathnameFilter for expand, in-record for compress) */ + const UNICHAR* pathnameUNI; + /* basename of "pathname" (for convenience) */ + const UNICHAR* filenameUNI; + /* pointer to the record we're expanding from */ + const NuRecord* pRecord; + + uint32_t uncompressedLength; /* size of uncompressed data */ + uint32_t uncompressedProgress; /* #of bytes in/out */ + + struct { + NuThreadFormat threadFormat; /* compression being applied */ + } compress; + + struct { + uint32_t totalCompressedLength; /* all "data" threads */ + uint32_t totalUncompressedLength; + + /*uint32_t compressedLength; * size of compressed data */ + /*uint32_t compressedProgress; * #of compressed bytes in/out*/ + const NuThread* pThread; /* thread we're working on */ + NuValue convertEOL; /* set if LF/CR conv is on */ + } expand; + + /* pay no attention */ + NuCallback progressFunc; +} NuProgressData; + +/* + * Passed into the ErrorHandler callback. + */ +typedef struct NuErrorStatus { + NuOperation operation; /* were we adding, extracting, ?? */ + NuError err; /* library error code */ + int sysErr; /* system error code, if applicable */ + const UNICHAR* message; /* (optional) message to user */ + const NuRecord* pRecord; /* relevant record, if any */ + const UNICHAR* pathnameUNI; /* problematic pathname, if any */ + const void* origPathname; /* original pathname ref, if any */ + UNICHAR filenameSeparator; /* fssep for pathname, if any */ + /*char origArchiveTouched;*/ + + char canAbort; /* give option to abort */ + /*char canAbortAll;*/ /* abort + discard all recent changes */ + char canRetry; /* give option to retry same op */ + char canIgnore; /* give option to ignore error */ + char canSkip; /* give option to skip this file/rec */ + char canRename; /* give option to rename file */ + char canOverwrite; /* give option to overwrite file */ +} NuErrorStatus; + +/* + * Error message callback gets one of these. + */ +typedef struct NuErrorMessage { + const char* message; /* the message itself (UTF-8) */ + NuError err; /* relevant error code (may be none) */ + short isDebug; /* set for debug-only messages */ + + /* these identify where the message originated if lib built w/debug set */ + const char* file; /* source file (UTF-8) */ + int line; /* line number */ + const char* function; /* function name (might be NULL) */ +} NuErrorMessage; + + +/* + * Options for the NuTestFeature function. + */ +typedef enum NuFeature { + kNuFeatureUnknown = 0, + + kNuFeatureCompressSQ = 1, /* kNuThreadFormatHuffmanSQ */ + kNuFeatureCompressLZW = 2, /* kNuThreadFormatLZW1 and LZW2 */ + kNuFeatureCompressLZC = 3, /* kNuThreadFormatLZC12 and LZC16 */ + kNuFeatureCompressDeflate = 4, /* kNuThreadFormatDeflate */ + kNuFeatureCompressBzip2 = 5, /* kNuThreadFormatBzip2 */ +} NuFeature; + + +/* + * =========================================================================== + * Function prototypes + * =========================================================================== + */ + +/* + * Win32 dll magic. + */ +#if defined(_WIN32) +# include +# if defined(NUFXLIB_EXPORTS) + /* building the NufxLib DLL */ +# define NUFXLIB_API __declspec(dllexport) +# elif defined (NUFXLIB_DLL) + /* building to link against the NufxLib DLL */ +# define NUFXLIB_API __declspec(dllimport) +# else + /* using static libs */ +# define NUFXLIB_API +# endif +#else + /* not using Win32... hooray! */ +# define NUFXLIB_API +#endif + +/* streaming and non-streaming read-only interfaces */ +NUFXLIB_API NuError NuStreamOpenRO(FILE* infp, NuArchive** ppArchive); +NUFXLIB_API NuError NuContents(NuArchive* pArchive, NuCallback contentFunc); +NUFXLIB_API NuError NuExtract(NuArchive* pArchive); +NUFXLIB_API NuError NuTest(NuArchive* pArchive); + +/* strictly non-streaming read-only interfaces */ +NUFXLIB_API NuError NuOpenRO(const UNICHAR* archivePathnameUNI, + NuArchive** ppArchive); +NUFXLIB_API NuError NuExtractRecord(NuArchive* pArchive, NuRecordIdx recordIdx); +NUFXLIB_API NuError NuExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink); +NUFXLIB_API NuError NuTestRecord(NuArchive* pArchive, NuRecordIdx recordIdx); +NUFXLIB_API NuError NuGetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord); +NUFXLIB_API NuError NuGetRecordIdxByName(NuArchive* pArchive, + const char* nameMOR, NuRecordIdx* pRecordIdx); +NUFXLIB_API NuError NuGetRecordIdxByPosition(NuArchive* pArchive, + uint32_t position, NuRecordIdx* pRecordIdx); + +/* read/write interfaces */ +NUFXLIB_API NuError NuOpenRW(const UNICHAR* archivePathnameUNI, + const UNICHAR* tempPathnameUNI, uint32_t flags, + NuArchive** ppArchive); +NUFXLIB_API NuError NuFlush(NuArchive* pArchive, uint32_t* pStatusFlags); +NUFXLIB_API NuError NuAddRecord(NuArchive* pArchive, + const NuFileDetails* pFileDetails, NuRecordIdx* pRecordIdx); +NUFXLIB_API NuError NuAddThread(NuArchive* pArchive, NuRecordIdx recordIdx, + NuThreadID threadID, NuDataSource* pDataSource, + NuThreadIdx* pThreadIdx); +NUFXLIB_API NuError NuAddFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + const NuFileDetails* pFileDetails, short fromRsrcFork, + NuRecordIdx* pRecordIdx); +NUFXLIB_API NuError NuRename(NuArchive* pArchive, NuRecordIdx recordIdx, + const char* pathnameMOR, char fssep); +NUFXLIB_API NuError NuSetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr); +NUFXLIB_API NuError NuUpdatePresizedThread(NuArchive* pArchive, + NuThreadIdx threadIdx, NuDataSource* pDataSource, int32_t* pMaxLen); +NUFXLIB_API NuError NuDelete(NuArchive* pArchive); +NUFXLIB_API NuError NuDeleteRecord(NuArchive* pArchive, NuRecordIdx recordIdx); +NUFXLIB_API NuError NuDeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx); + +/* general interfaces */ +NUFXLIB_API NuError NuClose(NuArchive* pArchive); +NUFXLIB_API NuError NuAbort(NuArchive* pArchive); +NUFXLIB_API NuError NuGetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader); +NUFXLIB_API NuError NuGetExtraData(NuArchive* pArchive, void** ppData); +NUFXLIB_API NuError NuSetExtraData(NuArchive* pArchive, void* pData); +NUFXLIB_API NuError NuGetValue(NuArchive* pArchive, NuValueID ident, + NuValue* pValue); +NUFXLIB_API NuError NuSetValue(NuArchive* pArchive, NuValueID ident, + NuValue value); +NUFXLIB_API NuError NuGetAttr(NuArchive* pArchive, NuAttrID ident, + NuAttr* pAttr); +NUFXLIB_API NuError NuDebugDumpArchive(NuArchive* pArchive); + +/* sources and sinks */ +NUFXLIB_API NuError NuCreateDataSourceForFile(NuThreadFormat threadFormat, + uint32_t otherLen, const UNICHAR* pathnameUNI, + short isFromRsrcFork, NuDataSource** ppDataSource); +NUFXLIB_API NuError NuCreateDataSourceForFP(NuThreadFormat threadFormat, + uint32_t otherLen, FILE* fp, long offset, long length, + NuCallback closeFunc, NuDataSource** ppDataSource); +NUFXLIB_API NuError NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, + uint32_t otherLen, const uint8_t* buffer, long offset, + long length, NuCallback freeFunc, NuDataSource** ppDataSource); +NUFXLIB_API NuError NuFreeDataSource(NuDataSource* pDataSource); +NUFXLIB_API NuError NuDataSourceSetRawCrc(NuDataSource* pDataSource, + uint16_t crc); +NUFXLIB_API NuError NuCreateDataSinkForFile(short doExpand, NuValue convertEOL, + const UNICHAR* pathnameUNI, UNICHAR fssep, NuDataSink** ppDataSink); +NUFXLIB_API NuError NuCreateDataSinkForFP(short doExpand, NuValue convertEOL, + FILE* fp, NuDataSink** ppDataSink); +NUFXLIB_API NuError NuCreateDataSinkForBuffer(short doExpand, + NuValue convertEOL, uint8_t* buffer, uint32_t bufLen, + NuDataSink** ppDataSink); +NUFXLIB_API NuError NuFreeDataSink(NuDataSink* pDataSink); +NUFXLIB_API NuError NuDataSinkGetOutCount(NuDataSink* pDataSink, + uint32_t* pOutCount); + +/* miscellaneous non-archive operations */ +NUFXLIB_API NuError NuGetVersion(int32_t* pMajorVersion, int32_t* pMinorVersion, + int32_t* pBugVersion, const char** ppBuildDate, + const char** ppBuildFlags); +NUFXLIB_API const char* NuStrError(NuError err); +NUFXLIB_API NuError NuTestFeature(NuFeature feature); +NUFXLIB_API void NuRecordCopyAttr(NuRecordAttr* pRecordAttr, + const NuRecord* pRecord); +NUFXLIB_API NuError NuRecordCopyThreads(const NuRecord* pRecord, + NuThread** ppThreads); +NUFXLIB_API uint32_t NuRecordGetNumThreads(const NuRecord* pRecord); +NUFXLIB_API const NuThread* NuThreadGetByIdx(const NuThread* pThread, + int32_t idx); +NUFXLIB_API short NuIsPresizedThreadID(NuThreadID threadID); +NUFXLIB_API size_t NuConvertMORToUNI(const char* stringMOR, + UNICHAR* bufUNI, size_t bufSize); +NUFXLIB_API size_t NuConvertUNIToMOR(const UNICHAR* stringUNI, + char* bufMOR, size_t bufSize); + +#define NuGetThread(pRecord, idx) ( (const NuThread*) \ + ((uint32_t) (idx) < (pRecord)->recTotalThreads ? \ + &(pRecord)->pThreads[(idx)] : NULL) \ + ) + + +/* callback setters */ +#define kNuInvalidCallback ((NuCallback) 1) +NUFXLIB_API NuCallback NuSetSelectionFilter(NuArchive* pArchive, + NuCallback filterFunc); +NUFXLIB_API NuCallback NuSetOutputPathnameFilter(NuArchive* pArchive, + NuCallback filterFunc); +NUFXLIB_API NuCallback NuSetProgressUpdater(NuArchive* pArchive, + NuCallback updateFunc); +NUFXLIB_API NuCallback NuSetErrorHandler(NuArchive* pArchive, + NuCallback errorFunc); +NUFXLIB_API NuCallback NuSetErrorMessageHandler(NuArchive* pArchive, + NuCallback messageHandlerFunc); +NUFXLIB_API NuCallback NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc); + + +#ifdef __cplusplus +} +#endif + +#endif /*NUFXLIB_NUFXLIB_H*/ diff --git a/nufxlib/NufxLibPriv.h b/nufxlib/NufxLibPriv.h new file mode 100644 index 0000000..c18a4b7 --- /dev/null +++ b/nufxlib/NufxLibPriv.h @@ -0,0 +1,861 @@ +/* + * 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. + * + * Global internal declarations and definitions. + */ +#ifndef NUFXLIB_NUFXLIBPRIV_H +#define NUFXLIB_NUFXLIBPRIV_H + +/* include files that everybody needs */ +#include "SysDefs.h" +#include "NufxLib.h" +#include "MiscStuff.h" + +#ifdef USE_DMALLOC +/* enable with something like "dmalloc -l logfile -i 100 medium" */ +# include "dmalloc.h" +#endif + + +/* + * =========================================================================== + * NuArchive definition + * =========================================================================== + */ + +/* + * Archives can be opened in streaming read-only, non-streaming read-only, + * and non-streaming read-write mode. + */ +typedef enum NuOpenMode { + kNuOpenUnknown, + kNuOpenStreamingRO, + kNuOpenRO, + kNuOpenRW +} NuOpenMode; +#define Nu_IsStreaming(pArchive) ((pArchive)->openMode == kNuOpenStreamingRO) +#define Nu_IsReadOnly(pArchive) ((pArchive)->openMode == kNuOpenStreamingRO ||\ + (pArchive)->openMode == kNuOpenRO) + +#ifdef FOPEN_WANTS_B +# define kNuFileOpenReadOnly "rb" +# define kNuFileOpenReadWrite "r+b" +# define kNuFileOpenWriteTrunc "wb" +# define kNuFileOpenReadWriteCreat "w+b" +#else +# define kNuFileOpenReadOnly "r" +# define kNuFileOpenReadWrite "r+" +# define kNuFileOpenWriteTrunc "w" +# define kNuFileOpenReadWriteCreat "w+" +#endif + + +/* + * Some NuFX and Binary II definitions. + */ +#define kNuMasterHeaderSize 48 /* size of fixed-length master header */ +#define kNuRecordHeaderBaseSize 58 /* size of rec hdr up to variable stuff */ +#define kNuThreadHeaderSize 16 /* size of fixed-length thread header */ +#define kNuDefaultFilenameThreadSize 32 /* default size of filename thred */ +#define kNuDefaultCommentSize 200 /* size of GSHK-mimic comments */ +#define kNuBinary2BlockSize 128 /* size of bxy header and padding */ +#define kNuSEAOffset 0x2ee5 /* fixed(??) offset to data in SEA */ + +#define kNuInitialChunkCRC 0x0000 /* start for CRC in LZW/1 chunk */ +#define kNuInitialThreadCRC 0xffff /* start for CRC in v3 thread header */ + +/* size of general-purpose compression buffer */ +#define kNuGenCompBufSize 32768 + +#define kNuCharLF 0x0a +#define kNuCharCR 0x0d + + +/* + * A list of records. Generally we use one of these for read-only + * archives, and two for read-write. + * + * The "loaded" flag is set when we've made some use of the record set. + * Relying on "numRecords" won't always work; for example, if the "copy" + * record set was initialized from "orig", and then had all of its records + * deleted, you couldn't look at "numRecords" and decide whether it was + * appropriate to use "orig" or not. + */ +typedef struct NuRecordSet { + Boolean loaded; + uint32_t numRecords; + NuRecord* nuRecordHead; + NuRecord* nuRecordTail; +} NuRecordSet; + +/* + * Archive state. + */ +struct NuArchive { + uint32_t structMagic; + Boolean busy; + + NuOpenMode openMode; + Boolean newlyCreated; + UNICHAR* archivePathnameUNI; /* pathname or "(stream)" */ + FILE* archiveFp; + NuArchiveType archiveType; + + /* stuff before NuFX; both offsets are from 0, i.e. hdrOff includes junk */ + long junkOffset; /* skip past leading junk */ + long headerOffset; /* adjustment for BXY/SEA/BSE */ + + UNICHAR* tmpPathnameUNI; /* temp file, for writes */ + FILE* tmpFp; + + /* used during initial processing; helps avoid ftell() calls */ + long currentOffset; + + /* setting this changes Extract into Test */ + Boolean testMode; + + /* clumsy way of remembering name used for other fork in forked file */ + const UNICHAR* lastFileCreatedUNI; + /* clumsy way to avoid trying to create the same subdir several times */ + const UNICHAR* lastDirCreatedUNI; + + /* master header from the archive */ + NuMasterHeader masterHeader; /* original */ + NuMasterHeader newMasterHeader; /* working copy during update */ + + /* list of records from archive, plus some extra state */ + NuRecordIdx recordIdxSeed; /* where the NuRecordIdxs start */ + NuRecordIdx nextRecordIdx; /* next record gets this value */ + Boolean haveToc; /* set if we have full TOC */ + NuRecordSet origRecordSet; /* records from archive */ + NuRecordSet copyRecordSet; /* copy of orig, for write ops */ + NuRecordSet newRecordSet; /* newly-added records */ + + /* state for compression functions */ + uint8_t* compBuf; /* large general-purpose buffer */ + void* lzwCompressState; /* state for LZW/1 and LZW/2 */ + void* lzwExpandState; /* state for LZW/1 and LZW/2 */ + + /* options and attributes that the user can set */ + /* (these can be changed by a callback, so don't cache them internally) */ + void* extraData; /* application-defined pointer */ + + NuValue valAllowDuplicates; /* allow dups when adding? */ + NuValue valConvertExtractedEOL; /* convert EOL during extract? */ + NuValue valDataCompression; /* how to compress when adding? */ + NuValue valDiscardWrapper; /* remove BNY or SEA header? */ + NuValue valEOL; /* EOL value to convert to */ + NuValue valHandleExisting; /* how to deal with existing files*/ + NuValue valIgnoreCRC; /* don't compute or test CRCs */ + NuValue valMaskDataless; /* alter Records w/o data threads */ + NuValue valMimicSHK; /* mimic some ShrinkIt quirks */ + NuValue valModifyOrig; /* modify original arc in place? */ + NuValue valOnlyUpdateOlder; /* modify original arc in place? */ + NuValue valStripHighASCII; /* during EOL conv, strip hi bit? */ + NuValue valJunkSkipMax; /* scan this far for header */ + NuValue valIgnoreLZW2Len; /* don't verify LZW/II len field */ + NuValue valHandleBadMac; /* handle "bad Mac" archives */ + + /* callback functions */ + NuCallback selectionFilterFunc; + NuCallback outputPathnameFunc; + NuCallback progressUpdaterFunc; + NuCallback errorHandlerFunc; + NuCallback messageHandlerFunc; +}; + +#define kNuArchiveStructMagic 0xc0edbabe + +#define kNuDefaultRecordName "UNKNOWN" /* use ASCII charset */ + + +/* + * =========================================================================== + * ThreadMod definition + * =========================================================================== + */ + +/* operations we can perform on threads in a record */ +typedef enum ThreadModKind { + kNuThreadModUnknown = 0, + + kNuThreadModAdd, + kNuThreadModUpdate, + kNuThreadModDelete +} ThreadModKind; + +/* + * We attach a list of these to records we plan to modify. Care is taken + * to ensure that they don't conflict, e.g. you can't update a thread + * right after you delete it, nor delete one you have modified. + */ +struct NuThreadMod { + union { + ThreadModKind kind; + + struct { + ThreadModKind kind; + Boolean used; + } generic; + + struct { + ThreadModKind kind; + Boolean used; + Boolean isPresized; + NuThreadIdx threadIdx; + NuThreadID threadID; + NuThreadFormat threadFormat; + NuDataSource* pDataSource; + } add; + + struct { + ThreadModKind kind; + Boolean used; + NuThreadIdx threadIdx; + NuDataSource* pDataSource; + } update; + + struct { + ThreadModKind kind; + Boolean used; + NuThreadIdx threadIdx; + NuThreadID threadID; /* used for watching filename threads */ + } delete; + } entry; + + struct NuThreadMod* pNext; +}; + + +/* + * =========================================================================== + * NuFunnel/NuStraw definition + * =========================================================================== + */ + +#define kNuFunnelBufSize 16384 + +/* + * File funnel definition. This is used for writing output to files + * (so we can do things like pipe compressed output through an LF->CR + * converter) and archive files (so we can halt compression when the + * output size exceeds the uncompressed original). [ for various reasons, + * I'm not using this on the archive anymore. ] + * + * Funnels are unidirectional. You write data into them with a + * function call; the top-level action (which is usually compressing or + * expanding data) reads from the input and crams things into the pipe. + * We could fully abstract the concept, and write the compression + * functions so that they operate as a Funnel filter, but it's much + * easier to write block-oriented compression than stream-oriented (and + * more to the point, the ShrinkIt LZW functions are very much + * block-oriented). + */ +typedef struct NuFunnel { + /* data storage */ + uint8_t* buffer; /* kNuFunnelBufSize worth of storage */ + long bufCount; /* #of bytes in buffer */ + + /* text conversion; if "auto", on first flush we convert to "on" or "off" */ + NuValue convertEOL; /* on/off/auto */ + NuValue convertEOLTo; /* EOL to switch to */ + NuValue convertEOLFrom; /* EOL terminator we think we found */ + Boolean checkStripHighASCII; /* do we want to check for it? */ + Boolean doStripHighASCII; /* strip high ASCII during EOL conv */ + Boolean lastCR; /* was last char a CR? */ + + Boolean isFirstWrite; /* cleared on first write */ + +#if 0 + uint32_t inCount; /* total #of bytes in the top */ + uint32_t outCount; /* total #of bytes out the bottom */ + + uint32_t outMax; /* flag an err when outCount exceeds this */ + Boolean outMaxExceeded; /* in fact, it's this flag */ +#endif + + /* update this when stuff happens */ + NuProgressData* pProgress; + + /* data goeth out here */ + NuDataSink* pDataSink; +} NuFunnel; + + +/* + * File straw definition. This is used for slurping up input data. + * + * Mostly this is an encapsulation of an input source and a progress + * updater, useful for reading uncompressed data and feeding it to a + * compressor. It doesn't make sense to show a thermometer based on + * compressed output, since we don't know how big the eventual result + * will be, so we want to do it for the input. + */ +typedef struct NuStraw { + /* update this when stuff happens */ + NuProgressData* pProgress; + + /* data cometh in here */ + NuDataSource* pDataSource; + + /* progress update fields */ + uint32_t lastProgress; + uint32_t lastDisplayed; +} NuStraw; + +/*NuError Nu_CopyStreamToStream(FILE* outfp, FILE* infp, uint32_t count);*/ + + +/* + * =========================================================================== + * Data source and sink abstractions + * =========================================================================== + */ + +/* + * DataSource is used when adding data to an archive. + */ + +typedef enum NuDataSourceType { + kNuDataSourceUnknown = 0, + kNuDataSourceFromFile, + kNuDataSourceFromFP, + kNuDataSourceFromBuffer +} NuDataSourceType; + +typedef struct NuDataSourceCommon { + NuDataSourceType sourceType; + NuThreadFormat threadFormat; /* is it already compressed? */ + uint16_t rawCrc; /* crc for already-compressed data*/ + /*Boolean doClose; \* close on completion? */ + uint32_t dataLen; /* length of data (var for buf) */ + uint32_t otherLen; /* uncomp len or preset buf size */ + int refCount; /* so we can copy structs */ +} NuDataSourceCommon; + +union NuDataSource { + NuDataSourceType sourceType; + + NuDataSourceCommon common; + + struct { + NuDataSourceCommon common; + UNICHAR* pathnameUNI; + Boolean fromRsrcFork; + + /* temp storage; only valid when processing in library */ + FILE* fp; + } fromFile; + + struct { + NuDataSourceCommon common; + FILE* fp; + long offset; /* starting offset */ + + NuCallback fcloseFunc; /* how to fclose the file */ + } fromFP; + + struct { + NuDataSourceCommon common; + const uint8_t* buffer; /* non-const if doClose=true */ + long offset; /* starting offset */ + + long curOffset; /* current offset */ + long curDataLen; /* remaining data */ + + NuCallback freeFunc; /* how to free data */ + } fromBuffer; +}; + + +/* + * DataSink is used when extracting data from an archive. + */ + +typedef enum NuDataSinkType { + kNuDataSinkUnknown = 0, + kNuDataSinkToFile, + kNuDataSinkToFP, + kNuDataSinkToBuffer, + kNuDataSinkToVoid +} NuDataSinkType; + +typedef struct NuDataSinkCommon { + NuDataSinkType sinkType; + Boolean doExpand; /* expand file? */ + NuValue convertEOL; /* convert EOL? (req "expand") */ + uint32_t outCount; +} NuDataSinkCommon; + +union NuDataSink { + NuDataSinkType sinkType; + + NuDataSinkCommon common; + + struct { + NuDataSinkCommon common; + UNICHAR* pathnameUNI; /* file to open */ + UNICHAR fssep; + + /* temp storage; must be NULL except when processing in library */ + FILE* fp; + } toFile; + + struct { + NuDataSinkCommon common; + FILE* fp; + } toFP; + + struct { + NuDataSinkCommon common; + uint8_t* buffer; + uint32_t bufLen; /* max amount of data "buffer" holds */ + NuError stickyErr; + } toBuffer; +}; + + +/* + * =========================================================================== + * Function prototypes + * =========================================================================== + */ + +/* + * This is a little unpleasant. This blob of stuff gets stuffed in as + * the first arguments to Nu_ReportError, so we don't have to type them + * in every time we use the function. It would've been much easier to + * use a gcc-style varargs macro, but not everybody uses gcc. + * + * TODO: Visual C++ has vararg macros now; time to replace this. + */ +#ifdef HAS__FUNCTION__ +# define _FUNCTION_ __FUNCTION__ +#else +# define _FUNCTION_ "" +#endif + +#define NU_BLOB pArchive, __FILE__, __LINE__, _FUNCTION_, false +#define NU_BLOB_DEBUG pArchive, __FILE__, __LINE__, _FUNCTION_, true +#define NU_NILBLOB NULL, __FILE__, __LINE__, _FUNCTION_, false + +#ifdef DEBUG_MSGS +# define DebugShowError(err) \ + Nu_ReportError(pArchive, __FILE__, __LINE__, _FUNCTION_, \ + true, err, "(DEBUG)"); +#else +# define DebugShowError(err) ((void)0) +#endif + +/* + * The BailError macro serves two purposes. First, it's a convenient + * way to avoid typing, "if (err != kNuErrNone) goto bail;". Second, + * when the library is built with debugging enabled, it vitually gives + * us a stack trace of exiting functions. This makes it easier to debug + * problems sent in as screen dumps via e-mail. + */ +#define BailError(err) { \ + if ((err) != kNuErrNone) { \ + /* [should this be debug-only, or all the time?] */ \ + DebugShowError(err); \ + goto bail; \ + } \ + } +#define BailErrorQuiet(err) { \ + if ((err) != kNuErrNone) \ + goto bail; \ + } +#define BailNil(val) { \ + if ((val) == NULL) { \ + err = kNuErrUnexpectedNil; \ + BailError(err); \ + } \ + } +#define BailAlloc(val) { \ + if ((val) == NULL) { \ + err = kNuErrMalloc; \ + BailError(err); \ + } \ + } + + +/* + * Internal function prototypes and inline functions. + */ + +/* Archive.c */ +void Nu_MasterHeaderCopy(NuArchive* pArchive, NuMasterHeader* pDstHeader, + const NuMasterHeader* pSrcHeader); +NuError Nu_GetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader); +NuRecordIdx Nu_GetNextRecordIdx(NuArchive* pArchive); +NuThreadIdx Nu_GetNextThreadIdx(NuArchive* pArchive); +NuError Nu_CopyWrapperToTemp(NuArchive* pArchive); +NuError Nu_UpdateWrapper(NuArchive* pArchive, FILE* fp); +NuError Nu_AdjustWrapperPadding(NuArchive* pArchive, FILE* fp); +NuError Nu_AllocCompressionBufferIFN(NuArchive* pArchive); +NuError Nu_StreamOpenRO(FILE* infp, NuArchive** ppArchive); +NuError Nu_OpenRO(const UNICHAR* archivePathnameUNI, NuArchive** ppArchive); +NuError Nu_OpenRW(const UNICHAR* archivePathnameUNI, + const UNICHAR* tempPathnameUNI, uint32_t flags, NuArchive** ppArchive); +NuError Nu_WriteMasterHeader(NuArchive* pArchive, FILE* fp, + NuMasterHeader* pMasterHeader); +NuError Nu_Close(NuArchive* pArchive); +NuError Nu_Abort(NuArchive* pArchive); +NuError Nu_RenameTempToArchive(NuArchive* pArchive); +NuError Nu_DeleteArchiveFile(NuArchive* pArchive); + +/* ArchiveIO.c */ +uint8_t Nu_ReadOneC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc); +uint8_t Nu_ReadOne(NuArchive* pArchive, FILE* fp); +void Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uint8_t val, uint16_t* pCrc); +void Nu_WriteOne(NuArchive* pArchive, FILE* fp, uint8_t val); +uint16_t Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc); +uint16_t Nu_ReadTwo(NuArchive* pArchive, FILE* fp); +void Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, uint16_t val, uint16_t* pCrc); +void Nu_WriteTwo(NuArchive* pArchive, FILE* fp, uint16_t val); +uint32_t Nu_ReadFourC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc); +uint32_t Nu_ReadFour(NuArchive* pArchive, FILE* fp); +void Nu_WriteFourC(NuArchive* pArchive, FILE* fp, uint32_t val, uint16_t* pCrc); +void Nu_WriteFour(NuArchive* pArchive, FILE* fp, uint32_t val); +NuDateTime Nu_ReadDateTimeC(NuArchive* pArchive, FILE* fp, uint16_t* pCrc); +NuDateTime Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, uint16_t* pCrc); +void Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime, + uint16_t* pCrc); +void Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime); +void Nu_ReadBytesC(NuArchive* pArchive, FILE* fp, void* vbuffer, long count, + uint16_t* pCrc); +void Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count); +void Nu_WriteBytesC(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count, uint16_t* pCrc); +void Nu_WriteBytes(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count); +NuError Nu_HeaderIOFailed(NuArchive* pArchive, FILE* fp); +NuError Nu_SeekArchive(NuArchive* pArchive, FILE* fp, long offset, + int ptrname); +NuError Nu_RewindArchive(NuArchive* pArchive); + +/* Bzip2.c */ +NuError Nu_CompressBzip2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_ExpandBzip2(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc); + +/* Charset.c */ +size_t Nu_ConvertMORToUNI(const char* stringMOR, UNICHAR* bufUNI, + size_t bufSize); +UNICHAR* Nu_CopyMORToUNI(const char* stringMOR); +size_t Nu_ConvertUNIToMOR(const UNICHAR* stringUNI, char* bufMOR, + size_t bufSize); + +/* Compress.c */ +NuError Nu_CompressToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, NuThreadFormat sourceFormat, + NuThreadFormat targetFormat, NuProgressData* progressData, FILE* dstFp, + NuThread* pThread); +NuError Nu_CopyPresizedToArchive(NuArchive* pArchive, + NuDataSource* pDataSource, NuThreadID threadID, FILE* dstFp, + NuThread* pThread, char** ppSavedCopy); + +/* Crc16.c */ +extern const uint16_t gNuCrc16Table[256]; +uint16_t Nu_CalcCRC16(uint16_t seed, const uint8_t* ptr, int count); +/* + * Update the CRC-16. + * + * _val (uint8_t) is the byte to add to the CRC. It's evaluated once. + * _crc (uint16_t) is the previous CRC. It's evaluated twice. + * Returns the updated CRC as a uint16_t. + */ +#define Nu_UpdateCRC16(_val, _crc) \ + (gNuCrc16Table[(((_crc) >> 8) & 0xff) ^ (_val)] ^ ((_crc) << 8)) + +/* Debug.c */ +#if defined(DEBUG_MSGS) || !defined(NDEBUG) +void Nu_DebugDumpAll(NuArchive* pArchive); +void Nu_DebugDumpThread(const NuThread* pThread); +#endif + +/* Deferred.c */ +NuError Nu_ThreadModAdd_New(NuArchive* pArchive, NuThreadID threadID, + NuThreadFormat threadFormat, NuDataSource* pDataSource, + NuThreadMod** ppThreadMod); +NuError Nu_ThreadModUpdate_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, NuThreadMod** ppThreadMod); +NuError Nu_ThreadModDelete_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuThreadID threadID, NuThreadMod** ppThreadMod); +void Nu_ThreadModFree(NuArchive* pArchive, NuThreadMod* pThreadMod); +NuError Nu_ThreadModAdd_FindByThreadID(const NuRecord* pRecord, + NuThreadID threadID, NuThreadMod** ppThreadMod); +void Nu_FreeThreadMods(NuArchive* pArchive, NuRecord* pRecord); +NuThreadMod* Nu_ThreadMod_FindByThreadIdx(const NuRecord* pRecord, + NuThreadIdx threadIdx); +NuError Nu_Flush(NuArchive* pArchive, uint32_t* pStatusFlags); + +/* Deflate.c */ +NuError Nu_CompressDeflate(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_ExpandDeflate(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc); + +/* Expand.c */ +NuError Nu_ExpandStream(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel); + +/* FileIO.c */ +void Nu_SetCurrentDateTime(NuDateTime* pDateTime); +Boolean Nu_IsOlder(const NuDateTime* pWhen1, const NuDateTime* pWhen2); +NuError Nu_OpenOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, const UNICHAR* newPathnameUNI, UNICHAR newFssep, + FILE** pFp); +NuError Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + FILE* fp, const UNICHAR* pathnameUNI); +NuError Nu_OpenInputFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + Boolean openRsrc, FILE** pFp); +NuError Nu_DeleteFile(const UNICHAR* pathnameUNI); +NuError Nu_RenameFile(const UNICHAR* fromPathUNI, const UNICHAR* toPathUNI); +NuError Nu_FTell(FILE* fp, long* pOffset); +NuError Nu_FSeek(FILE* fp, long offset, int ptrname); +NuError Nu_FRead(FILE* fp, void* buf, size_t nbyte); +NuError Nu_FWrite(FILE* fp, const void* buf, size_t nbyte); +NuError Nu_CopyFileSection(NuArchive* pArchive, FILE* dstFp, FILE* srcFp, + long length); +NuError Nu_GetFileLength(NuArchive* pArchive, FILE* fp, long* pLength); +NuError Nu_TruncateOpenFile(FILE* fp, long length); + +/* Funnel.c */ +NuError Nu_ProgressDataInit_Compress(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const UNICHAR* origPathnameUNI, const UNICHAR* pathnameUNI); +NuError Nu_ProgressDataInit_Expand(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const UNICHAR* newPathnameUNI, UNICHAR newFssep, + const UNICHAR* origPathnameUNI, NuValue convertEOL); +NuError Nu_SendInitialProgress(NuArchive* pArchive, NuProgressData* pProgress); + +NuError Nu_FunnelNew(NuArchive* pArchive, NuDataSink* pDataSink, + NuValue convertEOL, NuValue convertEOLTo, NuProgressData* pProgress, + NuFunnel** ppFunnel); +NuError Nu_FunnelFree(NuArchive* pArchive, NuFunnel* pFunnel); +/*void Nu_FunnelSetMaxOutput(NuFunnel* pFunnel, uint32_t maxBytes);*/ +NuError Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel, + const uint8_t* buffer, uint32_t count); +NuError Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel); +NuError Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw, + NuThreadFormat threadFormat, uint32_t sourceLen); +NuError Nu_ProgressDataExpandPrep(NuArchive* pArchive, NuFunnel* pFunnel, + const NuThread* pThread); +NuError Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state); +NuError Nu_FunnelSendProgressUpdate(NuArchive* pArchive, NuFunnel* pFunnel); +Boolean Nu_FunnelGetDoExpand(NuFunnel* pFunnel); + +NuError Nu_StrawNew(NuArchive* pArchive, NuDataSource* pDataSource, + NuProgressData* pProgress, NuStraw** ppStraw); +NuError Nu_StrawFree(NuArchive* pArchive, NuStraw* pStraw); +NuError Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state); +NuError Nu_StrawSendProgressUpdate(NuArchive* pArchive, NuStraw* pStraw); +NuError Nu_StrawRead(NuArchive* pArchive, NuStraw* pStraw, uint8_t* buffer, + long len); +NuError Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw); + +/* Lzc.c */ +NuError Nu_CompressLZC12(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_CompressLZC16(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_ExpandLZC(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc); + +/* Lzw.c */ +NuError Nu_CompressLZW1(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_CompressLZW2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_ExpandLZW(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, + uint16_t* pThreadCrc); + +/* MiscUtils.c */ +/*extern const char* kNufxLibName;*/ +extern NuCallback gNuGlobalErrorMessageHandler; +const char* Nu_StrError(NuError err); +void Nu_ReportError(NuArchive* pArchive, const char* file, int line, + const char* function, Boolean isDebug, NuError err, + const UNICHAR* format, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 7, 8))) + #endif + ; +#ifdef USE_DMALLOC /* want file and line numbers for calls */ +# define Nu_Malloc(archive, size) malloc(size) +# define Nu_Calloc(archive, size) calloc(1, size) +# define Nu_Realloc(archive, ptr, size) realloc(ptr, size) +# define Nu_Free(archive, ptr) (ptr != NULL ? free(ptr) : (void)0) +#else +void* Nu_Malloc(NuArchive* pArchive, size_t size); +void* Nu_Calloc(NuArchive* pArchive, size_t size); +void* Nu_Realloc(NuArchive* pArchive, void* ptr, size_t size); +void Nu_Free(NuArchive* pArchive, void* ptr); +#endif +NuResult Nu_InternalFreeCallback(NuArchive* pArchive, void* args); + +/* Record.c */ +void Nu_RecordAddThreadMod(NuRecord* pRecord, NuThreadMod* pThreadMod); +Boolean Nu_RecordIsEmpty(NuArchive* pArchive, const NuRecord* pRecord); +Boolean Nu_RecordSet_GetLoaded(const NuRecordSet* pRecordSet); +uint32_t Nu_RecordSet_GetNumRecords(const NuRecordSet* pRecordSet); +void Nu_RecordSet_SetNumRecords(NuRecordSet* pRecordSet, uint32_t val); +void Nu_RecordSet_IncNumRecords(NuRecordSet* pRecordSet); +NuRecord* Nu_RecordSet_GetListHead(const NuRecordSet* pRecordSet); +NuRecord** Nu_RecordSet_GetListHeadPtr(NuRecordSet* pRecordSet); +NuRecord* Nu_RecordSet_GetListTail(const NuRecordSet* pRecordSet); +Boolean Nu_RecordSet_IsEmpty(const NuRecordSet* pRecordSet); +NuError Nu_RecordSet_FreeAllRecords(NuArchive* pArchive, + NuRecordSet* pRecordSet); +NuError Nu_RecordSet_DeleteRecordPtr(NuArchive* pArchive, + NuRecordSet* pRecordSet, NuRecord** ppRecord); +NuError Nu_RecordSet_DeleteRecord(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord* pRecord); +NuError Nu_RecordSet_Clone(NuArchive* pArchive, NuRecordSet* pDstSet, + const NuRecordSet* pSrcSet); +NuError Nu_RecordSet_MoveAllRecords(NuArchive* pArchive, NuRecordSet* pDstSet, + NuRecordSet* pSrcSet); +NuError Nu_RecordSet_FindByIdx(const NuRecordSet* pRecordSet, NuRecordIdx rec, + NuRecord** ppRecord); +NuError Nu_RecordSet_FindByThreadIdx(NuRecordSet* pRecordSet, + NuThreadIdx threadIdx, NuRecord** ppRecord, NuThread** ppThread); +NuError Nu_RecordSet_ReplaceRecord(NuArchive* pArchive, NuRecordSet* pBadSet, + NuRecord* pBadRecord, NuRecordSet* pGoodSet, NuRecord** ppGoodRecord); +Boolean Nu_ShouldIgnoreBadCRC(NuArchive* pArchive, const NuRecord* pRecord, + NuError err); +NuError Nu_WriteRecordHeader(NuArchive* pArchive, NuRecord* pRecord, FILE* fp); +NuError Nu_GetTOCIfNeeded(NuArchive* pArchive); +NuError Nu_StreamContents(NuArchive* pArchive, NuCallback contentFunc); +NuError Nu_StreamExtract(NuArchive* pArchive); +NuError Nu_StreamTest(NuArchive* pArchive); +NuError Nu_Contents(NuArchive* pArchive, NuCallback contentFunc); +NuError Nu_Extract(NuArchive* pArchive); +NuError Nu_ExtractRecord(NuArchive* pArchive, NuRecordIdx recIdx); +NuError Nu_Test(NuArchive* pArchive); +NuError Nu_TestRecord(NuArchive* pArchive, NuRecordIdx recIdx); +NuError Nu_GetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord); +NuError Nu_GetRecordIdxByName(NuArchive* pArchive, const char* nameMOR, + NuRecordIdx* pRecordIdx); +NuError Nu_GetRecordIdxByPosition(NuArchive* pArchive, uint32_t position, + NuRecordIdx* pRecordIdx); +NuError Nu_FindRecordForWriteByIdx(NuArchive* pArchive, NuRecordIdx recIdx, + NuRecord** ppFoundRecord); +NuError Nu_AddFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + const NuFileDetails* pFileDetails, Boolean fromRsrcFork, + NuRecordIdx* pRecordIdx); +NuError Nu_AddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx, NuRecord** ppRecord); +NuError Nu_Rename(NuArchive* pArchive, NuRecordIdx recIdx, + const char* pathnameMOR, char fssepMOR); +NuError Nu_SetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr); +NuError Nu_Delete(NuArchive* pArchive); +NuError Nu_DeleteRecord(NuArchive* pArchive, NuRecordIdx rec); + +/* SourceSink.c */ +NuError Nu_DataSourceFile_New(NuThreadFormat threadFormat, + uint32_t otherLen, const UNICHAR* pathnameUNI, Boolean isFromRsrcFork, + NuDataSource** ppDataSource); +NuError Nu_DataSourceFP_New(NuThreadFormat threadFormat, + uint32_t otherLen, FILE* fp, long offset, long length, + NuCallback fcloseFunc, NuDataSource** ppDataSource); +NuError Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, + uint32_t otherLen, const uint8_t* buffer, long offset, long length, + NuCallback freeFunc, NuDataSource** ppDataSource); +NuDataSource* Nu_DataSourceCopy(NuDataSource* pDataSource); +NuError Nu_DataSourceFree(NuDataSource* pDataSource); +NuDataSourceType Nu_DataSourceGetType(const NuDataSource* pDataSource); +NuThreadFormat Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource); +uint32_t Nu_DataSourceGetDataLen(const NuDataSource* pDataSource); +uint32_t Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource); +void Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen); +uint16_t Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource); +void Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, uint16_t crc); +NuError Nu_DataSourcePrepareInput(NuArchive* pArchive, + NuDataSource* pDataSource); +void Nu_DataSourceUnPrepareInput(NuArchive* pArchive, + NuDataSource* pDataSource); +const char* Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource); +NuError Nu_DataSourceGetBlock(NuDataSource* pDataSource, uint8_t* buf, + uint32_t len); +NuError Nu_DataSourceRewind(NuDataSource* pDataSource); +NuError Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, + const UNICHAR* pathnameUNI, UNICHAR fssep, NuDataSink** ppDataSink); +NuError Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp, + NuDataSink** ppDataSink); +NuError Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL, + uint8_t* buffer, uint32_t bufLen, NuDataSink** ppDataSink); +NuError Nu_DataSinkVoid_New(Boolean doExpand, NuValue convertEOL, + NuDataSink** ppDataSink); +NuError Nu_DataSinkFree(NuDataSink* pDataSink); +NuDataSinkType Nu_DataSinkGetType(const NuDataSink* pDataSink); +Boolean Nu_DataSinkGetDoExpand(const NuDataSink* pDataSink); +NuValue Nu_DataSinkGetConvertEOL(const NuDataSink* pDataSink); +uint32_t Nu_DataSinkGetOutCount(const NuDataSink* pDataSink); +const char* Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink); +UNICHAR Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink); +FILE* Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink); +void Nu_DataSinkFile_SetFP(NuDataSink* pDataSink, FILE* fp); +void Nu_DataSinkFile_Close(NuDataSink* pDataSink); +NuError Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uint8_t* buf, + uint32_t len); +NuError Nu_DataSinkGetError(NuDataSink* pDataSink); + +/* Squeeze.c */ +NuError Nu_CompressHuffmanSQ(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc); +NuError Nu_ExpandHuffmanSQ(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc); + +/* Thread.c */ +NuThread* Nu_GetThread(const NuRecord* pRecord, int idx); +void Nu_StripHiIfAllSet(char* str); +Boolean Nu_IsPresizedThreadID(NuThreadID threadID); +Boolean Nu_IsCompressibleThreadID(NuThreadID threadID); +Boolean Nu_ThreadHasCRC(uint16_t recordVersion, NuThreadID threadID); +NuError Nu_FindThreadByIdx(const NuRecord* pRecord, NuThreadIdx thread, + NuThread** ppThread); +NuError Nu_FindThreadByID(const NuRecord* pRecord, NuThreadID threadID, + NuThread** ppThread); +void Nu_CopyThreadContents(NuThread* pDstThread, const NuThread* pSrcThread); +NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, + uint16_t* pCrc); +NuError Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp, + uint16_t* pCrc); +NuError Nu_ComputeThreadData(NuArchive* pArchive, NuRecord* pRecord); +NuError Nu_ScanThreads(NuArchive* pArchive, NuRecord* pRecord,long numThreads); +NuError Nu_ExtractThreadBulk(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread); +NuError Nu_SkipThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread); +NuError Nu_ExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink); +NuError Nu_OkayToAddThread(NuArchive* pArchive, const NuRecord* pRecord, + NuThreadID threadID); +NuError Nu_AddThread(NuArchive* pArchive, NuRecordIdx rec, NuThreadID threadID, + NuDataSource* pDataSource, NuThreadIdx* pThreadIdx); +NuError Nu_UpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, int32_t* pMaxLen); +NuError Nu_DeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx); + +/* Value.c */ +NuError Nu_GetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue); +NuError Nu_SetValue(NuArchive* pArchive, NuValueID ident, NuValue value); +NuError Nu_GetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr); +NuThreadFormat Nu_ConvertCompressValToFormat(NuArchive* pArchive, + NuValue compValue); + +/* Version.c */ +NuError Nu_GetVersion(int32_t* pMajorVersion, int32_t* pMinorVersion, + int32_t* pBugVersion, const char** ppBuildDate, const char** ppBuildFlags); + +#endif /*NUFXLIB_NUFXLIBPRIV_H*/ diff --git a/nufxlib/README.txt b/nufxlib/README.txt new file mode 100644 index 0000000..3e246ee --- /dev/null +++ b/nufxlib/README.txt @@ -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. + diff --git a/nufxlib/Record.c b/nufxlib/Record.c new file mode 100644 index 0000000..6c85684 --- /dev/null +++ b/nufxlib/Record.c @@ -0,0 +1,2848 @@ +/* + * 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. + * + * Record-level operations. + */ +#include "NufxLibPriv.h" + + +/* + * Local constants. + */ +static const uint8_t kNufxID[kNufxIDLen] = { 0x4e, 0xf5, 0x46, 0xd8 }; + + +/* + * =========================================================================== + * Simple NuRecord stuff + * =========================================================================== + */ + +/* + * Initialize the contents of a NuRecord. The goal here is to init the + * things that a Nu_FreeRecordContents call will check, so that we don't + * end up trying to free garbage. No need to memset() the whole thing. + */ +static NuError Nu_InitRecordContents(NuArchive* pArchive, NuRecord* pRecord) +{ + Assert(pRecord != NULL); + + DebugFill(pRecord, sizeof(*pRecord)); + + pRecord->recOptionList = NULL; + pRecord->extraBytes = NULL; + pRecord->recFilenameMOR = NULL; + pRecord->threadFilenameMOR = NULL; + pRecord->newFilenameMOR = NULL; + pRecord->pThreads = NULL; + pRecord->pNext = NULL; + pRecord->pThreadMods = NULL; + pRecord->dirtyHeader = false; + pRecord->dropRecFilename = false; + pRecord->isBadMac = false; + + return kNuErrNone; +} + +/* + * Allocate and initialize a new NuRecord struct. + */ +static NuError Nu_RecordNew(NuArchive* pArchive, NuRecord** ppRecord) +{ + Assert(ppRecord != NULL); + + *ppRecord = Nu_Malloc(pArchive, sizeof(**ppRecord)); + if (*ppRecord == NULL) + return kNuErrMalloc; + + return Nu_InitRecordContents(pArchive, *ppRecord); +} + +/* + * Free anything allocated within a record. Doesn't try to free the record + * itself. + */ +static NuError Nu_FreeRecordContents(NuArchive* pArchive, NuRecord* pRecord) +{ + Assert(pRecord != NULL); + + Nu_Free(pArchive, pRecord->recOptionList); + Nu_Free(pArchive, pRecord->extraBytes); + Nu_Free(pArchive, pRecord->recFilenameMOR); + Nu_Free(pArchive, pRecord->threadFilenameMOR); + Nu_Free(pArchive, pRecord->newFilenameMOR); + Nu_Free(pArchive, pRecord->pThreads); + /* don't Free(pRecord->pNext)! */ + Nu_FreeThreadMods(pArchive, pRecord); + + (void) Nu_InitRecordContents(pArchive, pRecord); /* mark as freed */ + + return kNuErrNone; +} + +/* + * Free up a NuRecord struct. + */ +static NuError Nu_RecordFree(NuArchive* pArchive, NuRecord* pRecord) +{ + if (pRecord == NULL) + return kNuErrNone; + + (void) Nu_FreeRecordContents(pArchive, pRecord); + Nu_Free(pArchive, pRecord); + + return kNuErrNone; +} + +/* + * Copy a field comprised of a buffer and a length from one structure to + * another. It is assumed that the length value has already been copied. + */ +static NuError CopySizedField(NuArchive* pArchive, void* vppDst, + const void* vpSrc, uint32_t len) +{ + NuError err = kNuErrNone; + uint8_t** ppDst = vppDst; + const uint8_t* pSrc = vpSrc; + + Assert(ppDst != NULL); + + if (len) { + Assert(pSrc != NULL); + *ppDst = Nu_Malloc(pArchive, len); + BailAlloc(*ppDst); + memcpy(*ppDst, pSrc, len); + } else { + Assert(pSrc == NULL); + *ppDst = NULL; + } + +bail: + return err; +} + +/* + * Make a copy of a record. + */ +static NuError Nu_RecordCopy(NuArchive* pArchive, NuRecord** ppDst, + const NuRecord* pSrc) +{ + NuError err; + NuRecord* pDst; + + err = Nu_RecordNew(pArchive, ppDst); + BailError(err); + + /* copy all the static fields, then copy or blank the "hairy" parts */ + pDst = *ppDst; + memcpy(pDst, pSrc, sizeof(*pSrc)); + CopySizedField(pArchive, &pDst->recOptionList, pSrc->recOptionList, + pSrc->recOptionSize); + CopySizedField(pArchive, &pDst->extraBytes, pSrc->extraBytes, + pSrc->extraCount); + CopySizedField(pArchive, &pDst->recFilenameMOR, pSrc->recFilenameMOR, + pSrc->recFilenameLength == 0 ? 0 : pSrc->recFilenameLength+1); + CopySizedField(pArchive, &pDst->threadFilenameMOR, pSrc->threadFilenameMOR, + pSrc->threadFilenameMOR == NULL ? 0 : strlen(pSrc->threadFilenameMOR) +1); + CopySizedField(pArchive, &pDst->newFilenameMOR, pSrc->newFilenameMOR, + pSrc->newFilenameMOR == NULL ? 0 : strlen(pSrc->newFilenameMOR) +1); + CopySizedField(pArchive, &pDst->pThreads, pSrc->pThreads, + pSrc->recTotalThreads * sizeof(*pDst->pThreads)); + + /* now figure out what the filename is supposed to point at */ + if (pSrc->filenameMOR == pSrc->threadFilenameMOR) + pDst->filenameMOR = pDst->threadFilenameMOR; + else if (pSrc->filenameMOR == pSrc->recFilenameMOR) + pDst->filenameMOR = pDst->recFilenameMOR; + else if (pSrc->filenameMOR == pSrc->newFilenameMOR) + pDst->filenameMOR = pDst->newFilenameMOR; + else + pDst->filenameMOR = pSrc->filenameMOR; /* probably static kDefault value */ + + pDst->pNext = NULL; + + /* these only hold for copy from orig... may need to remove */ + Assert(pSrc->pThreadMods == NULL); + Assert(!pSrc->dirtyHeader); + +bail: + return err; +} + + +/* + * Add a ThreadMod to the list in the NuRecord. + * + * In general, the order is not significant. However, if we're adding + * a bunch of "add" threadMods for control threads to a record, their + * order might be important. So, we want to add the threadMod to the + * end of the list. + * + * I'm expecting these lists to be short, so walking down them is + * acceptable. We could do simple optimizations, like only preserving + * ordering for "add" threadMods, but even that seems silly. + */ +void Nu_RecordAddThreadMod(NuRecord* pRecord, NuThreadMod* pThreadMod) +{ + NuThreadMod* pScanThreadMod; + + Assert(pRecord != NULL); + Assert(pThreadMod != NULL); + + if (pRecord->pThreadMods == NULL) { + pRecord->pThreadMods = pThreadMod; + } else { + pScanThreadMod = pRecord->pThreadMods; + while (pScanThreadMod->pNext != NULL) + pScanThreadMod = pScanThreadMod->pNext; + + pScanThreadMod->pNext = pThreadMod; + } + + pThreadMod->pNext = NULL; +} + + +/* + * Decide if a record is empty. An empty record is one that will have no + * threads after all adds and deletes are processed. + * + * You can't delete something you just added or has been updated, and you + * can't update something that has been deleted, so any "add" or "update" + * items indicate that the thread isn't empty. + * + * You can't delete a thread more than once, or delete a thread that + * doesn't exist, so all we need to do is count up the number of current + * threads, subtract the number of deletes, and return "true" if the net + * result is zero. + */ +Boolean Nu_RecordIsEmpty(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThreadMod* pThreadMod; + int numThreads; + + Assert(pRecord != NULL); + + numThreads = pRecord->recTotalThreads; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + case kNuThreadModUpdate: + return false; + case kNuThreadModDelete: + numThreads--; + break; + case kNuThreadModUnknown: + default: + Assert(0); + return false; + } + + pThreadMod = pThreadMod->pNext; + } + + if (numThreads > 0) + return false; + else if (numThreads == 0) + return true; + else { + Assert(0); + Nu_ReportError(NU_BLOB, kNuErrInternal, + "Thread counting failed (%d)", numThreads); + return false; + } +} + + +/* + * =========================================================================== + * NuRecordSet functions + * =========================================================================== + */ + +/* + * Trivial getters and setters + */ + +Boolean Nu_RecordSet_GetLoaded(const NuRecordSet* pRecordSet) +{ + Assert(pRecordSet != NULL); + return pRecordSet->loaded; +} + +void Nu_RecordSet_SetLoaded(NuRecordSet* pRecordSet, Boolean val) +{ + pRecordSet->loaded = val; +} + +uint32_t Nu_RecordSet_GetNumRecords(const NuRecordSet* pRecordSet) +{ + return pRecordSet->numRecords; +} + +void Nu_RecordSet_SetNumRecords(NuRecordSet* pRecordSet, uint32_t val) +{ + pRecordSet->numRecords = val; +} + +void Nu_RecordSet_IncNumRecords(NuRecordSet* pRecordSet) +{ + pRecordSet->numRecords++; +} + +NuRecord* Nu_RecordSet_GetListHead(const NuRecordSet* pRecordSet) +{ + return pRecordSet->nuRecordHead; +} + +NuRecord** Nu_RecordSet_GetListHeadPtr(NuRecordSet* pRecordSet) +{ + return &pRecordSet->nuRecordHead; +} + +NuRecord* Nu_RecordSet_GetListTail(const NuRecordSet* pRecordSet) +{ + return pRecordSet->nuRecordTail; +} + + +/* + * Returns "true" if the record set has no records or hasn't ever been + * used. + */ +Boolean Nu_RecordSet_IsEmpty(const NuRecordSet* pRecordSet) +{ + if (!pRecordSet->loaded || pRecordSet->numRecords == 0) + return true; + + return false; +} + +/* + * Free the list of records, and reset the record sets to initial state. + */ +NuError Nu_RecordSet_FreeAllRecords(NuArchive* pArchive, + NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuRecord* pNextRecord; + + if (!pRecordSet->loaded) { + Assert(pRecordSet->nuRecordHead == NULL); + Assert(pRecordSet->nuRecordTail == NULL); + Assert(pRecordSet->numRecords == 0); + return kNuErrNone; + } + + DBUG(("+++ FreeAllRecords\n")); + pRecord = pRecordSet->nuRecordHead; + while (pRecord != NULL) { + pNextRecord = pRecord->pNext; + + err = Nu_RecordFree(pArchive, pRecord); + BailError(err); /* don't really expect this to fail */ + + pRecord = pNextRecord; + } + + pRecordSet->nuRecordHead = pRecordSet->nuRecordTail = NULL; + pRecordSet->numRecords = 0; + pRecordSet->loaded = false; + +bail: + return err; +} + + +/* + * Add a new record to the end of the list. + */ +static NuError Nu_RecordSet_AddRecord(NuRecordSet* pRecordSet, + NuRecord* pRecord) +{ + Assert(pRecordSet != NULL); + Assert(pRecord != NULL); + + /* if one is NULL, both must be NULL */ + Assert(pRecordSet->nuRecordHead == NULL || pRecordSet->nuRecordTail != NULL); + Assert(pRecordSet->nuRecordTail == NULL || pRecordSet->nuRecordHead != NULL); + + if (pRecordSet->nuRecordHead == NULL) { + /* empty list */ + pRecordSet->nuRecordHead = pRecordSet->nuRecordTail = pRecord; + pRecordSet->loaded = true; + Assert(!pRecordSet->numRecords); + } else { + pRecord->pNext = NULL; + pRecordSet->nuRecordTail->pNext = pRecord; + pRecordSet->nuRecordTail = pRecord; + } + + pRecordSet->numRecords++; + + return kNuErrNone; +} + + +/* + * Delete a record from the record set. Pass in a pointer to the pointer + * to the record (usually either the head pointer or another record's + * "pNext" pointer). + * + * (Should have a "heavy assert" mode where we verify that "ppRecord" + * actually has something to do with pRecordSet.) + */ +NuError Nu_RecordSet_DeleteRecordPtr(NuArchive* pArchive, + NuRecordSet* pRecordSet, NuRecord** ppRecord) +{ + NuError err; + NuRecord* pRecord; + + Assert(pRecordSet != NULL); + Assert(ppRecord != NULL); + Assert(*ppRecord != NULL); + + /* save a copy of the record we're freeing */ + pRecord = *ppRecord; + + /* update the pHead or pNext pointer */ + *ppRecord = (*ppRecord)->pNext; + pRecordSet->numRecords--; + + /* if we're deleting the tail, we have to find the "new" last entry */ + if (pRecord == pRecordSet->nuRecordTail) { + if (pRecordSet->nuRecordHead == NULL) { + /* this was the last entry; we're done */ + pRecordSet->nuRecordTail = NULL; + } else { + /* walk through the list... delete bottom-up will be slow! */ + pRecordSet->nuRecordTail = pRecordSet->nuRecordHead; + while (pRecordSet->nuRecordTail->pNext != NULL) + pRecordSet->nuRecordTail = pRecordSet->nuRecordTail->pNext; + } + } + + if (pRecordSet->numRecords) + Assert(pRecordSet->nuRecordHead!=NULL && pRecordSet->nuRecordTail!=NULL); + else + Assert(pRecordSet->nuRecordHead==NULL && pRecordSet->nuRecordTail==NULL); + + err = Nu_RecordFree(pArchive, pRecord); + return err; +} + +/* + * Delete a record from the record set. + */ +NuError Nu_RecordSet_DeleteRecord(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord* pRecord) +{ + NuError err; + NuRecord** ppRecord; + + ppRecord = Nu_RecordSet_GetListHeadPtr(pRecordSet); + Assert(ppRecord != NULL); + Assert(*ppRecord != NULL); + + /* look for the record, so we can update his neighbors */ + /* (this also ensures that the record really is in the set we think it is)*/ + while (*ppRecord) { + if (*ppRecord == pRecord) { + err = Nu_RecordSet_DeleteRecordPtr(pArchive, pRecordSet, ppRecord); + BailError(err); + goto bail; + } + + ppRecord = &((*ppRecord)->pNext); + } + + DBUG(("--- Nu_RecordSet_DeleteRecord failed\n")); + err = kNuErrNotFound; + +bail: + return err; +} + +/* + * Make a clone of a record set. This is used to create the "copy" record + * set out of the "orig" set. + */ +NuError Nu_RecordSet_Clone(NuArchive* pArchive, NuRecordSet* pDstSet, + const NuRecordSet* pSrcSet) +{ + NuError err = kNuErrNone; + const NuRecord* pSrcRecord; + NuRecord* pDstRecord; + + Assert(pDstSet != NULL); + Assert(pSrcSet != NULL); + Assert(Nu_RecordSet_GetLoaded(pDstSet) == false); + Assert(Nu_RecordSet_GetLoaded(pSrcSet) == true); + + DBUG(("--- Cloning record set\n")); + + Nu_RecordSet_SetLoaded(pDstSet, true); + + /* copy each record over */ + pSrcRecord = pSrcSet->nuRecordHead; + while (pSrcRecord != NULL) { + err = Nu_RecordCopy(pArchive, &pDstRecord, pSrcRecord); + BailError(err); + err = Nu_RecordSet_AddRecord(pDstSet, pDstRecord); + BailError(err); + + pSrcRecord = pSrcRecord->pNext; + } + + Assert(pDstSet->numRecords == pSrcSet->numRecords); + +bail: + if (err != kNuErrNone) { + Nu_RecordSet_FreeAllRecords(pArchive, pDstSet); + } + return err; +} + +/* + * Move all of the records from one record set to another. The records + * from "pSrcSet" are appended to "pDstSet". + * + * On completion, "pSrcSet" will be empty and "unloaded". + */ +NuError Nu_RecordSet_MoveAllRecords(NuArchive* pArchive, NuRecordSet* pDstSet, + NuRecordSet* pSrcSet) +{ + NuError err = kNuErrNone; + + Assert(pDstSet != NULL); + Assert(pSrcSet != NULL); + + /* move records over */ + if (Nu_RecordSet_GetNumRecords(pSrcSet)) { + Assert(pSrcSet->loaded); + Assert(pSrcSet->nuRecordHead != NULL); + Assert(pSrcSet->nuRecordTail != NULL); + if (pDstSet->nuRecordHead == NULL) { + /* empty dst list */ + Assert(pDstSet->nuRecordTail == NULL); + pDstSet->nuRecordHead = pSrcSet->nuRecordHead; + pDstSet->nuRecordTail = pSrcSet->nuRecordTail; + pDstSet->numRecords = pSrcSet->numRecords; + pDstSet->loaded = true; + } else { + /* append to dst list */ + Assert(pDstSet->loaded); + Assert(pDstSet->nuRecordTail != NULL); + pDstSet->nuRecordTail->pNext = pSrcSet->nuRecordHead; + pDstSet->nuRecordTail = pSrcSet->nuRecordTail; + pDstSet->numRecords += pSrcSet->numRecords; + } + } else { + /* no records in src set */ + Assert(pSrcSet->nuRecordHead == NULL); + Assert(pSrcSet->nuRecordTail == NULL); + + if (pSrcSet->loaded) + pDstSet->loaded = true; + } + + /* nuke all pointers in original list */ + pSrcSet->nuRecordHead = pSrcSet->nuRecordTail = NULL; + pSrcSet->numRecords = 0; + pSrcSet->loaded = false; + + return err; +} + + +/* + * Find a record in the list by index. + */ +NuError Nu_RecordSet_FindByIdx(const NuRecordSet* pRecordSet, + NuRecordIdx recIdx, NuRecord** ppRecord) +{ + NuRecord* pRecord; + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != NULL) { + if (pRecord->recordIdx == recIdx) { + *ppRecord = pRecord; + return kNuErrNone; + } + + pRecord = pRecord->pNext; + } + + return kNuErrRecIdxNotFound; +} + + +/* + * Search for a specific thread in all records in the specified record set. + */ +NuError Nu_RecordSet_FindByThreadIdx(NuRecordSet* pRecordSet, + NuThreadIdx threadIdx, NuRecord** ppRecord, NuThread** ppThread) +{ + NuError err = kNuErrThreadIdxNotFound; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + while (pRecord != NULL) { + err = Nu_FindThreadByIdx(pRecord, threadIdx, ppThread); + if (err == kNuErrNone) { + *ppRecord = pRecord; + break; + } + pRecord = pRecord->pNext; + } + + Assert(err != kNuErrNone || (*ppRecord != NULL && *ppThread != NULL)); + return err; +} + + +/* + * Compare two record filenames. This comes into play when looking for + * conflicts while adding records to an archive. + * + * Interesting issues: + * - some filesystems are case-sensitive, some aren't + * - the fssep may be different ('/', ':') for otherwise equivalent names + * - system-dependent conversions could resolve two different names to + * the same thing + * + * Some of these are out of our control. For now, I'm just doing a + * case-insensitive comparison, since the most interesting case for us is + * when the person is adding a data fork and a resource fork from the + * same file during the same operation. + * + * [ Could run both names through the pathname conversion callback first? + * Might be expensive. ] + * + * Returns an integer greater than, equal to, or less than 0, if the + * string pointed to by name1 is greater than, equal to, or less than + * the string pointed to by s2, respectively (i.e. same as strcmp). + */ +static int Nu_CompareRecordNames(const char* name1MOR, const char* name2MOR) +{ +#ifdef NU_CASE_SENSITIVE + return strcmp(name1MOR, name2MOR); +#else + return strcasecmp(name1MOR, name2MOR); +#endif +} + + +/* + * Find a record in the list by storageName. + */ +static NuError Nu_RecordSet_FindByName(const NuRecordSet* pRecordSet, + const char* nameMOR, NuRecord** ppRecord) +{ + NuRecord* pRecord; + + Assert(pRecordSet != NULL); + Assert(pRecordSet->loaded); + Assert(nameMOR != NULL); + Assert(ppRecord != NULL); + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != NULL) { + if (Nu_CompareRecordNames(pRecord->filenameMOR, nameMOR) == 0) { + *ppRecord = pRecord; + return kNuErrNone; + } + + pRecord = pRecord->pNext; + } + + return kNuErrRecNameNotFound; +} + +/* + * Find a record in the list by storageName, starting from the end and + * searching backwards. + * + * Since we don't actually have a "prev" pointer in the record, we end + * up scanning the entire list and keeping the last match. If this + * causes a notable reduction in efficiency we'll have to fix this. + */ +static NuError Nu_RecordSet_ReverseFindByName(const NuRecordSet* pRecordSet, + const char* nameMOR, NuRecord** ppRecord) +{ + NuRecord* pRecord; + NuRecord* pFoundRecord = NULL; + + Assert(pRecordSet != NULL); + Assert(pRecordSet->loaded); + Assert(nameMOR != NULL); + Assert(ppRecord != NULL); + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != NULL) { + if (Nu_CompareRecordNames(pRecord->filenameMOR, nameMOR) == 0) + pFoundRecord = pRecord; + + pRecord = pRecord->pNext; + } + + if (pFoundRecord != NULL) { + *ppRecord = pFoundRecord; + return kNuErrNone; + } + return kNuErrRecNameNotFound; +} + + +/* + * We have a copy of the record in the "copy" set, but we've decided + * (perhaps because the user elected to Skip a failed add) that we'd + * rather have the original. + * + * Delete the record from the "copy" set, clone the "orig" record, and + * insert the "orig" record into the same spot in the "copy" set. + * + * "ppNewRecord" will get a pointer to the newly-created clone. + */ +NuError Nu_RecordSet_ReplaceRecord(NuArchive* pArchive, NuRecordSet* pBadSet, + NuRecord* pBadRecord, NuRecordSet* pGoodSet, NuRecord** ppNewRecord) +{ + NuError err; + NuRecord* pGoodRecord; + NuRecord* pSiblingRecord; + NuRecord* pNewRecord = NULL; + + Assert(pArchive != NULL); + Assert(pBadSet != NULL); + Assert(pBadRecord != NULL); + Assert(pGoodSet != NULL); + Assert(ppNewRecord != NULL); + + /* + * Find a record in "pGoodSet" that has the same record index as + * the "bad" record. + */ + err = Nu_RecordSet_FindByIdx(pGoodSet, pBadRecord->recordIdx, + &pGoodRecord); + BailError(err); + + /* + * Clone the original. + */ + err = Nu_RecordCopy(pArchive, &pNewRecord, pGoodRecord); + BailError(err); + + /* + * Insert the new one into the "bad" record set, in the exact same + * position. + */ + pNewRecord->pNext = pBadRecord->pNext; + if (pBadSet->nuRecordTail == pBadRecord) + pBadSet->nuRecordTail = pNewRecord; + if (pBadSet->nuRecordHead == pBadRecord) + pBadSet->nuRecordHead = pNewRecord; + else { + /* find the record that points to pBadRecord */ + pSiblingRecord = pBadSet->nuRecordHead; + while (pSiblingRecord->pNext != pBadRecord && pSiblingRecord != NULL) + pSiblingRecord = pSiblingRecord->pNext; + + if (pSiblingRecord == NULL) { + /* looks like "pBadRecord" wasn't part of "pBadSet" after all */ + Assert(0); + err = kNuErrInternal; + goto bail; + } + + pSiblingRecord->pNext = pNewRecord; + } + + err = Nu_RecordFree(pArchive, pBadRecord); + BailError(err); + + *ppNewRecord = pNewRecord; + pNewRecord = NULL; /* don't free */ + +bail: + if (pNewRecord != NULL) + Nu_RecordFree(pArchive, pNewRecord); + return err; +} + + +/* + * =========================================================================== + * Assorted utility functions + * =========================================================================== + */ + +/* + * Ask the user if it's okay to ignore a bad CRC. If we can't ask the + * user, return "false". + */ +Boolean Nu_ShouldIgnoreBadCRC(NuArchive* pArchive, const NuRecord* pRecord, + NuError err) +{ + NuErrorStatus errorStatus; + NuResult result; + Boolean retval = false; + UNICHAR* pathnameUNI = NULL; + + Assert(pArchive->valIgnoreCRC == false); + + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.operation = kNuOpTest; /* mostly accurate */ + errorStatus.err = err; + errorStatus.sysErr = 0; + errorStatus.message = NULL; + errorStatus.pRecord = pRecord; + errorStatus.pathnameUNI = NULL; + errorStatus.origPathname = NULL; + errorStatus.filenameSeparator = 0; + if (pRecord != NULL) { + pathnameUNI = Nu_CopyMORToUNI(pRecord->filenameMOR); + errorStatus.pathnameUNI = pathnameUNI; + errorStatus.filenameSeparator = + NuGetSepFromSysInfo(pRecord->recFileSysInfo); + } + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = false; + errorStatus.canIgnore = true; + errorStatus.canSkip = false; + errorStatus.canRename = false; + errorStatus.canOverwrite = false; + + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + goto bail; + case kNuIgnore: + retval = true; + goto bail; + case kNuSkip: + case kNuOverwrite: + case kNuRetry: + case kNuRename: + default: + Nu_ReportError(NU_BLOB, kNuErrSyntax, + "Wasn't expecting result %d here", result); + break; + } + } + +bail: + Nu_Free(pArchive, pathnameUNI); + return retval; +} + + +/* + * Read the next NuFX record from the current offset in the archive stream. + * This includes the record header and the thread header blocks. + * + * Pass in a NuRecord structure that will hold the data we read. + */ +static NuError Nu_ReadRecordHeader(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + uint16_t crc; + FILE* fp; + int bytesRead; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pRecord->pThreads == NULL); + Assert(pRecord->pNext == NULL); + + fp = pArchive->archiveFp; + + pRecord->recordIdx = Nu_GetNextRecordIdx(pArchive); + + /* points to whichever filename storage we like best */ + pRecord->filenameMOR = NULL; + pRecord->fileOffset = pArchive->currentOffset; + + (void) Nu_ReadBytes(pArchive, fp, pRecord->recNufxID, kNufxIDLen); + if (memcmp(kNufxID, pRecord->recNufxID, kNufxIDLen) != 0) { + err = kNuErrRecHdrNotFound; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't find start of next record"); + goto bail; + } + + /* + * Read the static fields. + */ + crc = 0; + pRecord->recHeaderCRC = Nu_ReadTwo(pArchive, fp); + pRecord->recAttribCount = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recVersionNumber = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recTotalThreads = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recFileSysID = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recFileSysInfo = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recAccess = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recFileType = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recExtraType = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recStorageType = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recCreateWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pRecord->recModWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pRecord->recArchiveWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + bytesRead = 56; /* 4-byte 'NuFX' plus the above */ + + /* + * Do some sanity checks before we continue. + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading record header"); + goto bail; + } + if (pRecord->recAttribCount > kNuReasonableAttribCount) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Attrib count is huge (%u)", + pRecord->recAttribCount); + goto bail; + } + if (pRecord->recVersionNumber > kNuMaxRecordVersion) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Unrecognized record version number (%u)", + pRecord->recVersionNumber); + goto bail; + } + if (pRecord->recTotalThreads > kNuReasonableTotalThreads) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Unreasonable number of threads (%u)", + pRecord->recTotalThreads); + goto bail; + } + + /* + * Read the option list, if present. + */ + if (pRecord->recVersionNumber > 0) { + pRecord->recOptionSize = Nu_ReadTwoC(pArchive, fp, &crc); + bytesRead += 2; + + /* + * It appears GS/ShrinkIt is creating bad option lists, claiming + * 36 bytes of data when there's only room for 18. Since we don't + * really pay attention to the option list + */ + if (pRecord->recOptionSize + bytesRead > pRecord->recAttribCount -2) { + DBUG(("--- truncating option list from %d to %d\n", + pRecord->recOptionSize, + pRecord->recAttribCount -2 - bytesRead)); + if (pRecord->recAttribCount -2 > bytesRead) + pRecord->recOptionSize = pRecord->recAttribCount -2 - bytesRead; + else + pRecord->recOptionSize = 0; + } + + /* this is the older test, which rejected funky archives */ + if (pRecord->recOptionSize + bytesRead > pRecord->recAttribCount -2) { + /* option size exceeds the total attribute area */ + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, kNuErrBadRecord, + "Option size (%u) exceeds attribs (%u,%u-2)", + pRecord->recOptionSize, bytesRead, + pRecord->recAttribCount); + goto bail; + } + + if (pRecord->recOptionSize) { + pRecord->recOptionList = Nu_Malloc(pArchive,pRecord->recOptionSize); + BailAlloc(pRecord->recOptionList); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->recOptionList, + pRecord->recOptionSize, &crc); + bytesRead += pRecord->recOptionSize; + } + } else { + pRecord->recOptionSize = 0; + pRecord->recOptionList = NULL; + } + + /* last two bytes are the filename len; all else is "extra" */ + pRecord->extraCount = (pRecord->recAttribCount -2) - bytesRead; + Assert(pRecord->extraCount >= 0); + + /* + * Some programs (for example, NuLib) may leave extra junk in here. This + * is allowed by the archive spec. We may want to preserve it, so we + * allocate space for it and read it if it exists. + */ + if (pRecord->extraCount) { + pRecord->extraBytes = Nu_Malloc(pArchive, pRecord->extraCount); + BailAlloc(pRecord->extraBytes); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->extraBytes, + pRecord->extraCount, &crc); + bytesRead += pRecord->extraCount; + } + + /* + * Read the in-record filename if one exists (likely in v0 records only). + */ + pRecord->recFilenameLength = Nu_ReadTwoC(pArchive, fp, &crc); + bytesRead += 2; + if (pRecord->recFilenameLength > kNuReasonableFilenameLen) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, kNuErrBadRecord, "Filename length is huge (%u)", + pRecord->recFilenameLength); + goto bail; + } + if (pRecord->recFilenameLength) { + pRecord->recFilenameMOR = + Nu_Malloc(pArchive, pRecord->recFilenameLength +1); + BailAlloc(pRecord->recFilenameMOR); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->recFilenameMOR, + pRecord->recFilenameLength, &crc); + pRecord->recFilenameMOR[pRecord->recFilenameLength] = '\0'; + + bytesRead += pRecord->recFilenameLength; + + Nu_StripHiIfAllSet(pRecord->recFilenameMOR); + + /* use the in-header one */ + pRecord->filenameMOR = pRecord->recFilenameMOR; + } + + /* + * Read the threads records. The data is included in the record header + * CRC, so we have to pass that in too. + */ + pRecord->fakeThreads = 0; + err = Nu_ReadThreadHeaders(pArchive, pRecord, &crc); + BailError(err); + + /* + * After all is said and done, did we read the file without errors, + * and does the CRC match? + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading late record header"); + goto bail; + } + if (!pArchive->valIgnoreCRC && crc != pRecord->recHeaderCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadRHCRC)) { + err = kNuErrBadRHCRC; + Nu_ReportError(NU_BLOB, err, "Stored RH CRC=0x%04x, calc=0x%04x", + pRecord->recHeaderCRC, crc); + Nu_ReportError(NU_BLOB_DEBUG, kNuErrNone, + "--- Problematic record is id=%u", pRecord->recordIdx); + goto bail; + } + } + + /* + * Init or compute misc record fields. + */ + /* adjust "currentOffset" for the entire record header */ + pArchive->currentOffset += bytesRead; + pArchive->currentOffset += + (pRecord->recTotalThreads - pRecord->fakeThreads) * kNuThreadHeaderSize; + + pRecord->recHeaderLength = + bytesRead + pRecord->recTotalThreads * kNuThreadHeaderSize; + pRecord->recHeaderLength -= pRecord->fakeThreads * kNuThreadHeaderSize; + + err = Nu_ComputeThreadData(pArchive, pRecord); + BailError(err); + + /* check for "bad Mac" archives */ + if (pArchive->valHandleBadMac) { + if (pRecord->recFileSysInfo == '?' && + pRecord->recFileSysID == kNuFileSysMacMFS) + { + DBUG(("--- using 'bad mac' handling\n")); + pRecord->isBadMac = true; + pRecord->recFileSysInfo = ':'; + } + } + +bail: + if (err != kNuErrNone) + (void)Nu_FreeRecordContents(pArchive, pRecord); + return err; +} + + +/* + * Update the record's storageType if it looks like it needs it, based on + * the current set of threads. + * + * The rules we follow (stopping at the first match) are: + * - If there's a disk thread, leave it alone. Disk block size issues + * should already have been resolved. If we end up copying the same + * bogus block size we were given initially, that's fine. + * - If there's a resource fork, set the storageType to 5. + * - If there's a data fork, set the storageType to 1-3. + * - If there are no data-class threads at all, set the storageType to zero. + * + * This assumes that all updates have already been processed, i.e. there's + * no lingering add or delete threadMods. This only examines the thread + * array. + * + * NOTE: for data files (types 1, 2, and 3), the actual value may not match + * up what ProDOS would use, because this doesn't test for sparseness. + */ +static void Nu_UpdateStorageType(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err; + NuThread* pThread; + + err = Nu_FindThreadByID(pRecord, kNuThreadIDDiskImage, &pThread); + if (err == kNuErrNone) + goto bail; + + err = Nu_FindThreadByID(pRecord, kNuThreadIDRsrcFork, &pThread); + if (err == kNuErrNone) { + DBUG(("--- setting storageType to %d (was %d)\n", kNuStorageExtended, + pRecord->recStorageType)); + pRecord->recStorageType = kNuStorageExtended; + goto bail; + } + + err = Nu_FindThreadByID(pRecord, kNuThreadIDDataFork, &pThread); + if (err == kNuErrNone) { + int newType; + if (pThread->actualThreadEOF <= 512) + newType = kNuStorageSeedling; + else if (pThread->actualThreadEOF < 131072) + newType = kNuStorageSapling; + else + newType = kNuStorageTree; + DBUG(("--- setting storageType to %d (was %d)\n", newType, + pRecord->recStorageType)); + pRecord->recStorageType = newType; + goto bail; + } + + DBUG(("--- no stuff here, setting storageType to %d (was %d)\n", + kNuStorageUnknown, pRecord->recStorageType)); + pRecord->recStorageType = kNuStorageUnknown; + +bail: + return; +} + +/* + * Write the record header to the current offset of the specified file. + * This includes writing all of the thread headers. + * + * We don't "promote" records to newer versions, because that might + * require expanding and CRCing data threads. Instead, we write the + * record in a manner appropriate for the version. + * + * As a side effect, this may update the storageType to something appropriate. + * + * The position of the file pointer on exit is undefined. The position + * past the end of the record will be stored in pArchive->currentOffset. + */ +NuError Nu_WriteRecordHeader(NuArchive* pArchive, NuRecord* pRecord, FILE* fp) +{ + NuError err = kNuErrNone; + uint16_t crc; + long crcOffset; + int bytesWritten; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(fp != NULL); + + /* + * Before we get started, let's make sure the storageType makes sense + * for this record. + */ + Nu_UpdateStorageType(pArchive, pRecord); + + DBUG(("--- Writing record header (v=%d)\n", pRecord->recVersionNumber)); + + (void) Nu_WriteBytes(pArchive, fp, pRecord->recNufxID, kNufxIDLen); + err = Nu_FTell(fp, &crcOffset); + BailError(err); + + /* + * Write the static fields. + */ + crc = 0; + Nu_WriteTwo(pArchive, fp, 0); /* crc -- come back later */ + Nu_WriteTwoC(pArchive, fp, pRecord->recAttribCount, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recVersionNumber, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recTotalThreads, &crc); + Nu_WriteTwoC(pArchive, fp, (uint16_t)pRecord->recFileSysID, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recFileSysInfo, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recAccess, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recFileType, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recExtraType, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recStorageType, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recCreateWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recModWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recArchiveWhen, &crc); + bytesWritten = 56; /* 4-byte 'NuFX' plus the above */ + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing record header"); + goto bail; + } + + /* + * Write the option list, if present. + */ + if (pRecord->recVersionNumber > 0) { + Nu_WriteTwoC(pArchive, fp, pRecord->recOptionSize, &crc); + bytesWritten += 2; + + if (pRecord->recOptionSize) { + Nu_WriteBytesC(pArchive, fp, pRecord->recOptionList, + pRecord->recOptionSize, &crc); + bytesWritten += pRecord->recOptionSize; + } + } + + /* + * Preserve whatever miscellaneous junk was left in here by the last guy. + * We don't know what this is or why it's here, but who knows, maybe + * it's important. + * + * Besides, if we don't, we'll have to go back and fix the attrib count. + */ + if (pRecord->extraCount) { + Nu_WriteBytesC(pArchive, fp, pRecord->extraBytes, pRecord->extraCount, + &crc); + bytesWritten += pRecord->extraCount; + } + + /* + * If the record has a filename in the header, write it, unless + * recent changes have inspired us to drop the name from the header. + * + * Records that begin with no filename will have a default one + * stuffed in, so it's possible for pRecord->filename to be set + * already even if there wasn't one in the record. (In such cases, + * we don't write a name.) + */ + if (pRecord->recFilenameLength && !pRecord->dropRecFilename) { + Nu_WriteTwoC(pArchive, fp, pRecord->recFilenameLength, &crc); + bytesWritten += 2; + Nu_WriteBytesC(pArchive, fp, pRecord->recFilenameMOR, + pRecord->recFilenameLength, &crc); + } else { + Nu_WriteTwoC(pArchive, fp, 0, &crc); + bytesWritten += 2; + } + + /* make sure we are where we thought we would be */ + if (bytesWritten != pRecord->recAttribCount) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Didn't write what was expected (%d vs %d)", + bytesWritten, pRecord->recAttribCount); + goto bail; + } + + /* write the thread headers, and zero out "fake" thread count */ + err = Nu_WriteThreadHeaders(pArchive, pRecord, fp, &crc); + BailError(err); + + /* get the current file offset, for some computations later */ + err = Nu_FTell(fp, &pArchive->currentOffset); + BailError(err); + + /* go back and fill in the CRC */ + pRecord->recHeaderCRC = crc; + err = Nu_FSeek(fp, crcOffset, SEEK_SET); + BailError(err); + Nu_WriteTwo(pArchive, fp, pRecord->recHeaderCRC); + + /* + * All okay? + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing late record header"); + goto bail; + } + + /* + * Update values for misc record fields. + */ + Assert(pRecord->fakeThreads == 0); + pRecord->recHeaderLength = + bytesWritten + pRecord->recTotalThreads * kNuThreadHeaderSize; + pRecord->recHeaderLength -= pRecord->fakeThreads * kNuThreadHeaderSize; + + err = Nu_ComputeThreadData(pArchive, pRecord); + BailError(err); + +bail: + return err; +} + + +/* + * Prepare for a "walk" through the records. This is useful for the + * "read the TOC as you go" method of archive use. + */ +static NuError Nu_RecordWalkPrepare(NuArchive* pArchive, NuRecord** ppRecord) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(ppRecord != NULL); + + DBUG(("--- walk prep\n")); + + *ppRecord = NULL; + + if (!pArchive->haveToc) { + /* might have tried and aborted earlier, rewind to start of records */ + err = Nu_RewindArchive(pArchive); + BailError(err); + } + +bail: + return err; +} + +/* + * Get the next record from the "orig" set in the archive. + * + * On entry, pArchive->archiveFp must point at the start of the next + * record. On exit, it will point past the end of the record (headers and + * all data) that we just read. + * + * If we have the TOC, we just pull it out of the structure. If we don't, + * we read it from the archive file, and add it to the TOC being + * constructed. + */ +static NuError Nu_RecordWalkGetNext(NuArchive* pArchive, NuRecord** ppRecord) +{ + NuError err = kNuErrNone; + + Assert(pArchive != NULL); + Assert(ppRecord != NULL); + + /*DBUG(("--- walk toc=%d\n", pArchive->haveToc));*/ + + if (pArchive->haveToc) { + if (*ppRecord == NULL) + *ppRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + else + *ppRecord = (*ppRecord)->pNext; + } else { + *ppRecord = NULL; /* so we don't try to free it on exit */ + + /* allocate and fill in a new record */ + err = Nu_RecordNew(pArchive, ppRecord); + BailError(err); + + /* read data from archive file */ + err = Nu_ReadRecordHeader(pArchive, *ppRecord); + BailError(err); + err = Nu_ScanThreads(pArchive, *ppRecord, (*ppRecord)->recTotalThreads); + BailError(err); + + DBUG(("--- Found record '%s'\n", (*ppRecord)->filenameMOR)); + + /* add to list */ + err = Nu_RecordSet_AddRecord(&pArchive->origRecordSet, *ppRecord); + BailError(err); + } + +bail: + if (err != kNuErrNone && !pArchive->haveToc) { + /* on failure, free whatever we allocated */ + Nu_RecordFree(pArchive, *ppRecord); + *ppRecord = NULL; + } + return err; +} + +/* + * Finish off a successful record walk by noting that we now have a + * full table of contents. On an unsuccessful walk, blow away the TOC + * if we don't have all of it. + */ +static NuError Nu_RecordWalkFinish(NuArchive* pArchive, NuError walkErr) +{ + if (pArchive->haveToc) + return kNuErrNone; + + if (walkErr == kNuErrNone) { + pArchive->haveToc = true; + /* mark as loaded, even if there weren't any entries (e.g. new arc) */ + Nu_RecordSet_SetLoaded(&pArchive->origRecordSet, true); + return kNuErrNone; + } else { + pArchive->haveToc = false; /* redundant */ + return Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + } +} + + +/* + * If we don't have the complete record listing from the archive in + * the "orig" record set, go get it. + * + * Uses the "record walk" functions, because they're there. + */ +NuError Nu_GetTOCIfNeeded(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + uint32_t count; + + Assert(pArchive != NULL); + + if (pArchive->haveToc) + goto bail; + + DBUG(("--- GetTOCIfNeeded\n")); + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + + +/* + * =========================================================================== + * Streaming read-only operations + * =========================================================================== + */ + +/* + * Run through the entire archive, pulling out the header bits, skipping + * over the data bits, and calling "contentFunc" for each record. + */ +NuError Nu_StreamContents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err = kNuErrNone; + NuRecord tmpRecord; + NuResult result; + uint32_t count; + + if (contentFunc == NULL) { + err = kNuErrInvalidArg; + goto bail; + } + + Nu_InitRecordContents(pArchive, &tmpRecord); + count = pArchive->masterHeader.mhTotalRecords; + + while (count--) { + err = Nu_ReadRecordHeader(pArchive, &tmpRecord); + BailError(err); + err = Nu_ScanThreads(pArchive, &tmpRecord, tmpRecord.recTotalThreads); + BailError(err); + + /*Nu_DebugDumpRecord(&tmpRecord); + printf("\n");*/ + + /* let them display the contents */ + result = (*contentFunc)(pArchive, &tmpRecord); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + + /* dispose of the entry */ + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + (void) Nu_InitRecordContents(pArchive, &tmpRecord); + } + +bail: + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + return err; +} + + +/* + * If we're trying to be compatible with ShrinkIt, and we tried to extract + * a record that had nothing in it but comments and filenames, then we need + * to create a zero-byte data file. + * + * GS/ShrinkIt v1.1 has a bug that causes it to store zero-byte data files + * (and, for that matter, zero-byte resource forks) without a thread header. + * It isn't able to extract them. This isn't so much a compatibility + * thing as it is a bug-workaround thing. + * + * The record's storage type should tell us if it was an extended file or + * a plain file. Not really important when extracting, but if we want + * to recreate the original we need to re-add the resource fork so + * NufxLib knows to make it an extended file. + */ +static NuError Nu_FakeZeroExtract(NuArchive* pArchive, NuRecord* pRecord, + int threadKind) +{ + NuError err; + NuThread fakeThread; + + Assert(pRecord != NULL); + + DBUG(("--- found empty record, creating zero-byte file (kind=0x%04x)\n", + threadKind)); + fakeThread.thThreadClass = kNuThreadClassData; + fakeThread.thThreadFormat = kNuThreadFormatUncompressed; + fakeThread.thThreadKind = threadKind; + fakeThread.thThreadCRC = kNuInitialThreadCRC; + fakeThread.thThreadEOF = 0; + fakeThread.thCompThreadEOF = 0; + + fakeThread.threadIdx = (NuThreadIdx)-1; /* shouldn't matter */ + fakeThread.actualThreadEOF = 0; + fakeThread.fileOffset = 0; /* shouldn't matter */ + fakeThread.used = false; + + err = Nu_ExtractThreadBulk(pArchive, pRecord, &fakeThread); + if (err == kNuErrSkipped) + err = Nu_SkipThread(pArchive, pRecord, &fakeThread); + + return err; +} + + +/* + * Run through the entire archive, extracting the contents. + */ +NuError Nu_StreamExtract(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord tmpRecord; + Boolean needFakeData, needFakeRsrc; + uint32_t count; + long idx; + + /* reset this just to be safe */ + pArchive->lastDirCreatedUNI = NULL; + + Nu_InitRecordContents(pArchive, &tmpRecord); + count = pArchive->masterHeader.mhTotalRecords; + + while (count--) { + /* + * Read the record header (which includes the thread header blocks). + */ + err = Nu_ReadRecordHeader(pArchive, &tmpRecord); + BailError(err); + + /* + * We may need to pull the filename out of a thread, but we don't + * want to blow past any data while we do it. There's no really + * good way to deal with this, so we just assume that all NuFX + * applications are nice and put the filename thread first. + */ + for (idx = 0; idx < (long)tmpRecord.recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(&tmpRecord, idx); + + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) + == kNuThreadIDFilename) + { + break; + } + } + /* if we have fn, read it; either way, leave idx pointing at next */ + if (idx < (long)tmpRecord.recTotalThreads) { + idx++; /* want count, not index */ + err = Nu_ScanThreads(pArchive, &tmpRecord, idx); + BailError(err); + } else + idx = 0; + if (tmpRecord.filenameMOR == NULL) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't find filename in record"); + err = kNuErrBadRecord; + goto bail; + } + + /*Nu_DebugDumpRecord(&tmpRecord); + printf("\n");*/ + + needFakeData = true; + needFakeRsrc = (tmpRecord.recStorageType == kNuStorageExtended); + + /* extract all relevant (remaining) threads */ + pArchive->lastFileCreatedUNI = NULL; + for ( ; idx < (long)tmpRecord.recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(&tmpRecord, idx); + + if (pThread->thThreadClass == kNuThreadClassData) { + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } else if (pThread->thThreadKind == kNuThreadKindDiskImage) { + /* needFakeRsrc shouldn't be set, but clear anyway */ + needFakeData = needFakeRsrc = false; + } + err = Nu_ExtractThreadBulk(pArchive, &tmpRecord, pThread); + if (err == kNuErrSkipped) { + err = Nu_SkipThread(pArchive, &tmpRecord, pThread); + BailError(err); + } else if (err != kNuErrNone) + goto bail; + } else { + DBUG(("IGNORING 0x%08lx from '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + tmpRecord.filename)); + if (NuGetThreadID(pThread) != kNuThreadIDComment && + NuGetThreadID(pThread) != kNuThreadIDFilename) + { + /* unknown stuff in record, skip thread fakery */ + needFakeData = needFakeRsrc = false; + } + err = Nu_SkipThread(pArchive, &tmpRecord, pThread); + BailError(err); + } + } + + /* + * As in Nu_ExtractRecordByPtr, we need to synthesize empty forks for + * cases where GSHK omitted the data thread entirely. + */ + Assert(!pArchive->valMaskDataless || (!needFakeData && !needFakeRsrc)); + if (needFakeData) { + err = Nu_FakeZeroExtract(pArchive, &tmpRecord, + kNuThreadKindDataFork); + BailError(err); + } + if (needFakeRsrc) { + err = Nu_FakeZeroExtract(pArchive, &tmpRecord, + kNuThreadKindRsrcFork); + BailError(err); + } + + /* dispose of the entry */ + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + (void) Nu_InitRecordContents(pArchive, &tmpRecord); + } + +bail: + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + return err; +} + +/* + * Test the contents of an archive. Works just like extraction, but we + * don't store anything. + */ +NuError Nu_StreamTest(NuArchive* pArchive) +{ + NuError err; + + pArchive->testMode = true; + err = Nu_StreamExtract(pArchive); + pArchive->testMode = false; + return err; +} + + +/* + * =========================================================================== + * Non-streaming read-only operations + * =========================================================================== + */ + +/* + * Shove the archive table of contents through the callback function. + * + * This only walks through the "orig" list, so it does not reflect the + * results of un-flushed changes. + */ +NuError Nu_Contents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuResult result; + uint32_t count; + + if (contentFunc == NULL) { + err = kNuErrInvalidArg; + goto bail; + } + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + + Assert(pRecord->filenameMOR != NULL); + result = (*contentFunc)(pArchive, pRecord); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + +/* + * Extract all interesting threads from a record, given a NuRecord pointer + * into the archive data structure. + * + * This assumes random access, so it can't be used in streaming mode. + */ +static NuError Nu_ExtractRecordByPtr(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + Boolean needFakeData, needFakeRsrc; + uint32_t idx; + + needFakeData = true; + needFakeRsrc = (pRecord->recStorageType == kNuStorageExtended); + + Assert(!Nu_IsStreaming(pArchive)); /* we don't skip things we don't read */ + Assert(pRecord != NULL); + + /* extract all relevant threads */ + pArchive->lastFileCreatedUNI = NULL; + for (idx = 0; idx < pRecord->recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(pRecord, idx); + + if (pThread->thThreadClass == kNuThreadClassData) { + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } else if (pThread->thThreadKind == kNuThreadKindDiskImage) { + /* needFakeRsrc shouldn't be set, but clear anyway */ + needFakeData = needFakeRsrc = false; + } + err = Nu_ExtractThreadBulk(pArchive, pRecord, pThread); + if (err == kNuErrSkipped) { + err = Nu_SkipThread(pArchive, pRecord, pThread); + BailError(err); + } else if (err != kNuErrNone) + goto bail; + } else { + if (NuGetThreadID(pThread) != kNuThreadIDComment && + NuGetThreadID(pThread) != kNuThreadIDFilename) + { + /* + * This record has a thread we don't recognize. Disable + * the thread fakery to avoid doing anything weird -- we + * should only need to create zero-length files for + * simple file records. + */ + needFakeData = needFakeRsrc = false; + } + DBUG(("IGNORING 0x%08lx from '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filenameMOR)); + } + } + + /* + * GSHK creates empty threads for zero-length forks. It doesn't always + * handle them correctly when extracting, so it appears this behavior + * may not be intentional. + * + * We need to create an empty file for whichever forks are missing. + * Could be the data fork, resource fork, or both. The only way to + * know what's expected is to examine the file's storage type. + * + * If valMaskDataless is enabled, this won't fire, because we will have + * "forged" the appropriate threads. + * + * Note there's another one of these below, in Nu_StreamExtract. + */ + Assert(!pArchive->valMaskDataless || (!needFakeData && !needFakeRsrc)); + if (needFakeData) { + err = Nu_FakeZeroExtract(pArchive, pRecord, kNuThreadKindDataFork); + BailError(err); + } + if (needFakeRsrc) { + err = Nu_FakeZeroExtract(pArchive, pRecord, kNuThreadKindRsrcFork); + BailError(err); + } + +bail: + return err; +} + + +/* + * Extract a big buncha files. + */ +NuError Nu_Extract(NuArchive* pArchive) +{ + NuError err; + NuRecord* pRecord = NULL; + uint32_t count; + long offset; + + /* reset this just to be safe */ + pArchive->lastDirCreatedUNI = NULL; + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + /* read the record and threads if we don't have them yet */ + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + + if (!pArchive->haveToc) { + /* remember where the end of the record is */ + err = Nu_FTell(pArchive->archiveFp, &offset); + BailError(err); + } + + /* extract one or more threads */ + err = Nu_ExtractRecordByPtr(pArchive, pRecord); + BailError(err); + + if (!pArchive->haveToc) { + /* line us back up so RecordWalkGetNext can read the record hdr */ + err = Nu_FSeek(pArchive->archiveFp, offset, SEEK_SET); + BailError(err); + } + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + +/* + * Extract a single record. + */ +NuError Nu_ExtractRecord(NuArchive* pArchive, NuRecordIdx recIdx) +{ + NuError err; + NuRecord* pRecord; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the correct record by index */ + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recIdx, &pRecord); + BailError(err); + Assert(pRecord != NULL); + + /* extract whatever looks promising */ + err = Nu_ExtractRecordByPtr(pArchive, pRecord); + BailError(err); + +bail: + return err; +} + + +/* + * Test the contents of an archive. Works just like extraction, but we + * don't store anything. + */ +NuError Nu_Test(NuArchive* pArchive) +{ + NuError err; + + pArchive->testMode = true; + err = Nu_Extract(pArchive); + pArchive->testMode = false; + return err; +} + +/* + * Test a single record. + */ +NuError Nu_TestRecord(NuArchive* pArchive, NuRecordIdx recIdx) +{ + NuError err; + NuRecord* pRecord; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the correct record by index */ + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recIdx, &pRecord); + BailError(err); + Assert(pRecord != NULL); + + /* extract whatever looks promising */ + pArchive->testMode = true; + err = Nu_ExtractRecordByPtr(pArchive, pRecord); + pArchive->testMode = false; + BailError(err); + +bail: + return err; +} + + +/* + * Return a pointer to a NuRecord. + * + * This pulls the record out of the "orig" set, so it will work even + * for records that have been deleted. It will not reflect changes + * made by previous "write" calls, not even SetRecordAttr. + */ +NuError Nu_GetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord) +{ + NuError err; + + if (recordIdx == 0 || ppRecord == NULL) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recordIdx, + (NuRecord**)ppRecord); + if (err == kNuErrNone) { + Assert(*ppRecord != NULL); + } + /* fall through with error */ + +bail: + return err; +} + +/* + * Find the recordIdx of a record by storage name. + */ +NuError Nu_GetRecordIdxByName(NuArchive* pArchive, const char* nameMOR, + NuRecordIdx* pRecordIdx) +{ + NuError err; + NuRecord* pRecord = NULL; + + if (pRecordIdx == NULL) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_RecordSet_FindByName(&pArchive->origRecordSet, nameMOR, &pRecord); + if (err == kNuErrNone) { + Assert(pRecord != NULL); + *pRecordIdx = pRecord->recordIdx; + } + /* fall through with error */ + +bail: + return err; +} + +/* + * Find the recordIdx of a record by zero-based position. + */ +NuError Nu_GetRecordIdxByPosition(NuArchive* pArchive, uint32_t position, + NuRecordIdx* pRecordIdx) +{ + NuError err; + const NuRecord* pRecord; + + if (pRecordIdx == NULL) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + if (position >= Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet)) { + err = kNuErrRecordNotFound; + goto bail; + } + + pRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + while (position--) { + Assert(pRecord->pNext != NULL); + pRecord = pRecord->pNext; + } + + *pRecordIdx = pRecord->recordIdx; + +bail: + return err; +} + + +/* + * =========================================================================== + * Read/write record operations (add, delete) + * =========================================================================== + */ + +/* + * Find an existing record somewhere in the archive. If the "copy" set + * exists it will be searched. If not, the "orig" set is searched, and + * if an entry is found a "copy" set will be created. + * + * The goal is to always return something from the "copy" set, which we + * could do easily by just creating the "copy" set and then searching in + * it. However, we don't want to create the "copy" set if we don't have + * to, so we search "orig" if "copy" doesn't exist yet. + * + * The record returned will always be from the "copy" set. An error result + * is returned if the record isn't found. + */ +NuError Nu_FindRecordForWriteByIdx(NuArchive* pArchive, NuRecordIdx recIdx, + NuRecord** ppFoundRecord) +{ + NuError err; + + Assert(pArchive != NULL); + Assert(ppFoundRecord != NULL); + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, recIdx, + ppFoundRecord); + } else { + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recIdx, + ppFoundRecord); + *ppFoundRecord = NULL; /* can't delete from here */ + } + BailErrorQuiet(err); + + /* + * The record exists. If we were looking in the "orig" set, we have + * to create a "copy" set and return it from there. + */ + if (*ppFoundRecord == NULL) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, recIdx, + ppFoundRecord); + Assert(err == kNuErrNone && *ppFoundRecord != NULL); /* must succeed */ + BailError(err); + } + +bail: + return err; +} + + +/* + * Deal with the situation where we're trying to add a record with the + * same name as an existing record. The existing record can't be in the + * "new" list (that's handled differently) and can't already have been + * deleted. + * + * This will either delete the existing record or return with an error. + * + * If we decide to delete the record, and the "orig" record set was + * passed in, then the record will be deleted from the "copy" set (which + * will be created only if necessary). + */ +static NuError Nu_HandleAddDuplicateRecord(NuArchive* pArchive, + NuRecordSet* pRecordSet, NuRecord* pRecord, + const NuFileDetails* pFileDetails) +{ + NuError err = kNuErrNone; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pRecordSet == &pArchive->origRecordSet || + pRecordSet == &pArchive->copyRecordSet); + Assert(pRecord != NULL); + Assert(pFileDetails != NULL); + Assert(pArchive->valAllowDuplicates == false); + + /* + * If "only update older" is set, check the dates. Reject the + * request if the archived file isn't older than the new file. This + * tells the application that the request was rejected, but it's + * okay for them to move on to the next file. + */ + if (pArchive->valOnlyUpdateOlder) { + if (!Nu_IsOlder(&pRecord->recModWhen, &pFileDetails->modWhen)) + return kNuErrNotNewer; + } + + /* + * The file exists when it shouldn't. Decide what to do, based + * on the options configured by the application. + * + * If they "might" allow overwrites, and they have an error-handling + * callback defined, call that to find out what they want to do + * here. Options include skipping or overwriting the record. + * + * We don't currently allow renaming of records, though I suppose we + * could. + */ + switch (pArchive->valHandleExisting) { + case kNuMaybeOverwrite: + if (pArchive->errorHandlerFunc != NULL) { + errorStatus.operation = kNuOpAdd; + errorStatus.err = kNuErrRecordExists; + errorStatus.sysErr = 0; + errorStatus.message = NULL; + errorStatus.pRecord = pRecord; + UNICHAR* pathnameUNI = + Nu_CopyMORToUNI(pFileDetails->storageNameMOR); + errorStatus.pathnameUNI = pathnameUNI; + errorStatus.origPathname = pFileDetails->origName; + errorStatus.filenameSeparator = + NuGetSepFromSysInfo(pFileDetails->fileSysInfo); + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = false; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = false; + errorStatus.canOverwrite = true; + + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + Nu_Free(pArchive, pathnameUNI); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuRetry: + case kNuRename: + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, treat like NeverOverwrite */ + err = kNuErrSkipped; + goto bail; + } + break; + case kNuNeverOverwrite: + err = kNuErrSkipped; + goto bail; + case kNuMustOverwrite: + case kNuAlwaysOverwrite: + /* fall through to record deletion */ + break; + default: + Assert(0); + err = kNuErrInternal; + goto bail; + } + + err = kNuErrNone; + + /* + * We're going to overwrite the existing record. To do this, we have + * to start by deleting it from the "copy" list. + * + * If the copy set doesn't yet exist, we have to create it and find + * the record in the new set. + */ + if (pRecordSet == &pArchive->origRecordSet) { + Assert(!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)); + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, + pRecord->recordIdx, &pRecord); + Assert(err == kNuErrNone && pRecord != NULL); /* must succeed */ + BailError(err); + } + + DBUG(("+++ deleting record %ld\n", pRecord->recordIdx)); + err = Nu_RecordSet_DeleteRecord(pArchive,&pArchive->copyRecordSet, pRecord); + BailError(err); + +bail: + return err; +} + +/* + * Create a new record, filling in most of the blanks from "pFileDetails". + * + * The filename in pFileDetails->storageName will be remembered. If no + * filename thread is added to this record before the next Flush call, a + * filename thread will be generated from this name. + * + * This always creates a "version 3" record, regardless of what else is + * in the archive. The filename is always in a thread. + * + * On success, the NuRecordIdx of the newly-created record will be placed + * in "*pRecordIdx", and the NuThreadIdx of the filename thread will be + * placed in "*pThreadIdx". If "*ppNewRecord" is non-NULL, it gets a pointer + * to the newly-created record (this isn't part of the external interface). + */ +NuError Nu_AddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx, NuRecord** ppNewRecord) +{ + NuError err; + NuRecord* pNewRecord = NULL; + + if (pFileDetails == NULL || pFileDetails->storageNameMOR == NULL || + pFileDetails->storageNameMOR[0] == '\0' || + NuGetSepFromSysInfo(pFileDetails->fileSysInfo) == 0) + /* pRecordIdx may be NULL */ + /* ppNewRecord may be NULL */ + { + err = kNuErrInvalidArg; + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* NuFX spec forbids leading fssep chars */ + if (pFileDetails->storageNameMOR[0] == + NuGetSepFromSysInfo(pFileDetails->fileSysInfo)) + { + err = kNuErrLeadingFssep; + goto bail; + } + + /* + * If requested, look for an existing record. Look in the "copy" + * list if we have it (so we don't complain if they've already deleted + * the record), or in the "orig" list if we don't. Look in the "new" + * list to see if it clashes with something we've just added. + * + * If this is a brand-new archive, there won't be an "orig" list + * either. + */ + if (!pArchive->valAllowDuplicates) { + NuRecordSet* pRecordSet; + NuRecord* pFoundRecord; + + pRecordSet = &pArchive->copyRecordSet; + if (!Nu_RecordSet_GetLoaded(pRecordSet)) + pRecordSet = &pArchive->origRecordSet; + Assert(Nu_RecordSet_GetLoaded(pRecordSet)); + err = Nu_RecordSet_FindByName(pRecordSet, pFileDetails->storageNameMOR, + &pFoundRecord); + if (err == kNuErrNone) { + /* handle the existing record */ + DBUG(("--- Duplicate record found (%06ld) '%s'\n", + pFoundRecord->recordIdx, pFoundRecord->filenameMOR)); + err = Nu_HandleAddDuplicateRecord(pArchive, pRecordSet, + pFoundRecord, pFileDetails); + if (err != kNuErrNone) { + /* for whatever reason, we're not replacing it */ + DBUG(("--- Returning err=%d\n", err)); + goto bail; + } + } else { + /* if we *must* replace an existing file, we fail now */ + if (pArchive->valHandleExisting == kNuMustOverwrite) { + DBUG(("+++ can't freshen nonexistent '%s'\n", + pFileDetails->storageName)); + err = kNuErrDuplicateNotFound; + goto bail; + } + } + + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) { + err = Nu_RecordSet_FindByName(&pArchive->newRecordSet, + pFileDetails->storageNameMOR, &pFoundRecord); + if (err == kNuErrNone) { + /* we can't delete from the "new" list, so return an error */ + err = kNuErrRecordExists; + goto bail; + } + } + + /* clear "err" so we can continue */ + err = kNuErrNone; + } + + /* + * Prepare the new record structure. + */ + err = Nu_RecordNew(pArchive, &pNewRecord); + BailError(err); + (void) Nu_InitRecordContents(pArchive, pNewRecord); + memcpy(pNewRecord->recNufxID, kNufxID, kNufxIDLen); + /*pNewRecord->recHeaderCRC*/ + /*pNewRecord->recAttribCount*/ + pNewRecord->recVersionNumber = kNuOurRecordVersion; + pNewRecord->recTotalThreads = 0; + pNewRecord->recFileSysID = pFileDetails->fileSysID; + pNewRecord->recFileSysInfo = pFileDetails->fileSysInfo; + pNewRecord->recAccess = pFileDetails->access; + pNewRecord->recFileType = pFileDetails->fileType; + pNewRecord->recExtraType = pFileDetails->extraType; + pNewRecord->recStorageType = pFileDetails->storageType; + pNewRecord->recCreateWhen = pFileDetails->createWhen; + pNewRecord->recModWhen = pFileDetails->modWhen; + pNewRecord->recArchiveWhen = pFileDetails->archiveWhen; + pNewRecord->recOptionSize = 0; + pNewRecord->extraCount = 0; + pNewRecord->recFilenameLength = 0; + + pNewRecord->recordIdx = Nu_GetNextRecordIdx(pArchive); + pNewRecord->threadFilenameMOR = NULL; + pNewRecord->newFilenameMOR = strdup(pFileDetails->storageNameMOR); + pNewRecord->filenameMOR = pNewRecord->newFilenameMOR; + pNewRecord->recHeaderLength = -1; + pNewRecord->totalCompLength = 0; + pNewRecord->fakeThreads = 0; + pNewRecord->fileOffset = -1; + + /* + * Add it to the "new" record set. + */ + err = Nu_RecordSet_AddRecord(&pArchive->newRecordSet, pNewRecord); + BailError(err); + + /* return values */ + if (pRecordIdx != NULL) + *pRecordIdx = pNewRecord->recordIdx; + if (ppNewRecord != NULL) + *ppNewRecord = pNewRecord; + +bail: + return err; +} + + +/* + * Add a new "add file" thread mod to the specified record. + * + * The caller should have already verified that there isn't another + * "add file" thread mod with the same ThreadID. + */ +static NuError Nu_AddFileThreadMod(NuArchive* pArchive, NuRecord* pRecord, + const UNICHAR* pathnameUNI, const NuFileDetails* pFileDetails, + Boolean fromRsrcFork) +{ + NuError err; + NuThreadFormat threadFormat; + NuDataSource* pDataSource = NULL; + NuThreadMod* pThreadMod = NULL; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pathnameUNI != NULL); + Assert(pFileDetails != NULL); + Assert(fromRsrcFork == true || fromRsrcFork == false); + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + /* decide if this should be compressed; we know source isn't */ + if (Nu_IsCompressibleThreadID(pFileDetails->threadID)) + threadFormat = Nu_ConvertCompressValToFormat(pArchive, + pArchive->valDataCompression); + else + threadFormat = kNuThreadFormatUncompressed; + + /* create a data source for this file, which is assumed uncompressed */ + err = Nu_DataSourceFile_New(kNuThreadFormatUncompressed, 0, + pathnameUNI, fromRsrcFork, &pDataSource); + BailError(err); + + /* create a new ThreadMod */ + err = Nu_ThreadModAdd_New(pArchive, pFileDetails->threadID, threadFormat, + pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != NULL); + /*pDataSource = NULL;*/ /* ThreadModAdd_New makes a copy */ + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + pThreadMod = NULL; /* don't free on exit */ + +bail: + if (pDataSource != NULL) + Nu_DataSourceFree(pDataSource); + if (pThreadMod != NULL) + Nu_ThreadModFree(pArchive, pThreadMod); + return err; +} + +/* + * Make note of a file to add. This goes beyond AddRecord and AddThread + * calls by searching the list of newly-added files for matching pairs + * of data and rsrc forks. This is independent of the "overwrite existing + * files" feature. The comparison is made based on storageName. + * + * "fromRsrcFork" tells us how to open the source file, not what type + * of thread the file should be stored as. + * + * If "pRecordIdx" is non-NULL, it will receive the newly assigned recordID. + */ +NuError Nu_AddFile(NuArchive* pArchive, const UNICHAR* pathnameUNI, + const NuFileDetails* pFileDetails, Boolean fromRsrcFork, + NuRecordIdx* pRecordIdx) +{ + NuError err = kNuErrNone; + NuRecordIdx recordIdx = 0; + NuRecord* pRecord; + + if (pathnameUNI == NULL || pFileDetails == NULL || + !(fromRsrcFork == true || fromRsrcFork == false)) + { + return kNuErrInvalidArg; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + if (pFileDetails->storageNameMOR == NULL) { + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Must specify storageName"); + goto bail; + } + if (pFileDetails->storageNameMOR[0] == + NuGetSepFromSysInfo(pFileDetails->fileSysInfo)) + { + err = kNuErrLeadingFssep; + goto bail; + } + + DBUG(("+++ ADDING '%s' (%s) 0x%02lx 0x%04lx threadID=0x%08lx\n", + pathnameUNI, pFileDetails->storageName, pFileDetails->fileType, + pFileDetails->extraType, pFileDetails->threadID)); + + /* + * See if there's another record among the "new additions" with the + * same storageName and compatible threads. + * + * If found, add a new thread in that record. If an incompatibility + * exists (same fork already present, disk image is there, etc), either + * create a new record or return with an error. + * + * We want to search from the *end* of the "new" list, so that if + * duplicates are allowed we find the entry most likely to be paired + * up with the fork currently being added. + */ + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) { + NuRecord* pNewRecord; + + err = Nu_RecordSet_ReverseFindByName(&pArchive->newRecordSet, + pFileDetails->storageNameMOR, &pNewRecord); + if (err == kNuErrNone) { + /* is it okay to add it here? */ + err = Nu_OkayToAddThread(pArchive, pNewRecord, + pFileDetails->threadID); + + if (err == kNuErrNone) { + /* okay to add it to this record */ + DBUG((" attaching to existing record %06ld\n", + pNewRecord->recordIdx)); + err = Nu_AddFileThreadMod(pArchive, pNewRecord, pathnameUNI, + pFileDetails, fromRsrcFork); + BailError(err); + recordIdx = pNewRecord->recordIdx; + goto bail; /* we're done! */ + } + + err = kNuErrNone; /* go a little farther */ + + /* + * We found a brand-new record with the same name, but we + * can't add this fork to that record. We can't delete the + * item from the "new" list, so we can ignore HandleExisting. + * If we don't allow duplicates, return an error; if we do, + * then just continue with the normal processing path. + */ + if (!pArchive->valAllowDuplicates) { + DBUG(("+++ found matching record in new list, no dups\n")); + err = kNuErrRecordExists; + goto bail; + } + + } else if (err == kNuErrRecNameNotFound) { + /* no match in "new" list, fall through to normal processing */ + err = kNuErrNone; + } else { + /* general failure */ + goto bail; + } + } + + /* + * Wasn't found, invoke Nu_AddRecord. This will search through the + * existing records, using the "allow duplicates" flag to cope with + * any matches it finds. On success, we should have a brand-new record + * to play with. + */ + err = Nu_AddRecord(pArchive, pFileDetails, &recordIdx, &pRecord); + BailError(err); + DBUG(("--- Added new record %06ld\n", recordIdx)); + + /* + * Got the record, now add a data file thread. + */ + err = Nu_AddFileThreadMod(pArchive, pRecord, pathnameUNI, pFileDetails, + fromRsrcFork); + BailError(err); + +bail: + if (err == kNuErrNone && pRecordIdx != NULL) + *pRecordIdx = recordIdx; + + return err; +} + + +/* + * Rename a record. There are three situations: + * + * (1) Record has the filename in a thread, and the field has enough + * room to hold the new name. For this case we add an "update" threadMod + * with the new data. + * (2) Record has the filename in a thread, and there is not enough room + * to hold the new name. Here, we add a "delete" threadMod for the + * existing filename, and add an "add" threadMod for the new. + * (3) Record stores the filename in the header. We zero out the filename + * and add a filename thread. + * + * We don't actually check to see if the filename is changing. If you + * want to rename something to the same thing, go right ahead. (This + * provides a way for applications to "filter" records that have filenames + * in the headers instead of a thread.) + * + * BUG: we shouldn't allow a disk image to be renamed to have a complex + * path name (e.g. "dir1:dir2:foo"). However, we may not be able to catch + * that here depending on pending operations. + * + * We might also want to screen out trailing fssep chars, though the NuFX + * spec doesn't say they're illegal. + */ +NuError Nu_Rename(NuArchive* pArchive, NuRecordIdx recIdx, + const char* pathnameMOR, char fssepMOR) +{ + NuError err; + NuRecord* pRecord; + NuThread* pFilenameThread; + const NuThreadMod* pThreadMod; + NuThreadMod* pNewThreadMod = NULL; + NuDataSource* pDataSource = NULL; + long requiredCapacity, existingCapacity, newCapacity; + Boolean doDelete, doAdd, doUpdate; + + if (recIdx == 0 || pathnameMOR == NULL || pathnameMOR[0] == '\0' || + fssepMOR == '\0') + { + return kNuErrInvalidArg; + } + + if (pathnameMOR[0] == fssepMOR) { + err = kNuErrLeadingFssep; + Nu_ReportError(NU_BLOB, err, "rename path"); + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the record in the "copy" set */ + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + BailError(err); + Assert(pRecord != NULL); + + /* look for a filename thread */ + err = Nu_FindThreadByID(pRecord, kNuThreadIDFilename, &pFilenameThread); + + if (err != kNuErrNone) + pFilenameThread = NULL; + else if (err == kNuErrNone && pRecord->pThreadMods) { + /* found a thread, check to see if it has been deleted (or modifed) */ + Assert(pFilenameThread != NULL); + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pFilenameThread->threadIdx); + if (pThreadMod != NULL) { + DBUG(("--- tried to modify threadIdx %ld, which has already been\n", + pFilenameThread->threadIdx)); + err = kNuErrModThreadChange; + goto bail; + } + } + + /* + * Looks like we're okay so far. Figure out what to do. + */ + doDelete = doAdd = doUpdate = false; + newCapacity = existingCapacity = 0; + requiredCapacity = strlen(pathnameMOR); + + if (pFilenameThread != NULL) { + existingCapacity = pFilenameThread->thCompThreadEOF; + if (existingCapacity >= requiredCapacity) { + doUpdate = true; + newCapacity = existingCapacity; + } else { + doDelete = doAdd = true; + /* make sure they have a few bytes of leeway */ + /*newCapacity = (requiredCapacity + kNuDefaultFilenameThreadSize) & + (~(kNuDefaultFilenameThreadSize-1));*/ + newCapacity = requiredCapacity + 8; + } + } else { + doAdd = true; + /*newCapacity = (requiredCapacity + kNuDefaultFilenameThreadSize) & + (~(kNuDefaultFilenameThreadSize-1));*/ + newCapacity = requiredCapacity + 8; + } + + Assert(doAdd || doDelete || doUpdate); + Assert(doDelete == false || doAdd == true); + + /* create a data source for the filename, if needed */ + if (doAdd || doUpdate) { + Assert(newCapacity); + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, + newCapacity, (const uint8_t*)strdup(pathnameMOR), 0, + requiredCapacity /*(strlen)*/, Nu_InternalFreeCallback, + &pDataSource); + BailError(err); + } + + if (doDelete) { + err = Nu_ThreadModDelete_New(pArchive, pFilenameThread->threadIdx, + kNuThreadIDFilename, &pNewThreadMod); + BailError(err); + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = NULL; /* successful, don't free */ + } + + if (doAdd) { + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDFilename, + kNuThreadFormatUncompressed, pDataSource, &pNewThreadMod); + BailError(err); + /*pDataSource = NULL;*/ /* ThreadModAdd_New makes a copy */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = NULL; /* successful, don't free */ + } + + if (doUpdate) { + err = Nu_ThreadModUpdate_New(pArchive, pFilenameThread->threadIdx, + pDataSource, &pNewThreadMod); + BailError(err); + /*pDataSource = NULL;*/ /* ThreadModAdd_New makes a copy */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = NULL; /* successful, don't free */ + } + + DBUG(("--- renaming '%s' to '%s' with delete=%d add=%d update=%d\n", + pRecord->filenameMOR, pathnameMOR, doDelete, doAdd, doUpdate)); + + /* + * Update the fssep, if necessary. (This is slightly silly -- we + * have to rewrite the record header anyway since we're changing + * threads around.) + */ + if (NuGetSepFromSysInfo(pRecord->recFileSysInfo) != fssepMOR) { + DBUG(("--- and updating the fssep\n")); + pRecord->recFileSysInfo = NuSetSepInSysInfo(pRecord->recFileSysInfo, + fssepMOR); + pRecord->dirtyHeader = true; + } + + /* if we had a header filename, mark it for oblivion */ + if (pFilenameThread == NULL) { + DBUG(("+++ rename gonna drop the filename\n")); + pRecord->dropRecFilename = true; + } + +bail: + Nu_ThreadModFree(pArchive, pNewThreadMod); + Nu_DataSourceFree(pDataSource); + return err; +} + + +/* + * Update a record's attributes with the contents of pRecordAttr. + */ +NuError Nu_SetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr) +{ + NuError err; + NuRecord* pRecord; + + if (pRecordAttr == NULL) + return kNuErrInvalidArg; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* pull the record out of the "copy" set */ + err = Nu_FindRecordForWriteByIdx(pArchive, recordIdx, &pRecord); + BailError(err); + + Assert(pRecord != NULL); + pRecord->recFileSysID = pRecordAttr->fileSysID; + /*pRecord->recFileSysInfo = pRecordAttr->fileSysInfo;*/ + pRecord->recAccess = pRecordAttr->access; + pRecord->recFileType = pRecordAttr->fileType; + pRecord->recExtraType = pRecordAttr->extraType; + pRecord->recCreateWhen = pRecordAttr->createWhen; + pRecord->recModWhen = pRecordAttr->modWhen; + pRecord->recArchiveWhen = pRecordAttr->archiveWhen; + pRecord->dirtyHeader = true; + +bail: + return err; +} + + +/* + * Bulk-delete several records, using the selection filter callback. + */ +NuError Nu_Delete(NuArchive* pArchive) +{ + NuError err; + NuSelectionProposal selProposal; + NuRecord* pNextRecord; + NuRecord* pRecord; + NuResult result; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * If we don't yet have a copy set, make one. + */ + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + } + + /* + * Run through the copy set. This is different from most other + * operations, which run through the "orig" set. However, since + * we're not interested in allowing the user to delete things that + * have already been deleted, we might as well use this set. + */ + pNextRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pNextRecord != NULL) { + pRecord = pNextRecord; + pNextRecord = pRecord->pNext; + + /* + * Deletion of modified records (thread adds, deletes, or updates) + * isn't allowed. There's no point in showing the record to the + * user. + */ + if (pRecord->pThreadMods != NULL) { + DBUG(("+++ Skipping delete on a modified record\n")); + continue; + } + + /* + * If a selection filter is defined, allow the user the opportunity + * to select which files will be deleted, or abort the entire + * operation. + */ + if (pArchive->selectionFilterFunc != NULL) { + selProposal.pRecord = pRecord; + selProposal.pThread = pRecord->pThreads; /* doesn't matter */ + result = (*pArchive->selectionFilterFunc)(pArchive, &selProposal); + + if (result == kNuSkip) + continue; + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + + /* + * Do we want to allow this? (Same test as for DeleteRecord.) + */ + if (pRecord->pThreadMods != NULL || pRecord->dirtyHeader) { + DBUG(("--- Tried to delete a modified record\n")); + err = kNuErrModRecChange; + goto bail; + } + + err = Nu_RecordSet_DeleteRecord(pArchive, &pArchive->copyRecordSet, + pRecord); + BailError(err); + } + +bail: + return err; +} + +/* + * Delete an entire record. + */ +NuError Nu_DeleteRecord(NuArchive* pArchive, NuRecordIdx recIdx) +{ + NuError err; + NuRecord* pRecord; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + BailError(err); + + /* + * Deletion of modified records (thread adds, deletes, or updates) isn't + * allowed. It probably wouldn't be hard to handle, but it's pointless. + * Preventing the action maintains our general semantics of disallowing + * conflicting actions on the same object. + * + * We also block it if the header is dirty (e.g. they changed the + * record's filetype). This isn't necessary for correct operation, + * but again it maintains the semantics. + */ + if (pRecord->pThreadMods != NULL || pRecord->dirtyHeader) { + DBUG(("--- Tried to delete a modified record\n")); + err = kNuErrModRecChange; + goto bail; + } + + err = Nu_RecordSet_DeleteRecord(pArchive,&pArchive->copyRecordSet, pRecord); + BailError(err); + +bail: + return err; +} + diff --git a/nufxlib/SourceSink.c b/nufxlib/SourceSink.c new file mode 100644 index 0000000..e5d6313 --- /dev/null +++ b/nufxlib/SourceSink.c @@ -0,0 +1,858 @@ +/* + * 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. + * + * Implementation of DataSource and DataSink objects. + */ +#include "NufxLibPriv.h" + + + +/* + * =========================================================================== + * NuDataSource + * =========================================================================== + */ + +/* + * Allocate a new DataSource structure. + */ +static NuError Nu_DataSourceNew(NuDataSource** ppDataSource) +{ + Assert(ppDataSource != NULL); + + *ppDataSource = Nu_Malloc(NULL, sizeof(**ppDataSource)); + if (*ppDataSource == NULL) + return kNuErrMalloc; + + (*ppDataSource)->sourceType = kNuDataSourceUnknown; + + return kNuErrNone; +} + + +/* + * Make a copy of a DataSource. Actually just increments a reference count. + * + * What we *really* want to be doing is copying the structure (since we + * can't guarantee copy-on-write semantics for the fields without adding + * more stuff) and refcounting the underlying resource, so that "auto-free" + * semantics work out right. + * + * We're okay for now, since for the most part we only do work on one + * copy of each. (I wish I could remember why this copying thing was + * needed in the first place.) Buffer sources are a little scary since + * they include a "curOffset" value. + * + * Returns NULL on error. + */ +NuDataSource* Nu_DataSourceCopy(NuDataSource* pDataSource) +{ + Assert(pDataSource->common.refCount >= 1); + pDataSource->common.refCount++; + return pDataSource; + +#if 0 /* we used to copy them -- very bad idea */ + NuDataSource* pNewDataSource; + + Assert(pDataSource != NULL); + + if (Nu_DataSourceNew(&pNewDataSource) != kNuErrNone) + return NULL; + Assert(pNewDataSource != NULL); + + /* this gets most of it */ + memcpy(pNewDataSource, pDataSource, sizeof(*pNewDataSource)); + + /* copy anything we're sure to free up */ + if (pDataSource->sourceType == kNuDataSourceFromFile) { + Assert(pDataSource->fromFile.fp == NULL); /* does this matter? */ + pNewDataSource->fromFile.pathname = + strdup(pDataSource->fromFile.pathname); + } + + /* don't let the original free up the resources */ + if (pDataSource->common.doClose) { + DBUG(("--- clearing doClose on source-copy of data source\n")); + pDataSource->common.doClose = false; + } + + return pNewDataSource; +#endif +} + + +/* + * Free a data source structure, and any type-specific elements. + */ +NuError Nu_DataSourceFree(NuDataSource* pDataSource) +{ + if (pDataSource == NULL) + return kNuErrNone; + + Assert(pDataSource->common.refCount > 0); + if (pDataSource->common.refCount > 1) { + pDataSource->common.refCount--; + return kNuErrNone; + } + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Nu_Free(NULL, pDataSource->fromFile.pathnameUNI); + if (pDataSource->fromFile.fp != NULL) { + fclose(pDataSource->fromFile.fp); + pDataSource->fromFile.fp = NULL; + } + break; + case kNuDataSourceFromFP: + if (pDataSource->fromFP.fcloseFunc != NULL && + pDataSource->fromFP.fp != NULL) + { + (*pDataSource->fromFP.fcloseFunc)(NULL, pDataSource->fromFP.fp); + pDataSource->fromFP.fp = NULL; + } + break; + case kNuDataSourceFromBuffer: + if (pDataSource->fromBuffer.freeFunc != NULL) { + (*pDataSource->fromBuffer.freeFunc)(NULL, + (void*)pDataSource->fromBuffer.buffer); + pDataSource->fromBuffer.buffer = NULL; + } + break; + case kNuDataSourceUnknown: + break; + default: + Assert(0); + return kNuErrInternal; + } + + Nu_Free(NULL, pDataSource); + return kNuErrNone; +} + + +/* + * Create a data source for an unopened file. + */ +NuError Nu_DataSourceFile_New(NuThreadFormat threadFormat, uint32_t otherLen, + const UNICHAR* pathnameUNI, Boolean isFromRsrcFork, + NuDataSource** ppDataSource) +{ + NuError err; + + if (pathnameUNI == NULL || + !(isFromRsrcFork == true || isFromRsrcFork == false) || + ppDataSource == NULL) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromFile; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.rawCrc = 0; + (*ppDataSource)->common.dataLen = 0; /* to be filled in later */ + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->common.refCount = 1; + + (*ppDataSource)->fromFile.pathnameUNI = strdup(pathnameUNI); + (*ppDataSource)->fromFile.fromRsrcFork = isFromRsrcFork; + (*ppDataSource)->fromFile.fp = NULL; /* to be filled in later */ + +bail: + return err; +} + + +/* + * Create a data source for an open file at a specific offset. The FILE* + * must be seekable. + */ +NuError Nu_DataSourceFP_New(NuThreadFormat threadFormat, uint32_t otherLen, + FILE* fp, long offset, long length, NuCallback fcloseFunc, + NuDataSource** ppDataSource) +{ + NuError err; + + if (fp == NULL || offset < 0 || length < 0 || + ppDataSource == NULL) + { + return kNuErrInvalidArg; + } + + if (otherLen && otherLen < (uint32_t)length) { + DBUG(("--- rejecting FP len=%ld other=%ld\n", length, otherLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromFP; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.rawCrc = 0; + (*ppDataSource)->common.dataLen = length; + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->common.refCount = 1; + + (*ppDataSource)->fromFP.fp = fp; + (*ppDataSource)->fromFP.offset = offset; + (*ppDataSource)->fromFP.fcloseFunc = fcloseFunc; + +bail: + return err; +} + + +/* + * Create a data source for a buffer. + * + * We allow "buffer" to be NULL so long as "offset" and "length" are also + * NULL. This is useful for creating empty pre-sized buffers, such as + * blank comment fields. + */ +NuError Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, uint32_t otherLen, + const uint8_t* buffer, long offset, long length, NuCallback freeFunc, + NuDataSource** ppDataSource) +{ + NuError err; + + if (offset < 0 || length < 0 || ppDataSource == NULL) + return kNuErrInvalidArg; + if (buffer == NULL && (offset != 0 || length != 0)) + return kNuErrInvalidArg; + + if (buffer == NULL) { + DBUG(("+++ zeroing freeFunc for empty-buffer DataSource\n")); + freeFunc = NULL; + } + + if (otherLen && otherLen < (uint32_t)length) { + DBUG(("--- rejecting buffer len=%ld other=%ld\n", length, otherLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromBuffer; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.rawCrc = 0; + (*ppDataSource)->common.dataLen = length; + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->common.refCount = 1; + + (*ppDataSource)->fromBuffer.buffer = buffer; + (*ppDataSource)->fromBuffer.offset = offset; + (*ppDataSource)->fromBuffer.curOffset = offset; + (*ppDataSource)->fromBuffer.curDataLen = length; + (*ppDataSource)->fromBuffer.freeFunc = freeFunc; + +bail: + return err; +} + + +/* + * Get the type of a NuDataSource. + */ +NuDataSourceType Nu_DataSourceGetType(const NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + return pDataSource->sourceType; +} + +/* + * Get the threadFormat for a data source. + */ +NuThreadFormat Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + return pDataSource->common.threadFormat; +} + +/* + * Get "dataLen" from a dataSource. + */ +uint32_t Nu_DataSourceGetDataLen(const NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + + if (pDataSource->sourceType == kNuDataSourceFromFile) { + /* dataLen can only be valid if file has been opened */ + Assert(pDataSource->fromFile.fp != NULL); + } + + return pDataSource->common.dataLen; +} + +/* + * Get "otherLen" from a dataSource. + */ +uint32_t Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + return pDataSource->common.otherLen; +} + +/* + * Change the "otherLen" value. + */ +void Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen) +{ + Assert(pDataSource != NULL && otherLen > 0); + pDataSource->common.otherLen = otherLen; +} + + +/* + * Get the "raw CRC" value. + */ +uint16_t Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + return pDataSource->common.rawCrc; +} + +/* + * Set the "raw CRC" value. You would want to do this if the input was + * already-compressed data, and you wanted to propagate the thread CRC. + */ +void Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, uint16_t crc) +{ + Assert(pDataSource != NULL); + pDataSource->common.rawCrc = crc; +} + + +/* + * Prepare a data source for action. + */ +NuError Nu_DataSourcePrepareInput(NuArchive* pArchive, + NuDataSource* pDataSource) +{ + NuError err = kNuErrNone; + FILE* fileFp = NULL; + + /* + * Doesn't apply to buffer sources. + */ + if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromBuffer) + goto bail; + + /* + * FP sources can be used several times, so we need to seek them + * to the correct offset before we begin. + */ + if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromFP) { + err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset, + SEEK_SET); + goto bail; /* return this err */ + } + + /* + * We're adding from a file on disk. Open it. + */ + err = Nu_OpenInputFile(pArchive, + pDataSource->fromFile.pathnameUNI, + pDataSource->fromFile.fromRsrcFork, &fileFp); + BailError(err); + + Assert(fileFp != NULL); + pDataSource->fromFile.fp = fileFp; + err = Nu_GetFileLength(pArchive, fileFp, + (long*)&pDataSource->common.dataLen); + BailError(err); + + if (pDataSource->common.otherLen && + pDataSource->common.otherLen < pDataSource->common.dataLen) + { + DBUG(("--- Uh oh, looks like file len is too small for presized\n")); + } + +bail: + return err; +} + + +/* + * Un-prepare a data source. This really only affects "file" sources, and + * is only here so we don't end up with 200+ FILE* structures hanging around. + * If we don't do this, the first resource we're likely to run out of is + * file descriptors. + * + * It's not necessary to do this in all error cases -- the DataSource "Free" + * call will take care of this eventually -- but for normal operation on + * a large number of files, it's vital. + */ +void Nu_DataSourceUnPrepareInput(NuArchive* pArchive, NuDataSource* pDataSource) +{ + if (Nu_DataSourceGetType(pDataSource) != kNuDataSourceFromFile) + return; + + if (pDataSource->fromFile.fp != NULL) { + fclose(pDataSource->fromFile.fp); + pDataSource->fromFile.fp = NULL; + pDataSource->common.dataLen = 0; + } +} + + +/* + * Get the pathname from a "from-file" dataSource. Returned string is UTF-8. + */ +const char* Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource) +{ + Assert(pDataSource != NULL); + Assert(pDataSource->sourceType == kNuDataSourceFromFile); + Assert(pDataSource->fromFile.pathnameUNI != NULL); + + return pDataSource->fromFile.pathnameUNI; +} + + +/* + * Read a block of data from a dataSource. + */ +NuError Nu_DataSourceGetBlock(NuDataSource* pDataSource, uint8_t* buf, + uint32_t len) +{ + NuError err; + + Assert(pDataSource != NULL); + Assert(buf != NULL); + Assert(len > 0); + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Assert(pDataSource->fromFile.fp != NULL); + err = Nu_FRead(pDataSource->fromFile.fp, buf, len); + if (feof(pDataSource->fromFile.fp)) + Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly"); + return err; + + case kNuDataSourceFromFP: + err = Nu_FRead(pDataSource->fromFP.fp, buf, len); + if (feof(pDataSource->fromFP.fp)) + Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly"); + return err; + + case kNuDataSourceFromBuffer: + if ((long)len > pDataSource->fromBuffer.curDataLen) { + /* buffer underrun */ + return kNuErrBufferUnderrun; + } + memcpy(buf, + pDataSource->fromBuffer.buffer + pDataSource->fromBuffer.curOffset, + len); + pDataSource->fromBuffer.curOffset += len; + pDataSource->fromBuffer.curDataLen -= len; + return kNuErrNone; + + default: + Assert(false); + return kNuErrInternal; + } +} + + +/* + * Rewind a data source to the start of its input. + */ +NuError Nu_DataSourceRewind(NuDataSource* pDataSource) +{ + NuError err; + + Assert(pDataSource != NULL); + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Assert(pDataSource->fromFile.fp != NULL); + err = Nu_FSeek(pDataSource->fromFile.fp, 0, SEEK_SET); + break; /* fall through with error */ + case kNuDataSourceFromFP: + err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset, + SEEK_SET); + break; /* fall through with error */ + case kNuDataSourceFromBuffer: + pDataSource->fromBuffer.curOffset = pDataSource->fromBuffer.offset; + pDataSource->fromBuffer.curDataLen = pDataSource->common.dataLen; + err = kNuErrNone; + break; + default: + Assert(false); + err = kNuErrInternal; + } + + return err; +} + + +/* + * =========================================================================== + * NuDataSink + * =========================================================================== + */ + +/* + * Allocate a new DataSink structure. + */ +static NuError Nu_DataSinkNew(NuDataSink** ppDataSink) +{ + Assert(ppDataSink != NULL); + + *ppDataSink = Nu_Malloc(NULL, sizeof(**ppDataSink)); + if (*ppDataSink == NULL) + return kNuErrMalloc; + + (*ppDataSink)->sinkType = kNuDataSinkUnknown; + + return kNuErrNone; +} + + +/* + * Free a data sink structure, and any type-specific elements. + */ +NuError Nu_DataSinkFree(NuDataSink* pDataSink) +{ + if (pDataSink == NULL) + return kNuErrNone; + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + Nu_DataSinkFile_Close(pDataSink); + Nu_Free(NULL, pDataSink->toFile.pathnameUNI); + break; + case kNuDataSinkToFP: + break; + case kNuDataSinkToBuffer: + break; + case kNuDataSinkToVoid: + break; + case kNuDataSinkUnknown: + break; + default: + Assert(0); + return kNuErrInternal; + } + + Nu_Free(NULL, pDataSink); + return kNuErrNone; +} + + +/* + * Create a data sink for an unopened file. + */ +NuError Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, + const UNICHAR* pathnameUNI, UNICHAR fssep, NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + pathnameUNI == NULL || + fssep == 0 || + ppDataSink == NULL) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToFile; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toFile.pathnameUNI = strdup(pathnameUNI); + (*ppDataSink)->toFile.fssep = fssep; + + (*ppDataSink)->toFile.fp = NULL; + +bail: + return err; +} + + +/* + * Create a data sink based on a file pointer. + */ +NuError Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp, + NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + fp == NULL || + ppDataSink == NULL) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToFP; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toFP.fp = fp; + +bail: + return err; +} + + +/* + * Create a data sink for a buffer in memory. + */ +NuError Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL, + uint8_t* buffer, uint32_t bufLen, NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + buffer == NULL || + bufLen == 0 || + ppDataSink == NULL) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToBuffer; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.convertEOL = convertEOL; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toBuffer.buffer = buffer; + (*ppDataSink)->toBuffer.bufLen = bufLen; + (*ppDataSink)->toBuffer.stickyErr = kNuErrNone; + +bail: + return err; +} + + +/* + * Create a data sink that goes nowhere. + */ +NuError Nu_DataSinkVoid_New(Boolean doExpand, NuValue convertEOL, + NuDataSink** ppDataSink) +{ + NuError err; + + Assert(doExpand == true || doExpand == false); + Assert(ppDataSink != NULL); + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToVoid; + (*ppDataSink)->common.doExpand = doExpand; + (*ppDataSink)->common.convertEOL = convertEOL; + (*ppDataSink)->common.outCount = 0; + +bail: + return err; +} + + +/* + * Get the type of a NuDataSink. + */ +NuDataSinkType Nu_DataSinkGetType(const NuDataSink* pDataSink) +{ + Assert(pDataSink != NULL); + return pDataSink->sinkType; +} + + +/* + * Return the "doExpand" parameter from any kind of sink. + */ +Boolean Nu_DataSinkGetDoExpand(const NuDataSink* pDataSink) +{ + return pDataSink->common.doExpand; +} + +/* + * Return the "convertEOL" parameter from any kind of sink. + */ +NuValue Nu_DataSinkGetConvertEOL(const NuDataSink* pDataSink) +{ + return pDataSink->common.convertEOL; +} + +/* + * Return the #of bytes written to the sink. + */ +uint32_t Nu_DataSinkGetOutCount(const NuDataSink* pDataSink) +{ + return pDataSink->common.outCount; +} + + +/* + * Get "pathname" from a to-file sink. Returned string is UTF-8. + */ +const char* Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink) +{ + Assert(pDataSink != NULL); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.pathnameUNI; +} + +/* + * Get "fssep" from a to-file sink. + */ +UNICHAR Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink) +{ + Assert(pDataSink != NULL); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.fssep; +} + +/* + * Get the "fp" for a file sink. + */ +FILE* Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink) +{ + Assert(pDataSink != NULL); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.fp; +} + +/* + * Set the "fp" for a file sink. + */ +void Nu_DataSinkFile_SetFP(NuDataSink* pDataSink, FILE* fp) +{ + Assert(pDataSink != NULL); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + pDataSink->toFile.fp = fp; +} + +/* + * Close a to-file sink. + */ +void Nu_DataSinkFile_Close(NuDataSink* pDataSink) +{ + Assert(pDataSink != NULL); + + if (pDataSink->toFile.fp != NULL) { + fclose(pDataSink->toFile.fp); + pDataSink->toFile.fp = NULL; + } +} + + +/* + * Write a block of data to a DataSink. + */ +NuError Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uint8_t* buf, + uint32_t len) +{ + NuError err; + + Assert(pDataSink != NULL); + Assert(buf != NULL); + Assert(len > 0); + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + Assert(pDataSink->toFile.fp != NULL); + err = Nu_FWrite(pDataSink->toFile.fp, buf, len); + if (err != kNuErrNone) + return err; + break; + case kNuDataSinkToFP: + Assert(pDataSink->toFP.fp != NULL); + err = Nu_FWrite(pDataSink->toFP.fp, buf, len); + if (err != kNuErrNone) + return err; + break; + case kNuDataSinkToBuffer: + if (len > pDataSink->toBuffer.bufLen) { + /* buffer overrun; set a "sticky" error, like FILE* does */ + err = kNuErrBufferOverrun; + pDataSink->toBuffer.stickyErr = err; + return err; + } + memcpy(pDataSink->toBuffer.buffer, buf, len); + pDataSink->toBuffer.buffer += len; + pDataSink->toBuffer.bufLen -= len; + break; + case kNuDataSinkToVoid: + /* do nothing */ + break; + default: + Assert(false); + return kNuErrInternal; + } + pDataSink->common.outCount += len; + return kNuErrNone; +} + + +/* + * Figure out if one of our earlier writes has failed. + */ +NuError Nu_DataSinkGetError(NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + + Assert(pDataSink != NULL); + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + if (ferror(pDataSink->toFile.fp)) + err = kNuErrFileWrite; + break; + case kNuDataSinkToFP: + if (ferror(pDataSink->toFP.fp)) + err = kNuErrFileWrite; + break; + case kNuDataSinkToBuffer: + err = pDataSink->toBuffer.stickyErr; + break; + case kNuDataSinkToVoid: + /* do nothing */ + break; + default: + Assert(false); + err = kNuErrInternal; + break; + } + + return err; +} + diff --git a/nufxlib/Squeeze.c b/nufxlib/Squeeze.c new file mode 100644 index 0000000..a4c339f --- /dev/null +++ b/nufxlib/Squeeze.c @@ -0,0 +1,1131 @@ +/* + * 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. + * + * Huffman/RLE "squeeze" compression, based on SQ/USQ. This format is + * listed in the NuFX documentation, but to my knowledge has never + * actually been used (until now). Neither P8 ShrinkIt v3.4 nor II Unshrink + * handle the format correctly, so this is really only useful as an + * experiment. + * + * The algorithm appears to date back to the CP/M days. This implementation + * is based on "xsq"/"xusq" v1.7u by Richard Greenlaw (from December 1982). + * The code was also present in ARC v5.x. + * + * The "nusq.c" implementation found in NuLib was by Marcel J.E. Mol, + * who got it from Don Elton's sq3/usq2 programs for the Apple II. + * + * The SQ file format begins with this: + * +00 magic number (0xff76) + * +02 checksum on uncompressed data + * +04 filename, ending with \0 + * The NuFX format skips the above, starting immediately after it: + * +00 node count + * +02 node value array [node count], two bytes each + * +xx data immediately follows array + * + * NuFX drops the magic number, checksum, and filename from the header, + * since (with v3 records) all three are redundant. You can enable this + * if you want to experiment with SQ-compatible output. + */ +#include "NufxLibPriv.h" + +#ifdef ENABLE_SQ + +/* if this is defined, create and unpack the full SQ header (debugging only) */ +/* #define FULL_SQ_HEADER */ + + +#define kNuSQMagic 0xff76 /* magic value for file header */ +#define kNuSQRLEDelim 0x90 /* RLE delimiter */ +#define kNuSQEOFToken 256 /* distinguished stop symbol */ +#define kNuSQNumVals 257 /* 256 symbols + stop */ + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +#define kNuSQNoChild (-1) /* indicates end of path through tree */ +#define kNuSQNumNodes (kNuSQNumVals + kNuSQNumVals -1) +#define kNuSQMaxCount 65535 /* max value you can store in 16 bits */ + +/* states for the RLE encoding */ +typedef enum { + kNuSQRLEStateUnknown = 0, + + kNuSQRLEStateNoHist, /* nothing yet */ + kNuSQRLEStateSentChar, /* lastchar set, no lookahead yet */ + kNuSQRLEStateSendNewC, /* found run of two, send 2nd w/o DLE */ + kNuSQRLEStateSendCnt, /* newchar set, DLE sent, send count next */ +} NuSQRLEState; + +/* nodes in the Huffman encoding tree */ +typedef struct EncTreeNode { + int weight; /* #of appearances */ + int tdepth; /* length on longest path in tree */ + int lchild, rchild; /* indexes to next level */ +} EncTreeNode; + +/* + * State during compression. + */ +typedef struct SQState { + NuArchive* pArchive; + int doCalcCRC; /* boolean; if set, compute CRC on input */ + uint16_t crc; + + NuStraw* pStraw; + long uncompRemaining; + + #ifdef FULL_SQ_HEADER + uint16_t checksum; + #endif + + /* + * RLE state stuff. + */ + NuSQRLEState rleState; + int lastSym; + int likeCount; + + /* + * Huffman state stuff. + */ + EncTreeNode node[kNuSQNumNodes]; + + int treeHead; /* index to head node of final tree */ + + /* encoding table */ + int codeLen[kNuSQNumVals]; /* number of bits in code for symbol N */ + uint16_t code[kNuSQNumVals]; /* bits for symbol N (first bit in lsb) */ + uint16_t tmpCode; /* temporary code value */ +} SQState; + + +/* + * Get the next byte from the input straw. Also updates the checksum + * and SQ CRC, if "doCalcCRC" is set to true. + * + * This isn't exactly fast, but then this isn't exactly a fast algorithm, + * and there's not much point in optimizing something that isn't going + * to get used much. + * + * Returns kNuSQEOFToken as the value when we're out of data. + */ +static NuError Nu_SQGetcCRC(SQState* pSqState, int* pSym) +{ + NuError err; + uint8_t c; + + if (!pSqState->uncompRemaining) { + *pSym = kNuSQEOFToken; + return kNuErrNone; + } + + err = Nu_StrawRead(pSqState->pArchive, pSqState->pStraw, &c, 1); + if (err == kNuErrNone) { + if (pSqState->doCalcCRC) { + #ifdef FULL_SQ_HEADER + pSqState->checksum += c; + #endif + pSqState->crc = Nu_CalcCRC16(pSqState->crc, &c, 1); + } + *pSym = c; + pSqState->uncompRemaining--; + } + + return err; +} + +/* + * Get the next byte from the post-RLE input stream. + * + * Returns kNuSQEOFToken in "*pSum" when we reach the end of the input. + */ +static NuError Nu_SQGetcRLE(SQState* pSqState, int* pSym) +{ + NuError err = kNuErrNone; + int likeCount, newSym; + + switch (pSqState->rleState) { + case kNuSQRLEStateNoHist: + /* No relevant history */ + pSqState->rleState = kNuSQRLEStateSentChar; + err = Nu_SQGetcCRC(pSqState, pSym); + pSqState->lastSym = *pSym; + break; + + case kNuSQRLEStateSentChar: + /* lastChar is set, need lookahead */ + switch (pSqState->lastSym) { + case kNuSQRLEDelim: + /* send all DLEs escaped; note this is horrible for a run of DLEs */ + pSqState->rleState = kNuSQRLEStateNoHist; + *pSym = 0; /* zero len is how we define an escaped DLE */ + break; + case kNuSQEOFToken: + *pSym = kNuSQEOFToken; + break; + default: + /* + * Try for a run, using the character we previous read as + * the base. Thus, if the next character we read matches, + * we have a run of two. The count describes the total + * length of the run, including the character we've already + * emitted. + */ + likeCount = 0; + do { + likeCount++; + err = Nu_SQGetcCRC(pSqState, &newSym); + if (err != kNuErrNone) + goto bail; + } while (newSym == pSqState->lastSym && likeCount < 255); + + switch (likeCount) { + case 1: + /* not a run, return first one we got */ + pSqState->lastSym = newSym; + *pSym = newSym; + break; + case 2: + /* not long enough for run; return second one next time thru */ + pSqState->rleState = kNuSQRLEStateSendNewC; + *pSym = pSqState->lastSym; /* 1st new one */ + pSqState->lastSym = newSym; /* 2nd new one */ + break; + default: + pSqState->rleState = kNuSQRLEStateSendCnt; + pSqState->likeCount = likeCount; + pSqState->lastSym = newSym; /* 1st one after the run */ + *pSym = kNuSQRLEDelim; + break; + } + } + break; + + case kNuSQRLEStateSendNewC: + /* send first char past a run of two */ + pSqState->rleState = kNuSQRLEStateSentChar; + *pSym = pSqState->lastSym; + break; + + case kNuSQRLEStateSendCnt: + /* Sent DLE for repeat sequence, send count */ + pSqState->rleState = kNuSQRLEStateSendNewC; + *pSym = pSqState->likeCount; + break; + + default: + { + NuArchive* pArchive = pSqState->pArchive; + + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, err, "invalid state %d in SQ RLE encode", + pSqState->rleState); + break; + } + } + +bail: + return err; +} + + +/* + * Comment from xsq.c: + * + * This translation uses the Huffman algorithm to develop a + * binary tree representing the decoding information for + * a variable length bit string code for each input value. + * Each string's length is in inverse proportion to its + * frequency of appearance in the incoming data stream. + * The encoding table is derived from the decoding table. + * + * The range of valid values into the Huffman algorithm are + * the values of a byte stored in an integer plus the special + * endfile value chosen to be an adjacent value. Overall, 0-SPEOF. + * + * The "node" array of structures contains the nodes of the + * binary tree. The first NUMVALS nodes are the leaves of the + * tree and represent the values of the data bytes being + * encoded and the special endfile, SPEOF. + * The remaining nodes become the internal nodes of the tree. + * + * In the original design it was believed that + * a Huffman code would fit in the same number of + * bits that will hold the sum of all the counts. + * That was disproven by a user's file and was a rare but + * infamous bug. This version attempts to choose among equally + * weighted subtrees according to their maximum depths to avoid + * unnecessarily long codes. In case that is not sufficient + * to guarantee codes <= 16 bits long, we initially scale + * the counts so the total fits in an unsigned integer, but + * if codes longer than 16 bits are generated the counts are + * rescaled to a lower ceiling and code generation is retried. + */ + +/* + * Return the greater of two integers. + */ +static int Nu_SQMax(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +/* + * Compare two trees, if a > b return true, else return false. + * Priority is given to weight, then depth. "a" and "b" are heaps, + * so we only need to look at the root element. + */ +static int Nu_SQCmpTrees(SQState* pSqState, int a, int b) +{ + if (pSqState->node[a].weight > pSqState->node[b].weight) + return true; + if (pSqState->node[a].weight == pSqState->node[b].weight) + if (pSqState->node[a].tdepth > pSqState->node[b].tdepth) + return true; + return false; +} + +/* + * heap() and adjust() maintain a list of binary trees as a + * heap with the top indexing the binary tree on the list + * which has the least weight or, in case of equal weights, + * least depth in its longest path. The depth part is not + * strictly necessary, but tends to avoid long codes which + * might provoke rescaling. + */ + +/* + * Recursively make a heap from a heap with a new top. + */ +static void Nu_SQHeapAdjust(SQState* pSqState, int list[], int top, int bottom) +{ + int k, temp; + + k = 2 * top + 1; /* left child of top */ + temp = list[top]; /* remember root node of top tree */ + if (k <= bottom) { + if (k < bottom && Nu_SQCmpTrees(pSqState, list[k], list[k + 1])) + k++; + + /* k indexes "smaller" child (in heap of trees) of top */ + /* now make top index "smaller" of old top and smallest child */ + if (Nu_SQCmpTrees(pSqState, temp, list[k])) { + list[top] = list[k]; + list[k] = temp; + /* Make the changed list a heap */ + Nu_SQHeapAdjust(pSqState, list, k, bottom); /*recursive*/ + } + } +} + +/* + * Create a heap. + */ +static void Nu_SQHeap(SQState* pSqState, int list[], int length) +{ + int i; + + for (i = (length - 2) / 2; i >= 0; i--) + Nu_SQHeapAdjust(pSqState, list, i, length - 1); +} + + +/* + * Build the encoding tree. + * + * HUFFMAN ALGORITHM: develops the single element trees + * into a single binary tree by forming subtrees rooted in + * interior nodes having weights equal to the sum of weights of all + * their descendents and having depth counts indicating the + * depth of their longest paths. + * + * When all trees have been formed into a single tree satisfying + * the heap property (on weight, with depth as a tie breaker) + * then the binary code assigned to a leaf (value to be encoded) + * is then the series of left (0) and right (1) + * paths leading from the root to the leaf. + * Note that trees are removed from the heaped list by + * moving the last element over the top element and + * reheaping the shorter list. + */ +static void Nu_SQBuildTree(SQState* pSqState, int list[], int len) +{ + int freenode; /* next free node in tree */ + EncTreeNode* frnp; /* free node pointer */ + int lch, rch; /* temporaries for left, right children */ + + /* + * Initialize index to next available (non-leaf) node. + * Lower numbered nodes correspond to leaves (data values). + */ + freenode = kNuSQNumVals; + + while (len > 1) { + /* + * Take from list two btrees with least weight + * and build an interior node pointing to them. + * This forms a new tree. + */ + lch = list[0]; /* This one will be left child */ + + /* delete top (least) tree from the list of trees */ + list[0] = list[--len]; + Nu_SQHeapAdjust(pSqState, list, 0, len - 1); + + /* Take new top (least) tree. Reuse list slot later */ + rch = list[0]; /* This one will be right child */ + + /* + * Form new tree from the two least trees using + * a free node as root. Put the new tree in the list. + */ + frnp = &pSqState->node[freenode]; /* address of next free node */ + list[0] = freenode++; /* put at top for now */ + frnp->lchild = lch; + frnp->rchild = rch; + frnp->weight = + pSqState->node[lch].weight + pSqState->node[rch].weight; + frnp->tdepth = 1 + Nu_SQMax(pSqState->node[lch].tdepth, + pSqState->node[rch].tdepth); + + /* reheap list to get least tree at top*/ + Nu_SQHeapAdjust(pSqState, list, 0, len - 1); + } + + pSqState->treeHead = list[0]; /* head of final tree */ +} + + +/* + * Recursive routine to walk the indicated subtree and level + * and maintain the current path code in bstree. When a leaf + * is found the entire code string and length are put into + * the encoding table entry for the leaf's data value . + * + * Returns zero on success, nonzero if codes are too long. + */ +static int Nu_SQBuildEncTable(SQState* pSqState, int level, int root) +{ + int l, r; + + l = pSqState->node[root].lchild; + r = pSqState->node[root].rchild; + + if (l == kNuSQNoChild && r == kNuSQNoChild) { + /* Leaf. Previous path determines bit string + * code of length level (bits 0 to level - 1). + * Ensures unused code bits are zero. + */ + pSqState->codeLen[root] = level; + pSqState->code[root] = + pSqState->tmpCode & (((uint16_t)~0) >> (16 - level)); + return (level > 16) ? -1 : 0; + } else { + if (l != kNuSQNoChild) { + /* Clear path bit and continue deeper */ + pSqState->tmpCode &= ~(1 << level); + /* NOTE RECURSION */ + if (Nu_SQBuildEncTable(pSqState, level + 1, l) != 0) + return -1; + } + if (r != kNuSQNoChild) { + /* Set path bit and continue deeper */ + pSqState->tmpCode |= 1 << level; + /* NOTE RECURSION */ + if (Nu_SQBuildEncTable(pSqState, level + 1, r) != 0) + return -1; + } + } + + return 0; /* if we got here we're ok so far */ +} + + +/* + * The count of number of occurrances of each input value + * have already been prevented from exceeding MAXCOUNT. + * Now we must scale them so that their sum doesn't exceed + * ceiling and yet no non-zero count can become zero. + * This scaling prevents errors in the weights of the + * interior nodes of the Huffman tree and also ensures that + * the codes will fit in an unsigned integer. Rescaling is + * used if necessary to limit the code length. + */ +static void Nu_SQScale(SQState* pSqState, int ceiling) +{ + int i; + int wt, ovflw, divisor; + uint16_t sum; + int increased; /* flag */ + + do { + for (i = sum = ovflw = 0; i < kNuSQNumVals; i++) { + if (pSqState->node[i].weight > (ceiling - sum)) + ovflw++; + sum += pSqState->node[i].weight; + } + + divisor = ovflw + 1; /* use the high 16 bits of the sum */ + + /* Ensure no non-zero values are lost */ + increased = false; + for (i = 0; i < kNuSQNumVals; i++) { + wt = pSqState->node[i].weight; + if (wt < divisor && wt != 0) { + /* Don't fail to provide a code if it's used at all */ + pSqState->node[i].weight = divisor; + increased = true; + } + } + } while(increased); + + /* scaling factor choosen and minimums are set; now do the downscale */ + if (divisor > 1) { + for (i = 0; i < kNuSQNumVals; i++) + pSqState->node[i].weight /= divisor; + } +} + +/* + * Build a frequency table from the post-RLE input stream, then generate + * an encoding tree from the results. + */ +static NuError Nu_SQComputeHuffTree(SQState* pSqState) +{ + NuError err = kNuErrNone; + int btreeList[kNuSQNumVals]; /* list of intermediate binary trees */ + int listLen; /* length of btreeList */ + int ceiling; /* limit for scaling */ + int i, sym, result; + + /* init tree */ + for (i = 0; i < kNuSQNumNodes; i++) { + pSqState->node[i].weight = 0; + pSqState->node[i].tdepth = 0; + pSqState->node[i].lchild = kNuSQNoChild; + pSqState->node[i].rchild = kNuSQNoChild; + } + + DBUG(("+++ SQ scanning...\n")); + + do { + int* pWeight; + + err = Nu_SQGetcRLE(pSqState, &sym); + if (err != kNuErrNone) + goto bail; + + Assert(sym >= 0 && sym <= kNuSQEOFToken); + pWeight = &pSqState->node[(unsigned)sym].weight; + if (*pWeight != kNuSQMaxCount) + (*pWeight)++; + } while (sym != kNuSQEOFToken); + + DBUG(("+++ SQ generating tree...\n")); + + ceiling = kNuSQMaxCount; + + do { + if (ceiling != kNuSQMaxCount) { + DBUG(("+++ SQ rescaling\n")); + } + + /* pick a divisor and scale everything to fit in "ceiling" */ + Nu_SQScale(pSqState, ceiling); + + ceiling /= 2; /* in case we need to rescale */ + + /* + * Build list of single node binary trees having + * leaves for the input values with non-zero counts + */ + for (i = listLen = 0; i < kNuSQNumVals; i++) { + if (pSqState->node[i].weight != 0) { + pSqState->node[i].tdepth = 0; + btreeList[listLen++] = i; + } + } + + /* + * Arrange list of trees into a heap with the entry + * indexing the node with the least weight a the top. + */ + Nu_SQHeap(pSqState, btreeList, listLen); + + /* convert the list of trees to a single decoding tree */ + Nu_SQBuildTree(pSqState, btreeList, listLen); + + /* initialize encoding table */ + for (i = 0; i < kNuSQNumVals; i++) + pSqState->codeLen[i] = 0; + + /* + * Recursively build the encoding table; returns non-zero (failure) + * if any code is > 16 bits long. + */ + result = Nu_SQBuildEncTable(pSqState, 0, pSqState->treeHead); + } while (result != 0); + +#if 0 +{ + int jj; + printf("init_huff\n"); + for (jj = 0; jj < kNuSQNumNodes; jj++) { + printf("NODE %d: w=%d d=%d l=%d r=%d\n", jj, + pSqState->node[jj].weight, + pSqState->node[jj].tdepth, + pSqState->node[jj].lchild, + pSqState->node[jj].rchild); + } +} +#endif + +bail: + return err; +} + + +/* + * Compress data from input to output, using the values in the "code" + * and "codeLen" arrays. + */ +static NuError Nu_SQCompressInput(SQState* pSqState, FILE* fp, + long* pCompressedLen) +{ + NuError err = kNuErrNone; + int sym = kNuSQEOFToken-1; + uint32_t bits, code; /* must hold at least 23 bits */ + int codeLen, gotbits; + long compressedLen; + + DBUG(("+++ SQ compressing\n")); + + Assert(sizeof(bits) >= 4); + compressedLen = *pCompressedLen; + + bits = 0; + gotbits = 0; + while (sym != kNuSQEOFToken) { + err = Nu_SQGetcRLE(pSqState, &sym); + if (err != kNuErrNone) + goto bail; + + code = pSqState->code[sym]; + codeLen = pSqState->codeLen[sym]; + + bits |= code << gotbits; + gotbits += codeLen; + + /* if we have more than a byte, output it */ + while (gotbits > 7) { + putc(bits & 0xff, fp); + compressedLen++; + bits >>= 8; + gotbits -= 8; + } + } + + if (gotbits) { + Assert(gotbits < 8); + putc(bits & 0xff, fp); + compressedLen++; + } + +bail: + *pCompressedLen = compressedLen; + return err; +} + + +/* + * Write a 16-bit value in little-endian order. + */ +static NuError Nu_SQWriteShort(FILE* outfp, short val) +{ + NuError err; + uint8_t tmpc; + + tmpc = val & 0xff; + err = Nu_FWrite(outfp, &tmpc, 1); + if (err != kNuErrNone) + goto bail; + tmpc = (val >> 8) & 0xff; + err = Nu_FWrite(outfp, &tmpc, 1); + if (err != kNuErrNone) + goto bail; + +bail: + return err; +} + +/* + * Compress "srcLen" bytes into SQ format, from "pStraw" to "fp". + * + * This requires two passes through the input. + * + * Bit of trivia: "sq3" on the Apple II self-destructs if you hand + * it an empty file. "xsq" works fine, creating an empty tree that + * "xusq" unpacks. + */ +NuError Nu_CompressHuffmanSQ(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + SQState sqState; + long compressedLen; + int i, j, numNodes; + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + sqState.pArchive = pArchive; + sqState.crc = 0; + if (pCrc == NULL) { + sqState.doCalcCRC = false; + } else { + sqState.doCalcCRC = true; + sqState.crc = *pCrc; + } + + #ifdef FULL_SQ_HEADER + sqState.checksum = 0; + #endif + + /* + * Pass 1: analysis. Perform a frequency analysis on the post-RLE + * input file. This will calculate the file CRCs as a side effect. + */ + sqState.rleState = kNuSQRLEStateNoHist; + sqState.uncompRemaining = srcLen; + sqState.pStraw = pStraw; + (void) Nu_StrawSetProgressState(pStraw, kNuProgressAnalyzing); + + err = Nu_SQComputeHuffTree(&sqState); + BailError(err); + + if (pCrc != NULL) + *pCrc = sqState.crc; + + /* + * Pass 2: compression. Using the encoding tree we computed, + * compress the input with RLE and Huffman. Start by writing + * the file header and rewinding the input file. + */ + sqState.doCalcCRC = false; /* don't need to re-compute */ + sqState.rleState = kNuSQRLEStateNoHist; /* reset */ + compressedLen = 0; + + /* rewind for next pass */ + (void) Nu_StrawSetProgressState(pStraw, kNuProgressCompressing); + err = Nu_StrawRewind(pArchive, pStraw); + BailError(err); + sqState.uncompRemaining = srcLen; + + #ifdef FULL_SQ_HEADER + /* write file header */ + err = Nu_SQWriteShort(fp, kNuSQMagic); + BailError(err); + compressedLen += 2; + + err = Nu_SQWriteShort(fp, sqState.checksum); + BailError(err); + compressedLen += 2; + + { + static const char fakename[] = "s.qqq"; + err = Nu_FWrite(fp, fakename, sizeof(fakename)); + BailError(err); + compressedLen += sizeof(fakename); + } + #endif + + /* + * Original description: + * Write out a simplified decoding tree. Only the interior + * nodes are written. When a child is a leaf index + * (representing a data value) it is recoded as + * -(index + 1) to distinguish it from interior indexes + * which are recoded as positive indexes in the new tree. + * Note that this tree will be empty for an empty file. + */ + if (sqState.treeHead < kNuSQNumVals) + numNodes = 0; + else + numNodes = sqState.treeHead - (kNuSQNumVals - 1); + err = Nu_SQWriteShort(fp, (short) numNodes); + BailError(err); + compressedLen += 2; + + for (i = sqState.treeHead, j = 0; j < numNodes; j++, i--) { + int l, r; + + l = sqState.node[i].lchild; + r = sqState.node[i].rchild; + l = l < kNuSQNumVals ? -(l + 1) : sqState.treeHead - l; + r = r < kNuSQNumVals ? -(r + 1) : sqState.treeHead - r; + err = Nu_SQWriteShort(fp, (short) l); + BailError(err); + err = Nu_SQWriteShort(fp, (short) r); + BailError(err); + compressedLen += 4; + + /*DBUG(("TREE %d: %d %d\n", j, l, r));*/ + } + + /* + * Convert the input to RLE/Huffman. + */ + err = Nu_SQCompressInput(&sqState, fp, &compressedLen); + BailError(err); + + /* + * Done! + */ + *pDstLen = compressedLen; + +bail: + return err; +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* + * State during uncompression. + */ +typedef struct USQState { + uint32_t dataInBuffer; + uint8_t* dataPtr; + int bitPosn; + int bits; + + /* + * Decoding tree; first "nodeCount" values are populated. Positive + * values are indicies to another node in the tree, negative values + * are literals (+1 because "negative zero" doesn't work well). + */ + int nodeCount; + struct { + short child[2]; /* left/right kids, must be signed 16-bit */ + } decTree[kNuSQNumVals-1]; +} USQState; + + +/* + * Decode the next symbol from the Huffman stream. + */ +static NuError Nu_USQDecodeHuffSymbol(USQState* pUsqState, int* pVal) +{ + short val = 0; + int bits, bitPosn; + + bits = pUsqState->bits; /* local copy */ + bitPosn = pUsqState->bitPosn; + + do { + if (++bitPosn > 7) { + /* grab the next byte and use that */ + bits = *pUsqState->dataPtr++; + bitPosn = 0; + if (!pUsqState->dataInBuffer--) + return kNuErrBufferUnderrun; + + val = pUsqState->decTree[val].child[1 & bits]; + } else { + /* still got bits; shift right and use it */ + val = pUsqState->decTree[val].child[1 & (bits >>= 1)]; + } + } while (val >= 0); + + /* val is negative literal; add one to make it zero-based then negate it */ + *pVal = -(val + 1); + + pUsqState->bits = bits; + pUsqState->bitPosn = bitPosn; + + return kNuErrNone; +} + + +/* + * Read two bytes of signed data out of the buffer. + */ +static inline NuError Nu_USQReadShort(USQState* pUsqState, short* pShort) +{ + if (pUsqState->dataInBuffer < 2) + return kNuErrBufferUnderrun; + + *pShort = *pUsqState->dataPtr++; + *pShort |= (*pUsqState->dataPtr++) << 8; + pUsqState->dataInBuffer -= 2; + + return kNuErrNone; +} + +/* + * Expand "SQ" format. + * + * Because we have a stop symbol, knowing the uncompressed length of + * the file is not essential. + */ +NuError Nu_ExpandHuffmanSQ(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc) +{ + NuError err = kNuErrNone; + USQState usqState; + uint32_t compRemaining, getSize; +#ifdef FULL_SQ_HEADER + uint16_t magic, fileChecksum, checksum; +#endif + short nodeCount; + int i, inrep; + uint8_t lastc = 0; + + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + Assert(pArchive->compBuf != NULL); + + usqState.dataInBuffer = 0; + usqState.dataPtr = pArchive->compBuf; + usqState.bits = usqState.bitPosn = 0; + + compRemaining = pThread->thCompThreadEOF; +#ifdef FULL_SQ_HEADER + if (compRemaining < 8) +#else + if (compRemaining < 3) +#endif + { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "thread too short to be valid SQ data"); + goto bail; + } + + getSize = compRemaining; + if (getSize > kNuGenCompBufSize) + getSize = kNuGenCompBufSize; + + /* + * Grab a big chunk. "compRemaining" is the amount of compressed + * data left in the file, usqState.dataInBuffer is the amount of + * compressed data left in the buffer. + */ + err = Nu_FRead(infp, usqState.dataPtr, getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, + "failed reading compressed data (%u bytes)", getSize); + goto bail; + } + usqState.dataInBuffer += getSize; + compRemaining -= getSize; + + /* + * Read the header. We assume that the header is less than + * kNuGenCompBufSize bytes, which is pretty fair since the buffer is + * currently 20x larger than the longest possible header (sq allowed + * 300+ for the filename, plus 257*2 for the tree, plus misc). + */ + Assert(kNuGenCompBufSize > 1200); +#ifdef FULL_SQ_HEADER + err = Nu_USQReadShort(&usqState, &magic); + BailError(err); + if (magic != kNuSQMagic) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "bad magic number in SQ block"); + goto bail; + } + + err = Nu_USQReadShort(&usqState, &fileChecksum); + BailError(err); + + checksum = 0; + + while (*usqState.dataPtr++ != '\0') + usqState.dataInBuffer--; + usqState.dataInBuffer--; +#endif + + err = Nu_USQReadShort(&usqState, &nodeCount); + BailError(err); + if (nodeCount < 0 || nodeCount >= kNuSQNumVals) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "invalid decode tree in SQ (%d nodes)", + nodeCount); + goto bail; + } + usqState.nodeCount = nodeCount; + + /* initialize for possibly empty tree (only happens on an empty file) */ + usqState.decTree[0].child[0] = -(kNuSQEOFToken+1); + usqState.decTree[0].child[1] = -(kNuSQEOFToken+1); + + /* read the nodes, ignoring "read errors" until we're done */ + for (i = 0; i < nodeCount; i++) { + err = Nu_USQReadShort(&usqState, &usqState.decTree[i].child[0]); + err = Nu_USQReadShort(&usqState, &usqState.decTree[i].child[1]); + } + if (err != kNuErrNone) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "SQ data looks truncated at tree"); + goto bail; + } + + usqState.bitPosn = 99; /* force an immediate read */ + + /* + * Start pulling data out of the file. We have to Huffman-decode + * the input, and then feed that into an RLE expander. + * + * A completely lopsided (and broken) Huffman tree could require + * 256 tree descents, so we want to try to ensure we have at least 256 + * bits in the buffer. Otherwise, we could get a false buffer underrun + * indication back from DecodeHuffSymbol. + * + * The SQ sources actually guarantee that a code will fit entirely + * in 16 bits, but there's no reason not to use the larger value. + */ + inrep = false; + while (1) { + int val; + + if (usqState.dataInBuffer < 65 && compRemaining) { + /* + * Less than 256 bits, but there's more in the file. + * + * First thing we do is slide the old data to the start of + * the buffer. + */ + if (usqState.dataInBuffer) { + Assert(pArchive->compBuf != usqState.dataPtr); + memmove(pArchive->compBuf, usqState.dataPtr, + usqState.dataInBuffer); + } + usqState.dataPtr = pArchive->compBuf; + + /* + * Next we read as much as we can. + */ + if (kNuGenCompBufSize - usqState.dataInBuffer < compRemaining) + getSize = kNuGenCompBufSize - usqState.dataInBuffer; + else + getSize = compRemaining; + + err = Nu_FRead(infp, usqState.dataPtr + usqState.dataInBuffer, + getSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, + "failed reading compressed data (%u bytes)", getSize); + goto bail; + } + usqState.dataInBuffer += getSize; + compRemaining -= getSize; + + Assert(compRemaining < 32767*65536); + Assert(usqState.dataInBuffer <= kNuGenCompBufSize); + } + + err = Nu_USQDecodeHuffSymbol(&usqState, &val); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "failed decoding huff symbol"); + goto bail; + } + + if (val == kNuSQEOFToken) + break; + + /* + * Feed the symbol into the RLE decoder. + */ + if (inrep) { + /* + * Last char was RLE delim, handle this specially. We use + * --val instead of val-- because we already emitted the + * first occurrence of the char (right before the RLE delim). + */ + if (val == 0) { + /* special case -- just an escaped RLE delim */ + lastc = kNuSQRLEDelim; + val = 2; + } + while (--val) { + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1); + err = Nu_FunnelWrite(pArchive, pFunnel, &lastc, 1); + #ifdef FULL_SQ_HEADER + checksum += lastc; + #endif + } + inrep = false; + } else { + /* last char was ordinary */ + if (val == kNuSQRLEDelim) { + /* set a flag and catch the count the next time around */ + inrep = true; + } else { + lastc = val; + if (pCrc != NULL) + *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1); + err = Nu_FunnelWrite(pArchive, pFunnel, &lastc, 1); + #ifdef FULL_SQ_HEADER + checksum += lastc; + #endif + } + } + + } + + if (inrep) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "got stop symbol when run length expected"); + goto bail; + } + + #ifdef FULL_SQ_HEADER + /* verify the checksum stored in the SQ file */ + if (checksum != fileChecksum && !pArchive->valIgnoreCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadDataCRC)) { + err = kNuErrBadDataCRC; + Nu_ReportError(NU_BLOB, err, "expected 0x%04x, got 0x%04x (SQ)", + fileChecksum, checksum); + (void) Nu_FunnelFlush(pArchive, pFunnel); + goto bail; + } + } else { + DBUG(("--- SQ checksums match (0x%04x)\n", checksum)); + } + #endif + + /* + * SQ2 adds an extra 0xff to the end, xsq doesn't. In any event, it + * appears that having an extra byte at the end is okay. + */ + if (usqState.dataInBuffer > 1) { + DBUG(("--- Found %ld bytes following compressed data (compRem=%ld)\n", + usqState.dataInBuffer, compRemaining)); + Nu_ReportError(NU_BLOB, kNuErrNone, "(Warning) unexpected fluff (%u)", + usqState.dataInBuffer); + } + +bail: + return err; +} + +#endif /*ENABLE_SQ*/ diff --git a/nufxlib/SysDefs.h b/nufxlib/SysDefs.h new file mode 100644 index 0000000..aad4713 --- /dev/null +++ b/nufxlib/SysDefs.h @@ -0,0 +1,138 @@ +/* + * 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. + * + * External type definitions and function prototypes. + */ +#ifndef NUFXLIB_SYSDEFS_H +#define NUFXLIB_SYSDEFS_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef DEBUG_VERBOSE +# define DEBUG_MSGS +#endif + +/* these should exist everywhere */ +#include +#include +#include +#include +#include +#include + +/* basic Win32 stuff -- info-zip has much more complete defs */ +#if defined(_WIN32) || defined(MSDOS) +# define WINDOWS_LIKE + +# 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 +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# define HAVE_SYS_UTIME_H +# define HAVE_WINDOWS_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 mode_t int +# define ENABLE_SQ +# define ENABLE_LZW +# define ENABLE_LZC +/*# define ENABLE_DEFLATE*/ +/*# define ENABLE_BZIP2*/ +# endif + +# include +# include +# define FOPEN_WANTS_B +# define HAVE_CHSIZE +# if _MSC_VER < 1900 /* no snprintf until Visual Studio 2015 */ +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# endif + +#endif + +#ifdef HAVE_MALLOC_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_UTIME_H +# include +#endif +#ifdef HAVE_SYS_UTIME_H +# include +#endif + +#if defined(WINDOWS_LIKE) +# ifndef F_OK +# define F_OK 0 /* was 02 in <= v1.1.0 */ +# endif +#endif + +#if defined(__APPLE__) && defined(__MACH__) /* OS X */ +# define MAC_LIKE +# define UNIX_LIKE +#endif + +#if defined(__unix__) || defined(__unix) || defined(__BEOS__) || \ + defined(__hpux) || defined(_AIX) +# define UNIX_LIKE /* standardize */ +#endif + +#if defined(UNIX_LIKE) +# ifdef USE_REENTRANT_CALLS +# define _REENTRANT /* Solaris 2.x convention */ +# endif +#endif + +/* not currently using filesystem resource forks */ +//#if defined(__ORCAC__) || defined(MAC_LIKE) +//# define HAS_RESOURCE_FORKS +//#endif + +/* __FUNCTION__ was missing from BeOS __MWERKS__, and might be gcc-only */ +#ifdef __GNUC__ +# define HAS__FUNCTION__ +#endif + +#if defined(__linux__) +# define HAS_MALLOC_CHECK_ +#endif + +#endif /*NUFXLIB_SYSDEFS_H*/ diff --git a/nufxlib/Thread.c b/nufxlib/Thread.c new file mode 100644 index 0000000..6480bfd --- /dev/null +++ b/nufxlib/Thread.c @@ -0,0 +1,1381 @@ +/* + * 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. + * + * Thread-level operations. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Utils + * =========================================================================== + */ + +/* + * Returns thread N, or NULL if the index is invalid. + */ +NuThread* Nu_GetThread(const NuRecord* pRecord, int idx) +{ + if (idx >= (int)pRecord->recTotalThreads) + return NULL; + else + return &pRecord->pThreads[idx]; +} + +/* + * ShrinkIt v3.0.0 had a bug where the filename thread would get created + * with the high bits set. We want to undo that without stomping on + * filenames that just happen to have a fancy character in them. If all + * of the high bits are set, assume it's a "defective" name and clear + * them all. If some aren't set, assume it's just a fancy filename. + * + * This high-bit-ism was also done for disk archives by most older versions + * of ShrinkIt. + */ +void Nu_StripHiIfAllSet(char* str) +{ + uint8_t* cp; + + for (cp = (uint8_t*)str; *cp != '\0'; cp++) + if (!(*cp & 0x80)) + return; + + for (cp = (uint8_t*)str; *cp != '\0'; cp++) + *cp &= 0x7f; +} + + +/* + * Decide if a thread is pre-sized (i.e. has a fixed maximum size with a + * lesser amount of uncompressed data within) based on the threadID. + */ +Boolean Nu_IsPresizedThreadID(NuThreadID threadID) +{ + if (threadID == kNuThreadIDFilename || threadID == kNuThreadIDComment) + return true; + else + return false; +} + + +/* + * Return an indication of whether the type of thread specified by ThreadID + * should ever be compressed. Right now, that's only data-class threads. + */ +Boolean Nu_IsCompressibleThreadID(NuThreadID threadID) +{ + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) + return true; + else + return false; +} + + +/* + * Decide if the thread has a CRC, based on the record version and the + * threadID. + */ +Boolean Nu_ThreadHasCRC(uint16_t recordVersion, NuThreadID threadID) +{ + return recordVersion >= 3 && + NuThreadIDGetClass(threadID) == kNuThreadClassData; +} + + +/* + * Search through a given NuRecord for the specified thread. + */ +NuError Nu_FindThreadByIdx(const NuRecord* pRecord, NuThreadIdx thread, + NuThread** ppThread) +{ + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + if (pThread->threadIdx == thread) { + *ppThread = pThread; + return kNuErrNone; + } + } + + return kNuErrThreadIdxNotFound; +} + + +/* + * Search through a given NuRecord for the first thread with a matching + * threadID. + */ +NuError Nu_FindThreadByID(const NuRecord* pRecord, NuThreadID threadID, + NuThread** ppThread) +{ + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + if (NuGetThreadID(pThread) == threadID) { + *ppThread = pThread; + return kNuErrNone; + } + } + + return kNuErrThreadIDNotFound; +} + + +/* + * Copy the contents of a NuThread. + */ +void Nu_CopyThreadContents(NuThread* pDstThread, const NuThread* pSrcThread) +{ + Assert(pDstThread != NULL); + Assert(pSrcThread != NULL); + + memcpy(pDstThread, pSrcThread, sizeof(*pDstThread)); +} + + +/* + * =========================================================================== + * Reading threads from the archive + * =========================================================================== + */ + +/* + * Read a single thread header from the archive. + */ +static NuError Nu_ReadThreadHeader(NuArchive* pArchive, NuThread* pThread, + uint16_t* pCrc) +{ + FILE* fp; + + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(pCrc != NULL); + + fp = pArchive->archiveFp; + + pThread->thThreadClass = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadFormat = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadKind = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadCRC = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadEOF = Nu_ReadFourC(pArchive, fp, pCrc); + pThread->thCompThreadEOF = Nu_ReadFourC(pArchive, fp, pCrc); + + pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); + pThread->actualThreadEOF = 0; /* fix me later */ + pThread->fileOffset = -1; /* mark as invalid */ + pThread->used = 0xcfcf; /* init to invalid value */ + + return Nu_HeaderIOFailed(pArchive, fp); +} + +/* + * Read the threads from the current archive file position. + * + * The storage for the threads is allocated here, in one block. We could + * have used a linked list like NuLib, but that doesn't really provide any + * benefit for us, and adds complexity. + */ +NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, + uint16_t* pCrc) +{ + NuError err = kNuErrNone; + NuThread* pThread; + long count; + Boolean needFakeData, needFakeRsrc; + + needFakeData = true; + needFakeRsrc = (pRecord->recStorageType == kNuStorageExtended); + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + Assert(pCrc != NULL); + + if (!pRecord->recTotalThreads) { + /* not sure if this is reasonable, but we can handle it */ + DBUG(("--- WEIRD: no threads in the record?\n")); + goto bail; + } + + pRecord->pThreads = Nu_Malloc(pArchive, + pRecord->recTotalThreads * sizeof(NuThread)); + BailAlloc(pRecord->pThreads); + + count = pRecord->recTotalThreads; + pThread = pRecord->pThreads; + while (count--) { + err = Nu_ReadThreadHeader(pArchive, pThread, pCrc); + BailError(err); + + if (pThread->thThreadClass == kNuThreadClassData) { + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } else if (pThread->thThreadKind == kNuThreadKindDiskImage) { + /* needFakeRsrc shouldn't be set, but clear anyway */ + needFakeData = needFakeRsrc = false; + } + } + + /* + * Some versions of ShrinkIt write an invalid thThreadEOF for disks, + * so we have to figure out what it's supposed to be. + */ + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDDiskImage) + { + if (pRecord->recStorageType <= 13) { + /* supposed to be block size, but SHK v3.0.1 stored it wrong */ + pThread->actualThreadEOF = pRecord->recExtraType * 512; + + } else if (pRecord->recStorageType == 256 && + pRecord->recExtraType == 280 && + pRecord->recFileSysID == kNuFileSysDOS33) + { + /* + * Fix for less-common ShrinkIt problem: looks like an old + * version of GS/ShrinkIt used 256 as the block size when + * compressing DOS 3.3 images from 5.25" disks. If that + * appears to be the case here, crank up the block size. + */ + DBUG(("--- no such thing as a 70K disk image!\n")); + pThread->actualThreadEOF = pRecord->recExtraType * 512; + + } else { + pThread->actualThreadEOF = + pRecord->recExtraType * pRecord->recStorageType; + } + } else { + pThread->actualThreadEOF = pThread->thThreadEOF; + } + + pThread->used = false; + pThread++; + } + + /* + * If "mask threadless" is set, create "fake" threads with empty + * data and resource forks as needed. + */ + if ((needFakeData || needFakeRsrc) && pArchive->valMaskDataless) { + int firstNewThread = pRecord->recTotalThreads; + + if (needFakeData) { + pRecord->recTotalThreads++; + pRecord->fakeThreads++; + } + if (needFakeRsrc) { + pRecord->recTotalThreads++; + pRecord->fakeThreads++; + } + + pRecord->pThreads = Nu_Realloc(pArchive, pRecord->pThreads, + pRecord->recTotalThreads * sizeof(NuThread)); + BailAlloc(pRecord->pThreads); + + pThread = pRecord->pThreads + firstNewThread; + + if (needFakeData) { + pThread->thThreadClass = kNuThreadClassData; + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadKind = kNuThreadKindDataFork; + pThread->thThreadCRC = kNuInitialThreadCRC; + pThread->thThreadEOF = 0; + pThread->thCompThreadEOF = 0; + pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); + pThread->actualThreadEOF = 0; + pThread->fileOffset = -99999999; + pThread->used = false; + pThread++; + } + if (needFakeRsrc) { + pThread->thThreadClass = kNuThreadClassData; + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadKind = kNuThreadKindRsrcFork; + pThread->thThreadCRC = kNuInitialThreadCRC; + pThread->thThreadEOF = 0; + pThread->thCompThreadEOF = 0; + pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); + pThread->actualThreadEOF = 0; + pThread->fileOffset = -99999999; + pThread->used = false; + } + } + +bail: + return err; +} + + +/* + * Write a single thread header to the archive. + */ +static NuError Nu_WriteThreadHeader(NuArchive* pArchive, + const NuThread* pThread, FILE* fp, uint16_t* pCrc) +{ + Assert(pArchive != NULL); + Assert(pThread != NULL); + Assert(fp != NULL); + Assert(pCrc != NULL); + + Nu_WriteTwoC(pArchive, fp, pThread->thThreadClass, pCrc); + Nu_WriteTwoC(pArchive, fp, (uint16_t)pThread->thThreadFormat, pCrc); + Nu_WriteTwoC(pArchive, fp, pThread->thThreadKind, pCrc); + Nu_WriteTwoC(pArchive, fp, pThread->thThreadCRC, pCrc); + Nu_WriteFourC(pArchive, fp, pThread->thThreadEOF, pCrc); + Nu_WriteFourC(pArchive, fp, pThread->thCompThreadEOF, pCrc); + + return Nu_HeaderIOFailed(pArchive, fp); +} + +/* + * Write the thread headers for the record at the current file position. + * + * Note this doesn't care whether a thread was "fake" or not. In + * effect, we promote all threads to "real" status. We update the + * "fake" count in pRecord accordingly. + */ +NuError Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp, + uint16_t* pCrc) +{ + NuError err = kNuErrNone; + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + err = Nu_WriteThreadHeader(pArchive, pThread, fp, pCrc); + BailError(err); + } + + if (pRecord->fakeThreads != 0) { + DBUG(("+++ promoting %ld fake threads to real\n",pRecord->fakeThreads)); + pRecord->fakeThreads = 0; + } + +bail: + return err; +} + + +/* + * Compute miscellaneous thread information, like total size and file + * offsets. Some values (like file offsets) will not be useful for + * streaming archives. + * + * Requires that the pArchive->currentOffset be set to the offset + * immediately after the last of the thread headers. + */ +NuError Nu_ComputeThreadData(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThread* pThread; + long fileOffset, count; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + /*pRecord->totalLength = 0;*/ + pRecord->totalCompLength = 0; + + fileOffset = pArchive->currentOffset; + + count = pRecord->recTotalThreads; + pThread = pRecord->pThreads; + while (count--) { + pThread->fileOffset = fileOffset; + + /*pRecord->totalLength += pThread->thThreadEOF;*/ + pRecord->totalCompLength += pThread->thCompThreadEOF; + fileOffset += pThread->thCompThreadEOF; + + pThread++; + } + + return kNuErrNone; +} + + +/* + * Skip past some or all of the thread data in the archive. For file + * archives, we scan all the threads, but for streaming archives we only + * want to scan up to the filename thread. (If the filename thread comes + * after one of the data threads, we have a problem!) + * + * The tricky part here is that we don't want to skip over a filename + * thread. We actually want to read it in, so that we have something to + * show to the application. (Someday I'll get AndyN for putting me + * through this...) + */ +NuError Nu_ScanThreads(NuArchive* pArchive, NuRecord* pRecord, long numThreads) +{ + NuError err = kNuErrNone; + NuThread* pThread; + FILE* fp; + + Assert(pArchive != NULL); + Assert(pRecord != NULL); + + fp = pArchive->archiveFp; + + Assert(numThreads <= (long)pRecord->recTotalThreads); + + pThread = pRecord->pThreads; + while (numThreads--) { + if (pRecord->threadFilenameMOR == NULL && + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDFilename) + { + /* it's the first filename thread, read the whole thing */ + if (pThread->thCompThreadEOF > kNuReasonableFilenameLen) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Bad thread filename len (%u)", + pThread->thCompThreadEOF); + goto bail; + } + pRecord->threadFilenameMOR = Nu_Malloc(pArchive, + pThread->thCompThreadEOF +1); + BailAlloc(pRecord->threadFilenameMOR); + + /* note there is no CRC on a filename thread */ + (void) Nu_ReadBytes(pArchive, fp, pRecord->threadFilenameMOR, + pThread->thCompThreadEOF); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading filename thread"); + goto bail; + } + + /* null-terminate on the actual len, not the buffer len */ + pRecord->threadFilenameMOR[pThread->thThreadEOF] = '\0'; + + Nu_StripHiIfAllSet(pRecord->threadFilenameMOR); + + /* prefer this one over the record one, but only one should exist */ + if (pRecord->filenameMOR != NULL) { + DBUG(("--- HEY: got record filename and thread filename\n")); + } + pRecord->filenameMOR = pRecord->threadFilenameMOR; + + } else { + /* not a filename (or not first filename), skip past it */ + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->thCompThreadEOF, SEEK_CUR); + BailError(err); + } + + pThread++; + } + + /* + * Should've had one by now. Supposedly, older versions of ShrinkIt + * wouldn't prompt for a disk image name on DOS 3.3 volumes, so you'd + * end up with a disk image that had no name attached. This will tend + * to confuse things, so we go ahead and give it a name. + */ + if (pRecord->filenameMOR == NULL) { + DBUG(("+++ no filename found, using default record name\n")); + pRecord->filenameMOR = kNuDefaultRecordName; + } + + pArchive->currentOffset += pRecord->totalCompLength; + + if (!Nu_IsStreaming(pArchive)) { + Assert(pArchive->currentOffset == ftell(pArchive->archiveFp)); + } + +bail: + return err; +} + + +/* + * Skip the thread. This only has meaning for streaming archives, and + * assumes that the file pointer is set to the start of the thread's data + * already. + */ +NuError Nu_SkipThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err; + + if (!Nu_IsStreaming(pArchive)) /* for debugging */ + return kNuErrNone; /* for debugging */ + Assert(Nu_IsStreaming(pArchive)); + + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->thCompThreadEOF, SEEK_CUR); + return err; +} + + +/* + * =========================================================================== + * Extract + * =========================================================================== + */ + +/* + * Extract the thread to the specified file pointer. + * + * If the archive is a stream, the stream must be positioned at the + * start of pThread's data. If not, it will be seeked first. + */ +static NuError Nu_ExtractThreadToDataSink(NuArchive* pArchive, + const NuRecord* pRecord, const NuThread* pThread, + NuProgressData* pProgress, NuDataSink* pDataSink) +{ + NuError err; + NuFunnel* pFunnel = NULL; + + /* if it's not a stream, seek to the appropriate spot in the file */ + if (!Nu_IsStreaming(pArchive)) { + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->fileOffset, SEEK_SET); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Unable to seek input to %ld", + pThread->fileOffset); + goto bail; + } + } + + /* + * Set up an output funnel to write to. + */ + err = Nu_FunnelNew(pArchive, pDataSink, Nu_DataSinkGetConvertEOL(pDataSink), + pArchive->valEOL, pProgress, &pFunnel); + BailError(err); + + /* + * Write it. + */ + err = Nu_ExpandStream(pArchive, pRecord, pThread, pArchive->archiveFp, + pFunnel); + if (err != kNuErrNone) { + if (err != kNuErrSkipped && err != kNuErrAborted) + Nu_ReportError(NU_BLOB, err, "ExpandStream failed"); + goto bail; + } + +bail: + (void) Nu_FunnelFree(pArchive, pFunnel); + return err; +} + + +/* + * Extract the specified thread to "pDataSink". If the sink is to a file, + * this will take care of opening (and, if appropriate, creating) the file. + * + * If we're operating on a streaming archive, the file pointer must be + * positioned at the start of the thread's data. If not, it will be + * seeked appropriately. + * + * This calls the "should we extract" and "what pathname should we use" + * filters for every thread, which means we can reject specific kinds + * of forks and/or give them different names. This is a good thing. + */ +static NuError Nu_ExtractThreadCommon(NuArchive* pArchive, + const NuRecord* pRecord, const NuThread* pThread, NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + NuSelectionProposal selProposal; + NuPathnameProposal pathProposal; + NuProgressData progressData; + NuProgressData* pProgressData; + NuDataSink* pOrigDataSink; + UNICHAR* newPathStorageUNI = NULL; + UNICHAR* recFilenameStorageUNI = NULL; + const UNICHAR* newPathnameUNI; + NuResult result; + uint8_t newFssep; + Boolean doFreeSink = false; + + Assert(pRecord != NULL); + Assert(pThread != NULL); + Assert(pDataSink != NULL); + + memset(&progressData, 0, sizeof(progressData)); + pProgressData = NULL; + + /* + * If we're just trying to verify the archive contents, create a + * data sink that goes nowhere at all. + */ + if (pArchive->testMode) { + err = Nu_DataSinkVoid_New( + Nu_DataSinkGetDoExpand(pDataSink), + Nu_DataSinkGetConvertEOL(pDataSink), + &pDataSink); + BailError(err); + doFreeSink = true; + } + + pOrigDataSink = pDataSink; /* save a copy for the "retry" loop */ + + /* + * Decide if we want to extract this thread. This is mostly for + * use by the "bulk" extract, not the per-thread extract, but it + * still applies if they so desire. + */ + if (pArchive->selectionFilterFunc != NULL) { + selProposal.pRecord = pRecord; + selProposal.pThread = pThread; + result = (*pArchive->selectionFilterFunc)(pArchive, &selProposal); + + if (result == kNuSkip) + return Nu_SkipThread(pArchive, pRecord, pThread); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + + newPathnameUNI = NULL; + newFssep = 0; + + recFilenameStorageUNI = Nu_CopyMORToUNI(pRecord->filenameMOR); + +retry_name: + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * We're extracting. Figure out the name of the file to write it to. + * If they want to use the sleazy FILE* back door, create a new + * data sink and use that instead. + * + * Start by resetting everything to defaults, in case this isn't + * our first time through the "rename" loop. + */ + newPathnameUNI = Nu_DataSinkFile_GetPathname(pDataSink); + newFssep = Nu_DataSinkFile_GetFssep(pDataSink); + pDataSink = pOrigDataSink; + + /* if they don't have a pathname func defined, we just use default */ + if (pArchive->outputPathnameFunc != NULL) { + pathProposal.pathnameUNI = recFilenameStorageUNI; + pathProposal.filenameSeparator = + NuGetSepFromSysInfo(pRecord->recFileSysInfo); + pathProposal.pRecord = pRecord; + pathProposal.pThread = pThread; + pathProposal.newPathnameUNI = NULL; + pathProposal.newFilenameSeparator = '\0'; + /*pathProposal.newStorage = (NuThreadID)-1;*/ + pathProposal.newDataSink = NULL; + + result = (*pArchive->outputPathnameFunc)(pArchive, &pathProposal); + + if (result == kNuSkip) + return Nu_SkipThread(pArchive, pRecord, pThread); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + + /* we don't own this string, so make a copy */ + if (pathProposal.newPathnameUNI != NULL) { + Nu_Free(pArchive, newPathStorageUNI); + newPathStorageUNI = strdup(pathProposal.newPathnameUNI); + newPathnameUNI = newPathStorageUNI; + } else { + newPathnameUNI = NULL; + } + if (pathProposal.newFilenameSeparator != '\0') + newFssep = pathProposal.newFilenameSeparator; + + /* if they want to send this somewhere else, let them */ + if (pathProposal.newDataSink != NULL) + pDataSink = pathProposal.newDataSink; + } + + /* at least one of these must be set */ + Assert(!(newPathnameUNI == NULL && pathProposal.newDataSink == NULL)); + } + + /* + * Prepare the progress data if this is a data thread. + */ + if (newPathnameUNI == NULL) { + /* using a data sink; get the pathname out of the record */ + newPathnameUNI = recFilenameStorageUNI; + newFssep = NuGetSepFromSysInfo(pRecord->recFileSysInfo); + } + if (pThread->thThreadClass == kNuThreadClassData) { + pProgressData = &progressData; + err = Nu_ProgressDataInit_Expand(pArchive, pProgressData, pRecord, + newPathnameUNI, newFssep, recFilenameStorageUNI, + Nu_DataSinkGetConvertEOL(pOrigDataSink)); + BailError(err); + + /* send initial progress so they see the right name if "open" fails */ + pProgressData->state = kNuProgressOpening; + err = Nu_SendInitialProgress(pArchive, pProgressData); + BailError(err); + } + + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * We're extracting to a file. Open it, creating it if necessary and + * allowed. + */ + FILE* fileFp = NULL; + + err = Nu_OpenOutputFile(pArchive, pRecord, pThread, newPathnameUNI, + newFssep, &fileFp); + if (err == kNuErrRename) { + /* they want to rename; the OutputPathname callback handles this */ + Nu_Free(pArchive, newPathStorageUNI); + newPathStorageUNI = NULL; + /* reset these just to be careful */ + newPathnameUNI = NULL; + fileFp = NULL; + goto retry_name; + } else if (err != kNuErrNone) { + goto bail; + } + + Assert(fileFp != NULL); + (void) Nu_DataSinkFile_SetFP(pDataSink, fileFp); + + DBUG(("+++ EXTRACTING 0x%08lx from '%s' at offset %0ld to '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filename, pThread->fileOffset, newPathname)); + } else { + DBUG(("+++ EXTRACTING 0x%08lx from '%s' at offset %0ld to sink\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filename, pThread->fileOffset)); + } + + /* extract to the file */ + err = Nu_ExtractThreadToDataSink(pArchive, pRecord, pThread, + pProgressData, pDataSink); + BailError(err); + + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * Close the file, adjusting the modification date and access + * permissions as appropriate. + */ + err = Nu_CloseOutputFile(pArchive, pRecord, + Nu_DataSinkFile_GetFP(pDataSink), newPathnameUNI); + Nu_DataSinkFile_SetFP(pDataSink, NULL); + BailError(err); + } + +bail: + if (err != kNuErrNone && pProgressData != NULL) { + /* send a final progress message, indicating failure */ + if (err == kNuErrSkipped) + pProgressData->state = kNuProgressSkipped; + else if (err == kNuErrAborted) + pProgressData->state = kNuProgressAborted; + else + pProgressData->state = kNuProgressFailed; + (void) Nu_SendInitialProgress(pArchive, pProgressData); + } + + /* if this was an ordinary file, and it's still open, close it */ + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) + Nu_DataSinkFile_Close(pDataSink); + + Nu_Free(pArchive, newPathStorageUNI); + Nu_Free(pArchive, recFilenameStorageUNI); + + if (doFreeSink) + Nu_DataSinkFree(pDataSink); + return err; +} + +/* + * Extract a thread from the archive as part of a "bulk" extract operation. + * + * Streaming archives must be properly positioned. + */ +NuError Nu_ExtractThreadBulk(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err; + NuDataSink* pDataSink = NULL; + UNICHAR* recFilenameStorageUNI = NULL; + NuValue eolConv; + + /* + * Create a file data sink for the file. We use whatever EOL conversion + * is set as the default for the entire archive. (If you want to + * specify your own EOL conversion for each individual file, you will + * need to extract them individually, creating a data sink for each.) + * + * One exception: we turn EOL conversion off for disk image threads. + * It's *very* unlikely this would be desirable, and could be a problem + * if the user is extracting a collection of disks and files. + */ + eolConv = pArchive->valConvertExtractedEOL; + if (NuGetThreadID(pThread) == kNuThreadIDDiskImage) + eolConv = kNuConvertOff; + recFilenameStorageUNI = Nu_CopyMORToUNI(pRecord->filenameMOR); + err = Nu_DataSinkFile_New(true, eolConv, recFilenameStorageUNI, + NuGetSepFromSysInfo(pRecord->recFileSysInfo), &pDataSink); + BailError(err); + + err = Nu_ExtractThreadCommon(pArchive, pRecord, pThread, pDataSink); + BailError(err); + +bail: + if (pDataSink != NULL) { + NuError err2 = Nu_DataSinkFree(pDataSink); + if (err == kNuErrNone) + err = err2; + } + Nu_Free(pArchive, recFilenameStorageUNI); + + return err; +} + + +/* + * Extract a thread, given the IDs and a data sink. + */ +NuError Nu_ExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink) +{ + NuError err; + NuRecord* pRecord; + NuThread* pThread; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + if (threadIdx == 0 || pDataSink == NULL) + return kNuErrInvalidArg; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the correct record and thread by index */ + err = Nu_RecordSet_FindByThreadIdx(&pArchive->origRecordSet, threadIdx, + &pRecord, &pThread); + BailError(err); + Assert(pRecord != NULL); + + /* extract away */ + err = Nu_ExtractThreadCommon(pArchive, pRecord, pThread, pDataSink); + BailError(err); + +bail: + return err; +} + + +/* + * =========================================================================== + * Add/update/delete + * =========================================================================== + */ + +/* + * Verify that a conflicting thread with the specified threadID does not + * exist in this record, now or in the future. + * + * The set of interesting threads is equal to the current threads, minus + * any that have been deleted, plus any that have been added already. + * + * If a matching threadID is found, this returns an error. + */ +static NuError Nu_FindNoFutureThread(NuArchive* pArchive, + const NuRecord* pRecord, NuThreadID threadID) +{ + NuError err = kNuErrNone; + const NuThread* pThread; + const NuThreadMod* pThreadMod; + int idx; + + /* + * Start by scanning the existing threads (if any). + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + if (NuGetThreadID(pThread) == threadID) { + /* found a match, see if it has been deleted */ + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + if (pThreadMod != NULL && + pThreadMod->entry.kind == kNuThreadModDelete) + { + /* it's deleted, ignore it */ + continue; + } + DBUG(("--- found existing thread matching 0x%08lx\n", threadID)); + err = kNuErrThreadAdd; + goto bail; + } + } + + /* + * Now look for "add" threadMods with a matching threadID. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + pThreadMod->entry.add.threadID == threadID) + { + DBUG(("--- found 'add' threadMod matching 0x%08lx\n", threadID)); + err = kNuErrThreadAdd; + goto bail; + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + return err; +} + +/* + * Like Nu_FindNoFutureThread, but tests against a whole class. + */ +static NuError Nu_FindNoFutureThreadClass(NuArchive* pArchive, + const NuRecord* pRecord, long threadClass) +{ + NuError err = kNuErrNone; + const NuThread* pThread; + const NuThreadMod* pThreadMod; + int idx; + + /* + * Start by scanning the existing threads (if any). + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != NULL); + + if (pThread->thThreadClass == threadClass) { + /* found a match, see if it has been deleted */ + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + if (pThreadMod != NULL && + pThreadMod->entry.kind == kNuThreadModDelete) + { + /* it's deleted, ignore it */ + continue; + } + DBUG(("--- Found existing thread matching 0x%04lx\n", threadClass)); + err = kNuErrThreadAdd; + goto bail; + } + } + + /* + * Now look for "add" threadMods with a matching threadClass. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != NULL) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + NuThreadIDGetClass(pThreadMod->entry.add.threadID) == threadClass) + { + DBUG(("--- Found 'add' threadMod matching 0x%04lx\n", threadClass)); + err = kNuErrThreadAdd; + goto bail; + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + return err; +} + + +/* + * Find an existing thread somewhere in the archive. If the "copy" set + * exists it will be searched. If not, the "orig" set is searched, and + * if an entry is found a "copy" set will be created. + * + * The record and thread returned will always be from the "copy" set. An + * error result is returned if the record and thread aren't found. + */ +static NuError Nu_FindThreadForWriteByIdx(NuArchive* pArchive, + NuThreadIdx threadIdx, NuRecord** ppFoundRecord, NuThread** ppFoundThread) +{ + NuError err; + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FindByThreadIdx(&pArchive->copyRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + } else { + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + err = Nu_RecordSet_FindByThreadIdx(&pArchive->origRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + *ppFoundThread = NULL; /* can't delete from here, wipe ptr */ + } + BailError(err); + + /* + * The thread exists. If we were looking in the "orig" set, we have + * to create a "copy" set, and delete it from that. + */ + if (*ppFoundThread == NULL) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_FindByThreadIdx(&pArchive->copyRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + Assert(err == kNuErrNone && *ppFoundThread != NULL); /* must succeed */ + BailError(err); + } + +bail: + return err; +} + +/* + * Determine if it's okay to add a thread of the type specified by + * "threadID" into "pRecord". + * + * Returns with an error (kNuErrThreadAdd) if it's not okay. + */ +NuError Nu_OkayToAddThread(NuArchive* pArchive, const NuRecord* pRecord, + NuThreadID threadID) +{ + NuError err = kNuErrNone; + + /* + * Check for class conflicts (can't mix data and control threads). + */ + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) { + err = Nu_FindNoFutureThreadClass(pArchive, pRecord, + kNuThreadClassControl); + BailError(err); + } else if (NuThreadIDGetClass(threadID) == kNuThreadClassControl) { + err = Nu_FindNoFutureThreadClass(pArchive, pRecord, + kNuThreadClassData); + BailError(err); + } + + /* + * Check for specific type conflicts. + */ + if (threadID == kNuThreadIDDataFork) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDataFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDRsrcFork) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDRsrcFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDDiskImage) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDataFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDRsrcFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDFilename) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDFilename); + BailError(err); + } + +bail: + return err; +} + + + +/* + * Add a new thread to a record. + * + * In some cases, you aren't allowed to add a thread whose type matches + * one that already exists. This applies to data threads and filenames, + * but not to comments, control threads, or IIgs icons. You also can't + * add a disk image thread when there are data-class threads, or vice-versa. + * + * This is the first and last place we do this sort of checking. If + * an illegal situation gets past this function, it will either get + * caught with a fatal assert or (if NDEBUG is defined) not at all. + * + * On success, the NuThreadIdx of the newly-created record will be placed + * in "*pThreadIdx", and "pDataSource" will be owned by NufxLib. + */ +NuError Nu_AddThread(NuArchive* pArchive, NuRecordIdx recIdx, + NuThreadID threadID, NuDataSource* pDataSource, NuThreadIdx* pThreadIdx) +{ + NuError err; + NuRecord* pRecord; + NuThreadMod* pThreadMod = NULL; + NuThreadFormat threadFormat; + + /* okay for pThreadIdx to be NULL */ + if (recIdx == 0 || pDataSource == NULL) + return kNuErrInvalidArg; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the record. If it doesn't exist in the copy set, check to + * see if it's in the "new" set. + */ + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + if (err == kNuErrRecIdxNotFound && + Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + { + err = Nu_RecordSet_FindByIdx(&pArchive->newRecordSet, recIdx, &pRecord); + } + BailError(err); + Assert(pRecord != NULL); + + /* + * Do some tests, looking for specific types of threads that conflict + * with what we're trying to add. + */ + err = Nu_OkayToAddThread(pArchive, pRecord, threadID); + BailError(err); + + /* + * Decide if we want to compress the data from this source. If the + * data is already compressed (as indicated by the data source) or + * this type of thread isn't compressible (e.g. it's a filename), then + * we don't compress it. Otherwise, we use whatever compression mode + * is currently configured. + */ + if (Nu_DataSourceGetThreadFormat(pDataSource) == kNuThreadFormatUncompressed && + Nu_IsCompressibleThreadID(threadID)) + { + threadFormat = Nu_ConvertCompressValToFormat(pArchive, + pArchive->valDataCompression); + } else { + threadFormat = kNuThreadFormatUncompressed; + } + DBUG(("--- using threadFormat = %d\n", threadFormat)); + + /* create a new ThreadMod (which makes a copy of the data source) */ + err = Nu_ThreadModAdd_New(pArchive, threadID, threadFormat, pDataSource, + &pThreadMod); + BailError(err); + Assert(pThreadMod != NULL); + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + if (pThreadIdx != NULL) + *pThreadIdx = pThreadMod->entry.add.threadIdx; + pThreadMod = NULL; /* successful, don't free */ + + /* + * If we've got a header filename and we're adding a filename thread, + * we don't want to write the record header name when we reconstruct + * the record. + */ + if (threadID == kNuThreadIDFilename && pRecord->recFilenameLength) { + DBUG(("+++ gonna drop the filename\n")); + pRecord->dropRecFilename = true; + } + +bail: + if (pThreadMod != NULL) + Nu_ThreadModFree(pArchive, pThreadMod); + if (err == kNuErrNone && pDataSource != NULL) { + /* on success, we have ownership of the data source. ThreadMod + made its own copy, so get rid of this one */ + Nu_DataSourceFree(pDataSource); + } + return err; +} + + +/* + * Update the contents of a pre-sized thread, such as a filename or + * comment thread. + * + * The data from the source must fit within the limits of the existing + * thread. The source data is never compressed, and must not come from + * a compressed source. + * + * You aren't allowed to update threads that have been deleted. Updating + * newly-added threads isn't possible, since they aren't really threads yet. + */ +NuError Nu_UpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, int32_t* pMaxLen) +{ + NuError err; + NuThreadMod* pThreadMod = NULL; + NuRecord* pFoundRecord; + NuThread* pFoundThread; + + if (pDataSource == NULL) { + err = kNuErrInvalidArg; + goto bail; + } + + /* presized threads always contain uncompressed data */ + if (Nu_DataSourceGetThreadFormat(pDataSource) != + kNuThreadFormatUncompressed) + { + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, err, + "presized threads can't hold compressed data"); + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the thread in the "copy" set. (If there isn't a copy set, + * make one.) + */ + err = Nu_FindThreadForWriteByIdx(pArchive, threadIdx, &pFoundRecord, + &pFoundThread); + BailError(err); + + if (!Nu_IsPresizedThreadID(NuGetThreadID(pFoundThread)) || + !(pFoundThread->thCompThreadEOF >= pFoundThread->thThreadEOF)) + { + err = kNuErrNotPreSized; + Nu_ReportError(NU_BLOB, err, "invalid thread for update"); + goto bail; + } + + if (pMaxLen != NULL) + *pMaxLen = pFoundThread->thCompThreadEOF; + + /* + * Check to see if somebody is trying to delete this, or has already + * updated it. + */ + if (Nu_ThreadMod_FindByThreadIdx(pFoundRecord, threadIdx) != NULL) { + DBUG(("--- Tried to modify a deleted or modified thread\n")); + err = kNuErrModThreadChange; + goto bail; + } + + /* + * Verify that "otherLen" in the data source is less than or equal + * to our len, if we can. If the data source is a file on disk, + * we're not really supposed to look at it until we flush. We + * could sneak a peek right now, which would prevent us from aborting + * the entire operation when it turns out the file won't fit, but + * that violates our semantics (and besides, the application really + * should've done that already). + * + * If the data source is from a file, we just assume it'll fit and + * let the chips fall where they may later on. + */ + if (Nu_DataSourceGetType(pDataSource) != kNuDataSourceFromFile) { + if (pFoundThread->thCompThreadEOF < + Nu_DataSourceGetOtherLen(pDataSource)) + { + err = kNuErrPreSizeOverflow; + Nu_ReportError(NU_BLOB, err, "can't put %u bytes into %u", + Nu_DataSourceGetOtherLen(pDataSource), + pFoundThread->thCompThreadEOF); + goto bail; + } + + /* check for zero-length and excessively long filenames */ + if (NuGetThreadID(pFoundThread) == kNuThreadIDFilename && + (Nu_DataSourceGetOtherLen(pDataSource) == 0 || + Nu_DataSourceGetOtherLen(pDataSource) > kNuReasonableFilenameLen)) + { + err = kNuErrInvalidFilename; + Nu_ReportError(NU_BLOB, err, "invalid filename (%u bytes)", + Nu_DataSourceGetOtherLen(pDataSource)); + goto bail; + } + } + + /* + * Looks like it'll fit, and it's the right kind of data. Create + * an "update" threadMod. Note this copies the data source. + */ + Assert(pFoundThread->thThreadFormat == kNuThreadFormatUncompressed); + err = Nu_ThreadModUpdate_New(pArchive, threadIdx, pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != NULL); + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pFoundRecord, pThreadMod); + + /* + * NOTE: changes to filename threads will be picked up later and + * incorporated into the record's threadFilename. We don't worry + * about the record header filename, because we might be doing an + * update-in-place and that prevents us from removing the filename + * (doing so would change the size of the archive). No need to + * do any filename-specific changes here. + */ + +bail: + return err; +} + +/* + * Delete an individual thread. + * + * You aren't allowed to delete threads that have been updated. Deleting + * newly-added threads isn't possible, since they aren't really threads yet. + * + * Don't worry about deleting filename threads here; we take care of that + * later on. Besides, it's sort of handy to hang on to the filename for + * as long as possible. + */ +NuError Nu_DeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx) +{ + NuError err; + NuThreadMod* pThreadMod = NULL; + NuRecord* pFoundRecord; + NuThread* pFoundThread; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the thread in the "copy" set. (If there isn't a copy set, + * make one.) + */ + err = Nu_FindThreadForWriteByIdx(pArchive, threadIdx, &pFoundRecord, + &pFoundThread); + BailError(err); + + /* + * Deletion of modified threads (updates or previous deletes) isn't + * allowed. Deletion of threads from deleted records can't happen, + * because deleted records are completely removed from the "copy" set. + */ + if (Nu_ThreadMod_FindByThreadIdx(pFoundRecord, threadIdx) != NULL) { + DBUG(("--- Tried to delete a deleted or modified thread\n")); + err = kNuErrModThreadChange; + goto bail; + } + + /* + * Looks good. Add a new "delete" ThreadMod to the list. + */ + err = Nu_ThreadModDelete_New(pArchive, threadIdx, + NuGetThreadID(pFoundThread), &pThreadMod); + BailError(err); + Nu_RecordAddThreadMod(pFoundRecord, pThreadMod); + pThreadMod = NULL; /* successful, don't free */ + +bail: + Nu_ThreadModFree(pArchive, pThreadMod); + return err; +} + diff --git a/nufxlib/Value.c b/nufxlib/Value.c new file mode 100644 index 0000000..c344b01 --- /dev/null +++ b/nufxlib/Value.c @@ -0,0 +1,326 @@ +/* + * 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. + * + * Get/set certain values and attributes. + */ +#include "NufxLibPriv.h" + +#define kMaxJunkSkipMax 8192 + + +/* + * Get a configurable parameter. + */ +NuError Nu_GetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue) +{ + NuError err = kNuErrNone; + + if (pValue == NULL) + return kNuErrInvalidArg; + + switch (ident) { + case kNuValueAllowDuplicates: + *pValue = pArchive->valAllowDuplicates; + break; + case kNuValueConvertExtractedEOL: + *pValue = pArchive->valConvertExtractedEOL; + break; + case kNuValueDataCompression: + *pValue = pArchive->valDataCompression; + break; + case kNuValueDiscardWrapper: + *pValue = pArchive->valDiscardWrapper; + break; + case kNuValueEOL: + *pValue = pArchive->valEOL; + break; + case kNuValueHandleExisting: + *pValue = pArchive->valHandleExisting; + break; + case kNuValueIgnoreCRC: + *pValue = pArchive->valIgnoreCRC; + break; + case kNuValueMaskDataless: + *pValue = pArchive->valMaskDataless; + break; + case kNuValueMimicSHK: + *pValue = pArchive->valMimicSHK; + break; + case kNuValueModifyOrig: + *pValue = pArchive->valModifyOrig; + break; + case kNuValueOnlyUpdateOlder: + *pValue = pArchive->valOnlyUpdateOlder; + break; + case kNuValueStripHighASCII: + *pValue = pArchive->valStripHighASCII; + break; + case kNuValueJunkSkipMax: + *pValue = pArchive->valJunkSkipMax; + break; + case kNuValueIgnoreLZW2Len: + *pValue = pArchive->valIgnoreLZW2Len; + break; + case kNuValueHandleBadMac: + *pValue = pArchive->valHandleBadMac; + break; + default: + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Unknown ValueID %d requested", ident); + goto bail; + } + +bail: + return err; +} + + +/* + * Set a configurable parameter. + */ +NuError Nu_SetValue(NuArchive* pArchive, NuValueID ident, NuValue value) +{ + NuError err = kNuErrInvalidArg; + + switch (ident) { + case kNuValueAllowDuplicates: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueAllowDuplicates value %u", value); + goto bail; + } + pArchive->valAllowDuplicates = value; + break; + case kNuValueConvertExtractedEOL: + if (value < kNuConvertOff || value > kNuConvertAuto) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueConvertExtractedEOL value %u", value); + goto bail; + } + pArchive->valConvertExtractedEOL = value; + break; + case kNuValueDataCompression: + if (value < kNuCompressNone || value > kNuCompressBzip2) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueDataCompression value %u", value); + goto bail; + } + pArchive->valDataCompression = value; + break; + case kNuValueDiscardWrapper: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueDiscardWrapper value %u", value); + goto bail; + } + pArchive->valDiscardWrapper = value; + break; + case kNuValueEOL: + if (value < kNuEOLUnknown || value > kNuEOLCRLF) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueEOL value %u", value); + goto bail; + } + pArchive->valEOL = value; + break; + case kNuValueHandleExisting: + if (value < kNuMaybeOverwrite || value > kNuMustOverwrite) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueHandleExisting value %u", value); + goto bail; + } + pArchive->valHandleExisting = value; + break; + case kNuValueIgnoreCRC: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueIgnoreCRC value %u", value); + goto bail; + } + pArchive->valIgnoreCRC = value; + break; + case kNuValueMaskDataless: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueMaskDataless value %u", value); + goto bail; + } + pArchive->valMaskDataless = value; + break; + case kNuValueMimicSHK: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueMimicSHK value %u", value); + goto bail; + } + pArchive->valMimicSHK = value; + break; + case kNuValueModifyOrig: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueModifyOrig value %u", value); + goto bail; + } + pArchive->valModifyOrig = value; + break; + case kNuValueOnlyUpdateOlder: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueOnlyUpdateOlder value %u", value); + goto bail; + } + pArchive->valOnlyUpdateOlder = value; + break; + case kNuValueStripHighASCII: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueStripHighASCII value %u", value); + goto bail; + } + pArchive->valStripHighASCII = value; + break; + case kNuValueJunkSkipMax: + if (value > kMaxJunkSkipMax) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueJunkSkipMax value %u", value); + goto bail; + } + pArchive->valJunkSkipMax = value; + break; + case kNuValueIgnoreLZW2Len: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueIgnoreLZW2Len value %u", value); + goto bail; + } + pArchive->valIgnoreLZW2Len = value; + break; + case kNuValueHandleBadMac: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueHandleBadMac value %u", value); + goto bail; + } + pArchive->valHandleBadMac = value; + break; + default: + Nu_ReportError(NU_BLOB, err, "Unknown ValueID %d requested", ident); + goto bail; + } + + err = kNuErrNone; + +bail: + return err; +} + + +/* + * Get an archive attribute. These are things that you would have to + * pry into pArchive to get at (like the archive type) or get the master + * header (like the number of records). + */ +NuError Nu_GetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr) +{ + NuError err = kNuErrNone; + if (pAttr == NULL) + return kNuErrInvalidArg; + + switch (ident) { + case kNuAttrArchiveType: + *pAttr = pArchive->archiveType; + break; + case kNuAttrNumRecords: + *pAttr = pArchive->masterHeader.mhTotalRecords; + break; + case kNuAttrHeaderOffset: + *pAttr = pArchive->headerOffset; + break; + case kNuAttrJunkOffset: + *pAttr = pArchive->junkOffset; + break; + default: + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Unknown AttrID %d requested", ident); + goto bail; + } + +bail: + return err; +} + +/* + * Convert a NuValue compression type to a "phyiscal" ThreadFormat. + * + * Unsupported compression types cause a warning to be flagged. + */ +NuThreadFormat Nu_ConvertCompressValToFormat(NuArchive* pArchive, + NuValue compValue) +{ + NuThreadFormat threadFormat; + Boolean unsup = false; + + switch (compValue) { + case kNuCompressNone: threadFormat = kNuThreadFormatUncompressed; break; + + #ifdef ENABLE_SQ + case kNuCompressSQ: threadFormat = kNuThreadFormatHuffmanSQ; break; + #else + case kNuCompressSQ: threadFormat = kNuThreadFormatHuffmanSQ; + unsup = true; break; + #endif + + #ifdef ENABLE_LZW + case kNuCompressLZW1: threadFormat = kNuThreadFormatLZW1; break; + case kNuCompressLZW2: threadFormat = kNuThreadFormatLZW2; break; + #else + case kNuCompressLZW1: threadFormat = kNuThreadFormatLZW1; + unsup = true; break; + case kNuCompressLZW2: threadFormat = kNuThreadFormatLZW2; + unsup = true; break; + #endif + + #ifdef ENABLE_LZC + case kNuCompressLZC12: threadFormat = kNuThreadFormatLZC12; break; + case kNuCompressLZC16: threadFormat = kNuThreadFormatLZC16; break; + #else + case kNuCompressLZC12: threadFormat = kNuThreadFormatLZC12; + unsup = true; break; + case kNuCompressLZC16: threadFormat = kNuThreadFormatLZC16; + unsup = true; break; + #endif + + #ifdef ENABLE_DEFLATE + case kNuCompressDeflate: threadFormat = kNuThreadFormatDeflate; break; + #else + case kNuCompressDeflate: threadFormat = kNuThreadFormatDeflate; + unsup = true; break; + #endif + + #ifdef ENABLE_BZIP2 + case kNuCompressBzip2: threadFormat = kNuThreadFormatBzip2; break; + #else + case kNuCompressBzip2: threadFormat = kNuThreadFormatBzip2; + unsup = true; break; + #endif + + default: + Nu_ReportError(NU_BLOB, kNuErrInvalidArg, + "Unknown compress value %u", compValue); + Assert(false); + return kNuThreadFormatUncompressed; + } + + if (unsup) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "Unsupported compression 0x%04x requested (%u), storing", + threadFormat, compValue); + return kNuThreadFormatUncompressed; + } + + return threadFormat; +} + diff --git a/nufxlib/Version.c b/nufxlib/Version.c new file mode 100644 index 0000000..9c3396c --- /dev/null +++ b/nufxlib/Version.c @@ -0,0 +1,41 @@ +/* + * 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. + */ +#include "NufxLibPriv.h" + +/* executable was build on or after this date */ +#ifdef __DATE__ +static const char gNuBuildDate[] = __DATE__; +#else +static const char gNuBuildDate[] = "??? ?? ????"; +#endif + +#ifdef OPTFLAGSTR +static const char gNuBuildFlags[] = OPTFLAGSTR; +#else +static const char gNuBuildFlags[] = "-"; +#endif + + +/* + * Return the version number, date built, and build flags. + */ +NuError Nu_GetVersion(int32_t* pMajorVersion, int32_t* pMinorVersion, + int32_t* pBugVersion, const char** ppBuildDate, const char** ppBuildFlags) +{ + if (pMajorVersion != NULL) + *pMajorVersion = kNuVersionMajor; + if (pMinorVersion != NULL) + *pMinorVersion = kNuVersionMinor; + if (pBugVersion != NULL) + *pBugVersion = kNuVersionBug; + if (ppBuildDate != NULL) + *ppBuildDate = gNuBuildDate; + if (ppBuildFlags != NULL) + *ppBuildFlags = gNuBuildFlags; + return kNuErrNone; +} + diff --git a/nufxlib/at.log b/nufxlib/at.log new file mode 100644 index 0000000..07d2c7a --- /dev/null +++ b/nufxlib/at.log @@ -0,0 +1,79 @@ +checking build system type... x86_64-unknown-linux-gnu +checking host system type... x86_64-unknown-linux-gnu +checking for gcc... gcc +checking whether the C compiler works... yes +checking for C compiler default output file name... a.out +checking for suffix of executables... +checking whether we are cross compiling... no +checking for suffix of object files... o +checking whether we are using the GNU C compiler... yes +checking whether gcc accepts -g... yes +checking for gcc option to accept ISO C89... none needed +checking for a BSD-compatible install... /usr/bin/install -c +checking whether make sets $(MAKE)... yes +checking for ranlib... ranlib +checking how to run the C preprocessor... gcc -E +checking for grep that handles long lines and -e... /usr/bin/grep +checking for egrep... /usr/bin/grep -E +checking for ANSI C header files... yes +checking for sys/types.h... yes +checking for sys/stat.h... yes +checking for stdlib.h... yes +checking for string.h... yes +checking for memory.h... yes +checking for strings.h... yes +checking for inttypes.h... yes +checking for stdint.h... yes +checking for unistd.h... yes +checking fcntl.h usability... yes +checking fcntl.h presence... yes +checking for fcntl.h... yes +checking malloc.h usability... yes +checking malloc.h presence... yes +checking for malloc.h... yes +checking for stdlib.h... (cached) yes +checking for sys/stat.h... (cached) yes +checking sys/time.h usability... yes +checking sys/time.h presence... yes +checking for sys/time.h... yes +checking for sys/types.h... (cached) yes +checking sys/utime.h usability... no +checking sys/utime.h presence... no +checking for sys/utime.h... no +checking for unistd.h... (cached) yes +checking utime.h usability... yes +checking utime.h presence... yes +checking for utime.h... yes +checking for an ANSI C-conforming const... yes +checking for inline... inline +checking for mode_t... yes +checking for off_t... yes +checking for size_t... yes +checking whether struct tm is in sys/time.h or time.h... time.h +checking for fdopen... yes +checking for ftruncate... yes +checking for memmove... yes +checking for mkdir... yes +checking for mkstemp... yes +checking for mktime... yes +checking for timelocal... yes +checking for localtime_r... yes +checking for snprintf... yes +checking for strcasecmp... yes +checking for strncasecmp... yes +checking for strtoul... yes +checking for strerror... yes +checking for vsnprintf... yes +checking if snprintf is declared... yes +checking if vsnprintf is declared... yes +checking if sprintf returns int... yes +checking for deflate in -lz... yes +checking zlib.h usability... yes +checking zlib.h presence... yes +checking for zlib.h... yes + (found libz and zlib.h, enabling deflate) +configure: creating ./config.status +config.status: creating Makefile +config.status: creating samples/Makefile +config.status: creating config.h +config.status: config.h is unchanged diff --git a/nufxlib/config.guess b/nufxlib/config.guess new file mode 100644 index 0000000..ad5281e --- /dev/null +++ b/nufxlib/config.guess @@ -0,0 +1,1466 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-08-03' + +# This file 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., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerppc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm:riscos:*:*|arm:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit ;; + crisv32:Linux:*:*) + echo crisv32-axis-linux-gnu + exit ;; + frv:Linux:*:*) + echo frv-unknown-linux-gnu + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + or32:Linux:*:*) + echo or32-unknown-linux-gnu + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && { + echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + exit + } + test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + case $UNAME_PROCESSOR in + *86) UNAME_PROCESSOR=i686 ;; + unknown) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NSE-?:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/nufxlib/config.h.in b/nufxlib/config.h.in new file mode 100644 index 0000000..dd294e2 --- /dev/null +++ b/nufxlib/config.h.in @@ -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 doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if your declares struct tm. */ +#undef TM_IN_SYS_TIME + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if 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 header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_UTIME_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the 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 + diff --git a/nufxlib/config.sub b/nufxlib/config.sub new file mode 100644 index 0000000..1c366df --- /dev/null +++ b/nufxlib/config.sub @@ -0,0 +1,1579 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-07-08' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file 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., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ + kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | bfin \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | m32r | m32rle | m68000 | m68k | m88k | maxq | mcore \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | ms1 \ + | msp430 \ + | ns16k | ns32k \ + | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xscale | xscalee[bl] | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m32c) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | ms1-* \ + | msp430-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xps100-* | xscale-* | xscalee[bl]-* \ + | xstormy16-* | xtensa-* \ + | ymp-* \ + | z8k-*) + ;; + m32c-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16c) + basic_machine=cr16c-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -zvmoe) + os=-zvmoe + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/nufxlib/configure b/nufxlib/configure new file mode 100755 index 0000000..f459119 --- /dev/null +++ b/nufxlib/configure @@ -0,0 +1,5503 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file="NufxLibPriv.h" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +SHARE_FLAGS +BUILD_FLAGS +EGREP +GREP +CPP +RANLIB +SET_MAKE +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_sq +enable_lzw +enable_lzc +enable_deflate +enable_bzip2 +enable_dmalloc +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --disable-sq disable SQ compression + --disable-lzw disable LZW/1 and LZW/2 compression + --disable-lzc disable 12- and 16-bit LZC compression + --disable-deflate disable zlib deflate compression + --enable-bzip2 enable libbz2 bzip2 compression + --enable-dmalloc do dmalloc stuff + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + eval "$3=yes" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_type + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_config_headers="$ac_config_headers config.h" + + +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \ + sys/utime.h unistd.h utime.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +LIBS="" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5 +$as_echo_n "checking for an ANSI C-conforming const... " >&6; } +if ${ac_cv_c_const+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + +#ifndef __cplusplus + /* Ultrix mips cc rejects this sort of thing. */ + typedef int charset[2]; + const charset cs = { 0, 0 }; + /* SunOS 4.1.1 cc rejects this. */ + char const *const *pcpcc; + char **ppc; + /* NEC SVR4.0.2 mips cc rejects this. */ + struct point {int x, y;}; + static struct point const zero = {0,0}; + /* AIX XL C 1.02.0.0 rejects this. + It does not let you subtract one const X* pointer from another in + an arm of an if-expression whose if-part is not a constant + expression */ + const char *g = "string"; + pcpcc = &g + (g ? g-g : 0); + /* HPUX 7.0 cc rejects these. */ + ++pcpcc; + ppc = (char**) pcpcc; + pcpcc = (char const *const *) ppc; + { /* SCO 3.2v4 cc rejects this sort of thing. */ + char tx; + char *t = &tx; + char const *s = 0 ? (char *) 0 : (char const *) 0; + + *t++ = 0; + if (s) return 0; + } + { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ + int x[] = {25, 17}; + const int *foo = &x[0]; + ++foo; + } + { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ + typedef const int *iptr; + iptr p = 0; + ++p; + } + { /* AIX XL C 1.02.0.0 rejects this sort of thing, saying + "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ + struct s { int j; const int *ap[3]; } bx; + struct s *b = &bx; b->j = 5; + } + { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; + if (!foo) return 0; + } + return !cs[0] && !zero.x; +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_const=yes +else + ac_cv_c_const=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5 +$as_echo "$ac_cv_c_const" >&6; } +if test $ac_cv_c_const = no; then + +$as_echo "#define const /**/" >>confdefs.h + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 +$as_echo_n "checking for inline... " >&6; } +if ${ac_cv_c_inline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __cplusplus +typedef int foo_t; +static $ac_kw foo_t static_foo () {return 0; } +$ac_kw foo_t foo () {return 0; } +#endif + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_inline=$ac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + test "$ac_cv_c_inline" != no && break +done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 +$as_echo "$ac_cv_c_inline" >&6; } + +case $ac_cv_c_inline in + inline | yes) ;; + *) + case $ac_cv_c_inline in + no) ac_val=;; + *) ac_val=$ac_cv_c_inline;; + esac + cat >>confdefs.h <<_ACEOF +#ifndef __cplusplus +#define inline $ac_val +#endif +_ACEOF + ;; +esac + +ac_fn_c_check_type "$LINENO" "mode_t" "ac_cv_type_mode_t" "$ac_includes_default" +if test "x$ac_cv_type_mode_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define mode_t int +_ACEOF + +fi + +ac_fn_c_check_type "$LINENO" "off_t" "ac_cv_type_off_t" "$ac_includes_default" +if test "x$ac_cv_type_off_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define off_t long int +_ACEOF + +fi + +ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" +if test "x$ac_cv_type_size_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define size_t unsigned int +_ACEOF + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5 +$as_echo_n "checking whether struct tm is in sys/time.h or time.h... " >&6; } +if ${ac_cv_struct_tm+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include + +int +main () +{ +struct tm tm; + int *p = &tm.tm_sec; + return !p; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_struct_tm=time.h +else + ac_cv_struct_tm=sys/time.h +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_tm" >&5 +$as_echo "$ac_cv_struct_tm" >&6; } +if test $ac_cv_struct_tm = sys/time.h; then + +$as_echo "#define TM_IN_SYS_TIME 1" >>confdefs.h + +fi + + +for ac_func in fdopen ftruncate memmove mkdir mkstemp mktime timelocal \ + localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if snprintf is declared" >&5 +$as_echo_n "checking if snprintf is declared... " >&6; } +if ${nufxlib_cv_snprintf_in_header+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "snprintf" >/dev/null 2>&1; then : + nufxlib_cv_snprintf_in_header=yes +else + nufxlib_cv_snprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_snprintf_in_header = "yes"; then + $as_echo "#define SNPRINTF_DECLARED 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_snprintf_in_header" >&5 +$as_echo "$nufxlib_cv_snprintf_in_header" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if vsnprintf is declared" >&5 +$as_echo_n "checking if vsnprintf is declared... " >&6; } +if ${nufxlib_cv_vsnprintf_in_header+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "vsnprintf" >/dev/null 2>&1; then : + nufxlib_cv_vsnprintf_in_header=yes +else + nufxlib_cv_vsnprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_vsnprintf_in_header = "yes"; then + $as_echo "#define VSNPRINTF_DECLARED 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_vsnprintf_in_header" >&5 +$as_echo "$nufxlib_cv_vsnprintf_in_header" >&6; } + +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + + +SHARE_FLAGS='-shared' +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + 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 + SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"' +fi + + + + +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' + + + + + + fi +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if sprintf returns int" >&5 +$as_echo_n "checking if sprintf returns int... " >&6; } +if ${nufxlib_cv_sprintf_returns_int+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "$cross_compiling" = yes; then : + nufxlib_cv_sprintf_returns_int=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + int main(void) + { + int count; + char buf[8]; + count = sprintf(buf, "123"); /* should return three */ + exit(count != 3); + } + +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + nufxlib_cv_sprintf_returns_int=yes +else + nufxlib_cv_sprintf_returns_int=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + +fi + + +if test $nufxlib_cv_sprintf_returns_int = "yes"; then + $as_echo "#define SPRINTF_RETURNS_INT 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $nufxlib_cv_sprintf_returns_int" >&5 +$as_echo "$nufxlib_cv_sprintf_returns_int" >&6; } + + +# Check whether --enable-sq was given. +if test "${enable_sq+set}" = set; then : + enableval=$enable_sq; +else + enable_sq=yes +fi + +if test $enable_sq = "yes"; then + $as_echo "#define ENABLE_SQ 1" >>confdefs.h + +fi + +# Check whether --enable-lzw was given. +if test "${enable_lzw+set}" = set; then : + enableval=$enable_lzw; +else + enable_lzw=yes +fi + +if test $enable_lzw = "yes"; then + $as_echo "#define ENABLE_LZW 1" >>confdefs.h + +fi + +# Check whether --enable-lzc was given. +if test "${enable_lzc+set}" = set; then : + enableval=$enable_lzc; +else + enable_lzc=yes +fi + +if test $enable_lzc = "yes"; then + $as_echo "#define ENABLE_LZC 1" >>confdefs.h + +fi + +# Check whether --enable-deflate was given. +if test "${enable_deflate+set}" = set; then : + enableval=$enable_deflate; +else + enable_deflate=yes +fi + +if test $enable_deflate = "yes"; then + got_zlibh=false + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for deflate in -lz" >&5 +$as_echo_n "checking for deflate in -lz... " >&6; } +if ${ac_cv_lib_z_deflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char deflate (); +int +main () +{ +return deflate (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_z_deflate=yes +else + ac_cv_lib_z_deflate=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_deflate" >&5 +$as_echo "$ac_cv_lib_z_deflate" >&6; } +if test "x$ac_cv_lib_z_deflate" = xyes; then : + got_libz=true +else + got_libz=false +fi + + if $got_libz; then + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + got_zlibh=true LIBS="$LIBS -lz" +fi + + + fi + if $got_zlibh; then + echo " (found libz and zlib.h, enabling deflate)" + $as_echo "#define ENABLE_DEFLATE 1" >>confdefs.h + + else + echo " (couldn't find libz and zlib.h, not enabling deflate)" + fi +fi + +# Check whether --enable-bzip2 was given. +if test "${enable_bzip2+set}" = set; then : + enableval=$enable_bzip2; +else + enable_bzip2=no +fi + +if test $enable_bzip2 = "yes"; then + got_bzlibh=false + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for BZ2_bzCompress in -lbz2" >&5 +$as_echo_n "checking for BZ2_bzCompress in -lbz2... " >&6; } +if ${ac_cv_lib_bz2_BZ2_bzCompress+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lbz2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char BZ2_bzCompress (); +int +main () +{ +return BZ2_bzCompress (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_bz2_BZ2_bzCompress=yes +else + ac_cv_lib_bz2_BZ2_bzCompress=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bz2_BZ2_bzCompress" >&5 +$as_echo "$ac_cv_lib_bz2_BZ2_bzCompress" >&6; } +if test "x$ac_cv_lib_bz2_BZ2_bzCompress" = xyes; then : + got_libbz=true +else + got_libbz=false +fi + + if $got_libbz; then + ac_fn_c_check_header_mongrel "$LINENO" "bzlib.h" "ac_cv_header_bzlib_h" "$ac_includes_default" +if test "x$ac_cv_header_bzlib_h" = xyes; then : + got_bzlibh=true LIBS="$LIBS -lbz2" +fi + + + fi + if $got_bzlibh; then + echo " (found libbz2 and bzlib.h, enabling bzip2)" + $as_echo "#define ENABLE_BZIP2 1" >>confdefs.h + + else + echo " (couldn't find libbz2 and bzlib.h, not enabling bzip2)" + fi +fi + + +# Check whether --enable-dmalloc was given. +if test "${enable_dmalloc+set}" = set; then : + enableval=$enable_dmalloc; echo "--- enabling dmalloc"; + LIBS="$LIBS -L/usr/local/lib -ldmalloc"; $as_echo "#define USE_DMALLOC 1" >>confdefs.h + +fi + + +ac_config_files="$ac_config_files Makefile samples/Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "samples/Makefile") CONFIG_FILES="$CONFIG_FILES samples/Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi + ;; + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/nufxlib/configure.in b/nufxlib/configure.in new file mode 100644 index 0000000..46df1a4 --- /dev/null +++ b/nufxlib/configure.in @@ -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 + 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) diff --git a/nufxlib/install-sh b/nufxlib/install-sh new file mode 100644 index 0000000..e9de238 --- /dev/null +++ b/nufxlib/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/nufxlib/mkinstalldirs b/nufxlib/mkinstalldirs new file mode 100644 index 0000000..936cf34 --- /dev/null +++ b/nufxlib/mkinstalldirs @@ -0,0 +1,34 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Last modified: 1995-03-05 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" > /dev/null 2>&1 || lasterr=$? + fi + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus diff --git a/nufxlib/nufxlib.def b/nufxlib/nufxlib.def new file mode 100644 index 0000000..ad33e3e --- /dev/null +++ b/nufxlib/nufxlib.def @@ -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 diff --git a/nufxlib/nufxlib.vcxproj b/nufxlib/nufxlib.vcxproj new file mode 100644 index 0000000..4f6f815 --- /dev/null +++ b/nufxlib/nufxlib.vcxproj @@ -0,0 +1,118 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C48AE53B-3DCB-43B1-9207-B7C5B6BB78AF} + Win32Proj + nufxlib + + + + DynamicLibrary + true + v143 + Unicode + Dynamic + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {b66109f4-217b-43c0-86aa-eb55657e5ac0} + + + + + + \ No newline at end of file diff --git a/nufxlib/nufxlib.vcxproj.filters b/nufxlib/nufxlib.vcxproj.filters new file mode 100644 index 0000000..816c720 --- /dev/null +++ b/nufxlib/nufxlib.vcxproj.filters @@ -0,0 +1,102 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/nufxlib/samples/Common.h b/nufxlib/samples/Common.h new file mode 100644 index 0000000..2391747 --- /dev/null +++ b/nufxlib/samples/Common.h @@ -0,0 +1,66 @@ +/* + * 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. + * + * Common functions for NuLib tests. + */ +#ifndef NUFXLIB_SAMPLES_COMMON_H +#define NUFXLIB_SAMPLES_COMMON_H + +#include "SysDefs.h" /* might as well draft off the autoconf */ +#include "NufxLib.h" + +#ifdef USE_DMALLOC +# include "dmalloc.h" +#endif + +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +#ifndef __cplusplus + #define false 0 + #define true (!false) +#endif + + +#ifdef FOPEN_WANTS_B +# define kNuFileOpenReadOnly "rb" +# define kNuFileOpenReadWrite "r+b" +# define kNuFileOpenWriteTrunc "wb" +# define kNuFileOpenReadWriteCreat "w+b" +#else +# define kNuFileOpenReadOnly "r" +# define kNuFileOpenReadWrite "r+" +# define kNuFileOpenWriteTrunc "w" +# define kNuFileOpenReadWriteCreat "w+" +#endif + + +/* + * Figure out what path separator to use. + * + * NOTE: recent versions of Win32 will also accept '/'. + */ + +#ifdef MSDOS +# define PATH_SEP '\\' +#endif + +#ifdef WIN32 +# define PATH_SEP '\\' +#endif + +#ifdef MACOS +# define PATH_SEP ':' +#endif + +#if defined(APW) || defined(__ORCAC__) +# define PATH_SEP ':' +#endif + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif + +#endif /*NUFXLIB_SAMPLES_COMMON_H*/ diff --git a/nufxlib/samples/Exerciser.c b/nufxlib/samples/Exerciser.c new file mode 100644 index 0000000..3ede796 --- /dev/null +++ b/nufxlib/samples/Exerciser.c @@ -0,0 +1,1365 @@ +/* + * 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. + * + * NufxLib exerciser. Most library functions can be invoked directly from + * the exerciser command line. + * + * This was written in C++ to evaluate the interaction between NufxLib and + * the C++ language, i.e. to make sure that all type definitions and + * function calls can be used without giving the compiler fits. This + * file will compile as either "Exerciser.c" or "Exerciser.cpp". + */ +#include "NufxLib.h" +#include "Common.h" +#include + +/* not portable to other OSs, but not all that important anyway */ +const char kFssep = PATH_SEP; + +/* ProDOS access permissions */ +#define kUnlocked 0xe3 + +#define kTempFile "exer-temp" + +#ifndef HAVE_STRCASECMP +static int strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} +#endif + + +/* + * =========================================================================== + * ExerciserState object + * =========================================================================== + */ + +/* + * Exerciser state. + * + * In case it isn't immediately apparent, this was written in C++ and + * then converted back to C. + */ +typedef struct ExerciserState { + NuArchive* pArchive; + char* archivePath; + const char* archiveFile; +} ExerciserState; + + +ExerciserState* ExerciserState_New(void) +{ + ExerciserState* pExerState; + + pExerState = (ExerciserState*) malloc(sizeof(*pExerState)); + if (pExerState == NULL) + return NULL; + + pExerState->pArchive = NULL; + pExerState->archivePath = NULL; + pExerState->archiveFile = NULL; + + return pExerState; +} + +void ExerciserState_Free(ExerciserState* pExerState) +{ + if (pExerState == NULL) + return; + + if (pExerState->pArchive != NULL) { + printf("Exerciser: aborting open archive\n"); + (void) NuAbort(pExerState->pArchive); + (void) NuClose(pExerState->pArchive); + } + if (pExerState->archivePath != NULL) + free(pExerState->archivePath); + + free(pExerState); +} + +inline NuArchive* ExerciserState_GetNuArchive(const ExerciserState* pExerState) +{ + return pExerState->pArchive; +} + +inline void ExerciserState_SetNuArchive(ExerciserState* pExerState, + NuArchive* newArchive) +{ + pExerState->pArchive = newArchive; +} + +inline char* ExerciserState_GetArchivePath(const ExerciserState* pExerState) +{ + return pExerState->archivePath; +} + +inline void ExerciserState_SetArchivePath(ExerciserState* pExerState, + char* newPath) +{ + if (pExerState->archivePath != NULL) + free(pExerState->archivePath); + + if (newPath == NULL) { + pExerState->archivePath = NULL; + pExerState->archiveFile = NULL; + } else { + pExerState->archivePath = strdup(newPath); + pExerState->archiveFile = strrchr(newPath, kFssep); + if (pExerState->archiveFile != NULL) + pExerState->archiveFile++; + + if (pExerState->archiveFile == NULL || *pExerState->archiveFile == '\0') + pExerState->archiveFile = pExerState->archivePath; + } +} + +inline const char* ExerciserState_GetArchiveFile(const ExerciserState* pExerState) +{ + if (pExerState->archiveFile == NULL) + return "[no archive open]"; + else + return pExerState->archiveFile; +} + + +/* + * =========================================================================== + * Utility functions + * =========================================================================== + */ + +/* + * NuContents callback function. Print the contents of an individual record. + */ +NuResult PrintEntry(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (const NuRecord*) vpRecord; + int idx; + + (void)pArchive; /* shut up, gcc */ + + printf("RecordIdx %u: '%s'\n", + pRecord->recordIdx, pRecord->filenameMOR); + + for (idx = 0; idx < (int) pRecord->recTotalThreads; idx++) { + const NuThread* pThread; + NuThreadID threadID; + const char* threadLabel; + + pThread = NuGetThread(pRecord, idx); + assert(pThread != NULL); + + threadID = NuGetThreadID(pThread); + switch (NuThreadIDGetClass(threadID)) { + case kNuThreadClassMessage: + threadLabel = "message class"; + break; + case kNuThreadClassControl: + threadLabel = "control class"; + break; + case kNuThreadClassData: + threadLabel = "data class"; + break; + case kNuThreadClassFilename: + threadLabel = "filename class"; + break; + default: + threadLabel = "(unknown class)"; + break; + } + + switch (threadID) { + case kNuThreadIDComment: + threadLabel = "comment"; + break; + case kNuThreadIDIcon: + threadLabel = "icon"; + break; + case kNuThreadIDMkdir: + threadLabel = "mkdir"; + break; + case kNuThreadIDDataFork: + threadLabel = "data fork"; + break; + case kNuThreadIDDiskImage: + threadLabel = "disk image"; + break; + case kNuThreadIDRsrcFork: + threadLabel = "rsrc fork"; + break; + case kNuThreadIDFilename: + threadLabel = "filename"; + break; + default: + break; + } + + printf(" ThreadIdx %u - 0x%08x (%s)\n", pThread->threadIdx, + threadID, threadLabel); + } + + return kNuOK; +} + + +#define kNiceLineLen 256 + +/* + * Get a line of input, stripping the '\n' off the end. + */ +static NuError GetLine(const char* prompt, char* buffer, int bufferSize) +{ + printf("%s> ", prompt); + fflush(stdout); + + if (fgets(buffer, bufferSize, stdin) == NULL) + return kNuErrGeneric; + + if (buffer[strlen(buffer)-1] == '\n') + buffer[strlen(buffer)-1] = '\0'; + + return kNuErrNone; +} + + +/* + * Selection filter for mass "extract" and "delete" operations. + */ +NuResult SelectionFilter(NuArchive* pArchive, void* vselFilt) +{ + const NuSelectionProposal* selProposal = (NuSelectionProposal*) vselFilt; + char buffer[8]; + + printf("%s (N/y)? ", selProposal->pRecord->filenameMOR); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return kNuAbort; + + if (tolower(buffer[0]) == 'y') + return kNuOK; + else + return kNuSkip; +} + + +/* + * General-purpose error handler. + */ +NuResult ErrorHandler(NuArchive* pArchive, void* vErrorStatus) +{ + const NuErrorStatus* pErrorStatus = (const NuErrorStatus*) vErrorStatus; + char buffer[8]; + NuResult result = kNuSkip; + + printf("Exerciser: error handler op=%d err=%d sysErr=%d message='%s'\n" + "\tfilename='%s' '%c'(0x%02x)\n", + pErrorStatus->operation, pErrorStatus->err, pErrorStatus->sysErr, + pErrorStatus->message == NULL ? "(NULL)" : pErrorStatus->message, + pErrorStatus->pathnameUNI, pErrorStatus->filenameSeparator, + pErrorStatus->filenameSeparator); + printf("\tValid options are:"); + if (pErrorStatus->canAbort) + printf(" a)bort"); + if (pErrorStatus->canRetry) + printf(" r)etry"); + if (pErrorStatus->canIgnore) + printf(" i)gnore"); + if (pErrorStatus->canSkip) + printf(" s)kip"); + if (pErrorStatus->canRename) + printf(" re)name"); + if (pErrorStatus->canOverwrite) + printf(" o)verwrite"); + putc('\n', stdout); + + printf("Return what (a/r/i/s/e/o)? "); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + printf("Returning kNuSkip\n"); + } else switch (buffer[0]) { + case 'a': result = kNuAbort; break; + case 'r': result = kNuRetry; break; + case 'i': result = kNuIgnore; break; + case 's': result = kNuSkip; break; + case 'e': result = kNuRename; break; + case 'o': result = kNuOverwrite; break; + default: + printf("Unknown value '%c', returning kNuSkip\n", buffer[0]); + break; + } + + return result; +} + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + + +/* + * =========================================================================== + * Command handlers + * =========================================================================== + */ + +typedef NuError (*CommandFunc)(ExerciserState* pState, int argc, + char** argv); + +static NuError HelpFunc(ExerciserState* pState, int argc, char** argv); + +#if 0 +static NuError +GenericFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + printf("Generic! argc=%d\n", argc); + return kNuErrNone; +} +#endif + +/* + * Do nothing. Useful when the user just hits on a blank line. + */ +static NuError NothingFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + return kNuErrNone; +} + +/* + * q - quit + * + * Do nothing. This is used as a trigger for quitting the program. In + * practice, we catch this earlier, and won't actually call here. + */ +static NuError QuitFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(0); + return kNuErrNone; +} + + + +/* + * ab - abort current changes + */ +static NuError AbortFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + return NuAbort(ExerciserState_GetNuArchive(pState)); +} + +/* + * af - add file to archive + */ +static NuError AddFileFunc(ExerciserState* pState, int argc, char** argv) +{ + NuFileDetails nuFileDetails; + int fromRsrc = false; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 3); + + if (strcasecmp(argv[2], "true") == 0) { + fromRsrc = true; + } else if (strcasecmp(argv[2], "false") != 0) { + fprintf(stderr, "WARNING: fromRsrc should be 'true' or 'false'\n"); + /* ignore */ + } + + memset(&nuFileDetails, 0, sizeof(nuFileDetails)); + if (fromRsrc) { + nuFileDetails.threadID = kNuThreadIDRsrcFork; + } else { + nuFileDetails.threadID = kNuThreadIDDataFork; + } + nuFileDetails.storageNameMOR = argv[1]; + nuFileDetails.fileSysID = kNuFileSysUnknown; + nuFileDetails.fileSysInfo = (short) kFssep; + nuFileDetails.access = kUnlocked; + /* fileType, extraType, storageType, dates */ + + return NuAddFile(ExerciserState_GetNuArchive(pState), argv[1], + &nuFileDetails, fromRsrc, NULL); +} + +/* + * ar - add an empty record + */ +static NuError AddRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuRecordIdx recordIdx; + NuFileDetails nuFileDetails; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + memset(&nuFileDetails, 0, sizeof(nuFileDetails)); + nuFileDetails.threadID = 0; /* irrelevant */ + nuFileDetails.storageNameMOR = argv[1]; + nuFileDetails.fileSysID = kNuFileSysUnknown; + nuFileDetails.fileSysInfo = (short) kFssep; + nuFileDetails.access = kUnlocked; + /* fileType, extraType, storageType, dates */ + + err = NuAddRecord(ExerciserState_GetNuArchive(pState), + &nuFileDetails, &recordIdx); + if (err == kNuErrNone) + printf("Exerciser: success, new recordIdx=%u\n", recordIdx); + return err; +} + +/* + * at - add thread to record + */ +static NuError AddThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuDataSource* pDataSource = NULL; + char* lineBuf = NULL; + long ourLen, maxLen; + NuThreadID threadID; + NuThreadIdx threadIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 3); + + lineBuf = (char*)malloc(kNiceLineLen); + assert(lineBuf != NULL); + + threadID = strtol(argv[2], NULL, 0); + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) { + /* load data from a file on disk */ + maxLen = 0; + err = GetLine("Enter filename", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + if (!lineBuf[0]) { + fprintf(stderr, "Invalid filename\n"); + err = kNuErrInvalidArg; + goto bail; + } + + err = NuCreateDataSourceForFile(kNuThreadFormatUncompressed, + 0, lineBuf, false, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "Exerciser: file data source create failed (err=%d)\n", err); + goto bail; + } + } else { + if (threadID == kNuThreadIDFilename || threadID == kNuThreadIDComment) { + /* select the buffer pre-size */ + err = GetLine("Enter max buffer size", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + maxLen = strtol(lineBuf, NULL, 0); + if (maxLen <= 0) { + fprintf(stderr, "Bad length\n"); + err = kNuErrInvalidArg; + goto bail; + } + } else { + maxLen = 0; + } + + err = GetLine("Enter the thread contents", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + ourLen = strlen(lineBuf); + + /* create a data source from the buffer */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + maxLen, (uint8_t*)lineBuf, 0, ourLen, FreeCallback, + &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "Exerciser: buffer data source create failed (err=%d)\n", err); + goto bail; + } + lineBuf = NULL; /* now owned by the library */ + } + + + err = NuAddThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), threadID, pDataSource, &threadIdx); + if (err == kNuErrNone) { + pDataSource = NULL; /* library owns it now */ + printf("Exerciser: success; function returned threadIdx=%u\n", + threadIdx); + } + +bail: + NuFreeDataSource(pDataSource); + if (lineBuf != NULL) + free(lineBuf); + return err; +} + +/* + * cl - close archive + */ +static NuError CloseFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + err = NuClose(ExerciserState_GetNuArchive(pState)); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, NULL); + ExerciserState_SetArchivePath(pState, NULL); + } + + return err; +} + +/* + * d - delete all records (selection-filtered) + */ +static NuError DeleteFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), SelectionFilter); + + return NuDelete(ExerciserState_GetNuArchive(pState)); +} + +/* + * dr - delete record + */ +static NuError DeleteRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + return NuDeleteRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0)); +} + +/* + * dt - delete thread + */ +static NuError DeleteThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + return NuDeleteThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0)); +} + +/* + * e - extract all files (selection-filtered) + */ +static NuError ExtractFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), SelectionFilter); + + return NuExtract(ExerciserState_GetNuArchive(pState)); +} + +/* + * er - extract record + */ +static NuError ExtractRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + return NuExtractRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0)); +} + +/* + * et - extract thread + */ +static NuError ExtractThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuDataSink* pDataSink = NULL; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 3); + + err = NuCreateDataSinkForFile(true, kNuConvertOff, argv[2], kFssep, + &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "Exerciser: data sink create failed\n"); + goto bail; + } + + err = NuExtractThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), pDataSink); + /* fall through with err */ + +bail: + NuFreeDataSink(pDataSink); + return err; +} + +/* + * fl - flush changes to archive + */ +static NuError FlushFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + uint32_t flushStatus; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + err = NuFlush(ExerciserState_GetNuArchive(pState), &flushStatus); + if (err != kNuErrNone) + printf("Exerciser: flush failed, status flags=0x%04x\n", flushStatus); + return err; +} + +/* + * gev - get value + * + * Currently takes numeric arguments. We could be nice and accept the + * things like "IgnoreCRC" for kNuValueIgnoreCRC, but not yet. + */ +static NuError GetValueFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuValue value; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + err = NuGetValue(ExerciserState_GetNuArchive(pState), + (NuValueID) strtol(argv[1], NULL, 0), &value); + if (err == kNuErrNone) + printf(" --> %u\n", value); + return err; +} + +/* + * gmh - get master header + */ +static NuError GetMasterHeaderFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + const NuMasterHeader* pMasterHeader; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + err = NuGetMasterHeader(ExerciserState_GetNuArchive(pState), + &pMasterHeader); + if (err == kNuErrNone) { + printf("Exerciser: success (version=%u, totalRecords=%u, EOF=%u)\n", + pMasterHeader->mhMasterVersion, pMasterHeader->mhTotalRecords, + pMasterHeader->mhMasterEOF); + } + return err; +} + +/* + * gr - get record attributes + */ +static NuError GetRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + const NuRecord* pRecord; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + err = NuGetRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), &pRecord); + if (err == kNuErrNone) { + printf("Exerciser: success, call returned:\n"); + printf("\tfileSysID : %d\n", pRecord->recFileSysID); + printf("\tfileSysInfo : 0x%04x ('%c')\n", pRecord->recFileSysInfo, + NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + printf("\taccess : 0x%02x\n", pRecord->recAccess); + printf("\tfileType : 0x%04x\n", pRecord->recFileType); + printf("\textraType : 0x%04x\n", pRecord->recExtraType); + printf("\tcreateWhen : ...\n"); + printf("\tmodWhen : ...\n"); /* too lazy */ + printf("\tarchiveWhen : ...\n"); + } + return err; +} + +/* + * grin - get record idx by name + */ +static NuError GetRecordIdxByNameFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + NuRecordIdx recIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + err = NuGetRecordIdxByName(ExerciserState_GetNuArchive(pState), + argv[1], &recIdx); + if (err == kNuErrNone) + printf("Exerciser: success, returned recordIdx=%u\n", recIdx); + return err; +} + +/* + * grip - get record idx by position + */ +static NuError GetRecordIdxByPositionFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + NuRecordIdx recIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + err = NuGetRecordIdxByPosition(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), &recIdx); + if (err == kNuErrNone) + printf("Exerciser: success, returned recordIdx=%u\n", recIdx); + return err; +} + +/* + * ocrw - open/create read-write + */ +static NuError OpenCreateReadWriteFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == NULL); + assert(argc == 2); + + err = NuOpenRW(argv[1], kTempFile, kNuOpenCreat|kNuOpenExcl, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * oro - open read-only + */ +static NuError OpenReadOnlyFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == NULL); + assert(argc == 2); + + err = NuOpenRO(argv[1], &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * ors - open streaming read-only + */ +static NuError OpenStreamingReadOnlyFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + NuArchive* pArchive; + FILE* fp = NULL; + + assert(ExerciserState_GetNuArchive(pState) == NULL); + assert(argc == 2); + + if ((fp = fopen(argv[1], kNuFileOpenReadOnly)) == NULL) { + err = errno ? (NuError)errno : kNuErrGeneric; + fprintf(stderr, "Exerciser: unable to open '%s'\n", argv[1]); + } else { + err = NuStreamOpenRO(fp, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + fp = NULL; + } + } + + if (fp != NULL) + fclose(fp); + + return err; +} + +/* + * orw - open read-write + */ +static NuError OpenReadWriteFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == NULL); + assert(argc == 2); + + err = NuOpenRW(argv[1], kTempFile, 0, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * p - print + */ +static NuError PrintFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + return NuContents(ExerciserState_GetNuArchive(pState), PrintEntry); +} + +/* + * pd - print debug + */ +static NuError PrintDebugFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + return NuDebugDumpArchive(ExerciserState_GetNuArchive(pState)); +} + +/* + * re - rename record + */ +static NuError RenameFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 4); + + return NuRename(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), argv[2], argv[3][0]); +} + +/* + * sec - set error callback + * + * Use an error handler callback. + */ +static NuError SetErrorCallbackFunc(ExerciserState* pState, int argc, + char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + NuSetErrorHandler(ExerciserState_GetNuArchive(pState), ErrorHandler); + return kNuErrNone; +} + +/* + * sev - set value + * + * Currently takes numeric arguments. + */ +static NuError SetValueFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 3); + + return NuSetValue(ExerciserState_GetNuArchive(pState), + (NuValueID) strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0)); +} + +/* + * sra - set record attributes + * + * Right now I'm only allowing changes to file type and aux type. This + * could be adapted to do more easily, but the command handler has a + * rigid notion of how many arguments each function should have, so + * you'd need to list all of them every time. + */ +static NuError SetRecordAttrFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + const NuRecord* pRecord; + NuRecordAttr recordAttr; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 4); + + err = NuGetRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), &pRecord); + if (err != kNuErrNone) + return err; + printf("Exerciser: NuGetRecord succeeded, calling NuSetRecordAttr\n"); + NuRecordCopyAttr(&recordAttr, pRecord); + recordAttr.fileType = strtol(argv[2], NULL, 0); + recordAttr.extraType = strtol(argv[3], NULL, 0); + /*recordAttr.fileSysInfo = ':';*/ + return NuSetRecordAttr(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), &recordAttr); +} + +/* + * t - test archive + */ +static NuError TestFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 1); + + return NuTest(ExerciserState_GetNuArchive(pState)); +} + +/* + * tr - test record + */ +static NuError TestRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + return NuTestRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0)); +} + +/* + * upt - update pre-sized thread + */ +static NuError UpdatePresizedThreadFunc(ExerciserState* pState, int argc, + char** argv) +{ + NuError err; + NuDataSource* pDataSource = NULL; + char* lineBuf = NULL; + long ourLen; + int32_t maxLen; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != NULL); + assert(argc == 2); + + lineBuf = (char*)malloc(kNiceLineLen); + assert(lineBuf != NULL); + err = GetLine("Enter data for thread", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + + ourLen = strlen(lineBuf); + + /* use "ourLen" for both buffer len and data len */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + ourLen, (uint8_t*)lineBuf, 0, ourLen, FreeCallback, + &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, "Exerciser: data source create failed (err=%d)\n", + err); + goto bail; + } + lineBuf = NULL; /* now owned by the library */ + + err = NuUpdatePresizedThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], NULL, 0), pDataSource, &maxLen); + if (err == kNuErrNone) + printf("Exerciser: success; function returned maxLen=%d\n", maxLen); + +bail: + NuFreeDataSource(pDataSource); + if (lineBuf != NULL) + free(lineBuf); + return err; +} + + +/* + * Command table. This drives the user interface. + */ + +/* flags for the CommandTable */ +#define kFlagArchiveReq (1L) /* must have archive open */ +#define kFlagNoArchiveReq (1L<<1) /* must NOT have archive open */ + +/* command set */ +static const struct { + const char* commandStr; + CommandFunc func; + int expectedArgCount; + const char* argumentList; + uint32_t flags; + const char* description; +} gCommandTable[] = { + { "--- exerciser commands ---", HelpFunc, 0, "", 0, + "" }, + { "?", HelpFunc, 0, "", 0, + "Show help" }, + { "h", HelpFunc, 0, "", 0, + "Show help" }, + { "q", QuitFunc, 0, "", 0, + "Quit program (will abort un-flushed changes)" }, + + { "--- archive commands ---", HelpFunc, 0, "", 0, + "" }, + + { "ab", AbortFunc, 0, "", kFlagArchiveReq, + "Abort current changes" }, + { "af", AddFileFunc, 2, "filename fromRsrc", kFlagArchiveReq, + "Add file" }, + { "ar", AddRecordFunc, 1, "storageName", kFlagArchiveReq, + "Add record" }, + { "at", AddThreadFunc, 2, "recordIdx threadID", kFlagArchiveReq, + "Add thread to record" }, + { "cl", CloseFunc, 0, "", kFlagArchiveReq, + "Close archive after flushing any changes" }, + { "d", DeleteFunc, 0, "", kFlagArchiveReq, + "Delete all records" }, + { "dr", DeleteRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Delete record" }, + { "dt", DeleteThreadFunc, 1, "threadIdx", kFlagArchiveReq, + "Delete thread" }, + { "e", ExtractFunc, 0, "", kFlagArchiveReq, + "Extract all files" }, + { "er", ExtractRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Extract record" }, + { "et", ExtractThreadFunc, 2, "threadIdx filename", kFlagArchiveReq, + "Extract thread" }, + { "fl", FlushFunc, 0, "", kFlagArchiveReq, + "Flush changes" }, + { "gev", GetValueFunc, 1, "ident", kFlagArchiveReq, + "Get value" }, + { "gmh", GetMasterHeaderFunc, 0, "", kFlagArchiveReq, + "Get master header" }, + { "gr", GetRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Get record" }, + { "grin", GetRecordIdxByNameFunc, 1, "name", kFlagArchiveReq, + "Get recordIdx by name" }, + { "grip", GetRecordIdxByPositionFunc, 1, "position", kFlagArchiveReq, + "Get recordIdx by position" }, + { "ocrw", OpenCreateReadWriteFunc, 1, "filename", kFlagNoArchiveReq, + "Open/create archive read-write" }, + { "oro", OpenReadOnlyFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive read-only" }, + { "ors", OpenStreamingReadOnlyFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive streaming read-only" }, + { "orw", OpenReadWriteFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive read-write" }, + { "p", PrintFunc, 0, "", kFlagArchiveReq, + "Print archive contents" }, + { "pd", PrintDebugFunc, 0, "", kFlagArchiveReq, + "Print debugging output (if available)" }, + { "re", RenameFunc, 3, "recordIdx name sep", kFlagArchiveReq, + "Rename record" }, + { "sec", SetErrorCallbackFunc, 0, "", kFlagArchiveReq, + "Set error callback" }, + { "sev", SetValueFunc, 2, "ident value", kFlagArchiveReq, + "Set value" }, + { "sra", SetRecordAttrFunc, 3, "recordIdx type aux", kFlagArchiveReq, + "Set record attributes" }, + { "t", TestFunc, 0, "", kFlagArchiveReq, + "Test archive" }, + { "tr", TestRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Test record" }, + { "upt", UpdatePresizedThreadFunc, 1, "threadIdx", kFlagArchiveReq, + "Update pre-sized thread" }, +}; + +#define kMaxArgs 4 + +/* + * Display a summary of available commands. + */ +static NuError HelpFunc(ExerciserState* pState, int argc, char** argv) +{ + int i; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + + printf("\nAvailable commands:\n"); + for (i = 0; i < (int)NELEM(gCommandTable); i++) { + printf(" %-4s %-21s %s\n", + gCommandTable[i].commandStr, + gCommandTable[i].argumentList, + gCommandTable[i].description); + } + + return kNuErrNone; +} + + +/* + * =========================================================================== + * Control + * =========================================================================== + */ + +static const char* kWhitespace = " \t\n"; + +/* + * Parse a command from the user. + * + * "lineBuf" will be mangled. On success, "pFunc", "pArgc", and "pArgv" + * will receive the results. + */ +static NuError ParseLine(char* lineBuf, ExerciserState* pState, + CommandFunc* pFunc, int* pArgc, char*** pArgv) +{ + NuError err = kNuErrSyntax; + char* command; + char* cp; + int i; + + /* + * Parse the strings. + */ + + command = strtok(lineBuf, kWhitespace); + if (command == NULL) { + /* no command; the user probably just hit "enter" on a blank line */ + *pFunc = NothingFunc; + *pArgc = 0; + *pArgv = NULL; + err = kNuErrNone; + goto bail; + } + + /* no real need to be flexible; add 1 for command and one for NULL */ + *pArgv = (char**) malloc(sizeof(char*) * (kMaxArgs+2)); + (*pArgv)[0] = command; + *pArgc = 1; + + cp = strtok(NULL, kWhitespace); + while (cp != NULL) { + if (*pArgc >= kMaxArgs+1) { + printf("ERROR: too many arguments\n"); + goto bail; + } + (*pArgv)[*pArgc] = cp; + (*pArgc)++; + + cp = strtok(NULL, kWhitespace); + } + assert(*pArgc < kMaxArgs+2); + (*pArgv)[*pArgc] = NULL; + + /* + * Look up the command. + */ + for (i = 0; i < (int)NELEM(gCommandTable); i++) { + if (strcmp(command, gCommandTable[i].commandStr) == 0) + break; + } + if (i == NELEM(gCommandTable)) { + printf("ERROR: unrecognized command\n"); + goto bail; + } + + *pFunc = gCommandTable[i].func; + + /* + * Check arguments and flags. + */ + if (*pArgc -1 != gCommandTable[i].expectedArgCount) { + printf("ERROR: expected %d args, found %d\n", + gCommandTable[i].expectedArgCount, *pArgc -1); + goto bail; + } + + if (gCommandTable[i].flags & kFlagArchiveReq) { + if (ExerciserState_GetNuArchive(pState) == NULL) { + printf("ERROR: must have an archive open\n"); + goto bail; + } + } + if (gCommandTable[i].flags & kFlagNoArchiveReq) { + if (ExerciserState_GetNuArchive(pState) != NULL) { + printf("ERROR: an archive is already open\n"); + goto bail; + } + } + + /* + * Looks good! + */ + err = kNuErrNone; + +bail: + return err; +} + + +/* + * Interpret commands, do clever things. + */ +static NuError CommandLoop(void) +{ + NuError err = kNuErrNone; + ExerciserState* pState = ExerciserState_New(); + CommandFunc func; + char lineBuf[128]; + int argc; + char** argv = NULL; + + while (1) { + printf("\nEnter command (%s)> ", ExerciserState_GetArchiveFile(pState)); + fflush(stdout); + + if (fgets(lineBuf, sizeof(lineBuf), stdin) == NULL) { + printf("\n"); + break; + } + + if (argv != NULL) { + free(argv); + argv = NULL; + } + + func = NULL; /* sanity check */ + + err = ParseLine(lineBuf, pState, &func, &argc, &argv); + if (err != kNuErrNone) + continue; + + assert(func != NULL); + if (func == QuitFunc) + break; + + err = (*func)(pState, argc, argv); + + if (err < 0) + printf("Exerciser: received error %d (%s)\n", err, NuStrError(err)); + else if (err > 0) + printf("Exerciser: received error %d\n", err); + + if (argv != NULL) { + free(argv); + argv = NULL; + } + } + + if (ExerciserState_GetNuArchive(pState) != NULL) { + /* ought to query the archive before saying something like this... */ + printf("Exerciser: aborting any un-flushed changes in archive %s\n", + ExerciserState_GetArchivePath(pState)); + (void) NuAbort(ExerciserState_GetNuArchive(pState)); + err = NuClose(ExerciserState_GetNuArchive(pState)); + if (err != kNuErrNone) + printf("Exerciser: got error %d closing archive\n", err); + ExerciserState_SetNuArchive(pState, NULL); + } + + if (pState != NULL) + ExerciserState_Free(pState); + if (argv != NULL) + free(argv); + return kNuErrNone; +} + + +/* + * Main entry point. + * + * We don't currently take any arguments, so this is pretty straightforward. + */ +int main(void) +{ + NuError result; + int32_t majorVersion, minorVersion, bugVersion; + const char* nufxLibDate; + const char* nufxLibFlags; + + (void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion, + &nufxLibDate, &nufxLibFlags); + printf("NufxLib exerciser, linked with NufxLib v%d.%d.%d [%s]\n\n", + majorVersion, minorVersion, bugVersion, nufxLibFlags); + printf("Use 'h' or '?' for help, 'q' to quit.\n"); + + /* stuff useful when debugging lots */ + if (unlink(kTempFile) == 0) + fprintf(stderr, "NOTE: whacked exer-temp\n"); + if (unlink("new.shk") == 0) + fprintf(stderr, "NOTE: whacked new.shk\n"); + +#if defined(HAS_MALLOC_CHECK_) && !defined(USE_DMALLOC) + /* + * This is really nice to have on Linux and any other system that + * uses the GNU libc/malloc stuff. It slows things down, but it + * tells you when you do something dumb with malloc/realloc/free. + * (Solaris 2.7 has a similar feature that is enabled by setting the + * environment variable LD_PRELOAD to include watchmalloc.so. Other + * OSs and 3rd-party malloc packages may have similar features.) + * + * This environment variable must be set when the program is launched. + * Tweaking the environment within the program has no effect. + * + * Now that the Linux world has "valgrind", this is probably + * unnecessary. + */ + { + char* debugSet = getenv("MALLOC_CHECK_"); + if (debugSet == NULL) + printf("WARNING: MALLOC_CHECK_ not enabled\n\n"); + } +#endif + + result = CommandLoop(); + + exit(result != kNuErrNone); +} + diff --git a/nufxlib/samples/ImgConv.c b/nufxlib/samples/ImgConv.c new file mode 100644 index 0000000..d9624b2 --- /dev/null +++ b/nufxlib/samples/ImgConv.c @@ -0,0 +1,642 @@ +/* + * 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. + * + * 2IMG <-> SHK converter. This is a practical example of using the + * NufxLib Thread functions to add and extract data in the middle of a file. + * + * Conversions from 2IMG files do not work for raw nibble images. + * Conversions from SHK archives only work if the disk image is in the + * first record in the archive. (This is easy to fix, but I'm trying to + * keep it simple.) + */ +#include "NufxLib.h" +#include "Common.h" + +#ifndef HAVE_STRCASECMP +static int strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} +#endif + + +#define kTempFile "imgconv.tmp" +#define kLocalFssep PATH_SEP +#define false 0 +#define true (!false) + + +/* + * =========================================================================== + * 2IMG stuff + * =========================================================================== + */ + +#define kImgMagic "2IMG" +#define kMyCreator "NFXL" + +#define kImageFormatDOS 0 +#define kImageFormatProDOS 1 +#define kImageFormatNibble 2 + +/* + * 2IMG header definition (http://www.magnet.ch/emutech/Tech/). + */ +typedef struct ImgHeader { + char magic[4]; + char creator[4]; + uint16_t headerLen; + uint16_t version; + uint32_t imageFormat; + uint32_t flags; + uint32_t numBlocks; + uint32_t dataOffset; + uint32_t dataLen; + uint32_t cmntOffset; + uint32_t cmntLen; + uint32_t creatorOffset; + uint32_t creatorLen; + uint32_t spare[4]; +} ImgHeader; + +/* + * Read a two-byte little-endian value. + */ +void ReadShortLE(FILE* fp, uint16_t* pBuf) +{ + *pBuf = getc(fp); + *pBuf += (uint16_t) getc(fp) << 8; +} + +/* + * Write a two-byte little-endian value. + */ +void WriteShortLE(FILE* fp, uint16_t val) +{ + putc(val, fp); + putc(val >> 8, fp); +} + +/* + * Read a four-byte little-endian value. + */ +void ReadLongLE(FILE* fp, uint32_t* pBuf) +{ + *pBuf = getc(fp); + *pBuf += (uint32_t) getc(fp) << 8; + *pBuf += (uint32_t) getc(fp) << 16; + *pBuf += (uint32_t) getc(fp) << 24; +} + +/* + * Write a four-byte little-endian value. + */ +void WriteLongLE(FILE* fp, uint32_t val) +{ + putc(val, fp); + putc(val >> 8, fp); + putc(val >> 16, fp); + putc(val >> 24, fp); +} + +/* + * Read the header from a 2IMG file. + */ +int ReadImgHeader(FILE* fp, ImgHeader* pHeader) +{ + size_t ignored; + ignored = fread(pHeader->magic, 4, 1, fp); + ignored = fread(pHeader->creator, 4, 1, fp); + ReadShortLE(fp, &pHeader->headerLen); + ReadShortLE(fp, &pHeader->version); + ReadLongLE(fp, &pHeader->imageFormat); + ReadLongLE(fp, &pHeader->flags); + ReadLongLE(fp, &pHeader->numBlocks); + ReadLongLE(fp, &pHeader->dataOffset); + ReadLongLE(fp, &pHeader->dataLen); + ReadLongLE(fp, &pHeader->cmntOffset); + ReadLongLE(fp, &pHeader->cmntLen); + ReadLongLE(fp, &pHeader->creatorOffset); + ReadLongLE(fp, &pHeader->creatorLen); + ReadLongLE(fp, &pHeader->spare[0]); + ReadLongLE(fp, &pHeader->spare[1]); + ReadLongLE(fp, &pHeader->spare[2]); + ReadLongLE(fp, &pHeader->spare[3]); + + (void) ignored; + if (feof(fp) || ferror(fp)) + return -1; + + if (strncmp(pHeader->magic, kImgMagic, 4) != 0) { + fprintf(stderr, "ERROR: bad magic number on 2IMG file\n"); + return -1; + } + + if (pHeader->version > 1) { + fprintf(stderr, "WARNING: might not be able to handle version=%d\n", + pHeader->version); + } + + return 0; +} + +/* + * Write the header to a 2IMG file. + */ +int WriteImgHeader(FILE* fp, ImgHeader* pHeader) +{ + fwrite(pHeader->magic, 4, 1, fp); + fwrite(pHeader->creator, 4, 1, fp); + WriteShortLE(fp, pHeader->headerLen); + WriteShortLE(fp, pHeader->version); + WriteLongLE(fp, pHeader->imageFormat); + WriteLongLE(fp, pHeader->flags); + WriteLongLE(fp, pHeader->numBlocks); + WriteLongLE(fp, pHeader->dataOffset); + WriteLongLE(fp, pHeader->dataLen); + WriteLongLE(fp, pHeader->cmntOffset); + WriteLongLE(fp, pHeader->cmntLen); + WriteLongLE(fp, pHeader->creatorOffset); + WriteLongLE(fp, pHeader->creatorLen); + WriteLongLE(fp, pHeader->spare[0]); + WriteLongLE(fp, pHeader->spare[1]); + WriteLongLE(fp, pHeader->spare[2]); + WriteLongLE(fp, pHeader->spare[3]); + + if (ferror(fp)) + return -1; + + return 0; +} + + +/* + * Dump the contents of an ImgHeader. + */ +void DumpImgHeader(ImgHeader* pHeader) +{ + printf("--- header contents:\n"); + printf("\tmagic = '%.4s'\n", pHeader->magic); + printf("\tcreator = '%.4s'\n", pHeader->creator); + printf("\theaderLen = %d\n", pHeader->headerLen); + printf("\tversion = %d\n", pHeader->version); + printf("\timageFormat = %u\n", pHeader->imageFormat); + printf("\tflags = 0x%08x\n", pHeader->flags); + printf("\tnumBlocks = %u\n", pHeader->numBlocks); + printf("\tdataOffset = %u\n", pHeader->dataOffset); + printf("\tdataLen = %u\n", pHeader->dataLen); + printf("\tcmntOffset = %u\n", pHeader->cmntOffset); + printf("\tcmntLen = %u\n", pHeader->cmntLen); + printf("\tcreatorOffset = %u\n", pHeader->creatorOffset); + printf("\tcreatorLen = %u\n", pHeader->creatorLen); + printf("\n"); +} + + +/* + * =========================================================================== + * Main functions + * =========================================================================== + */ + +typedef enum ArchiveKind { kKindUnknown, kKindShk, kKindImg } ArchiveKind; + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + +/* + * This gets called when an "FP" DataSource is no longer needed. + */ +NuResult FcloseCallback(NuArchive* pArchive, void* args) +{ + fclose((FILE*) args); + return kNuOK; +} + +/* + * Create a data source for a ProDOS-ordered image. Since this is already + * in the correct format, we just point at the correct offset in the 2MG file. + * + * This supplies an FcloseCallback so that we can exercise that feature + * of NufxLib. We could just as easily not set it and call fclose() + * ourselves, because the structure of this program is pretty simple. + */ +NuError CreateProdosSource(const ImgHeader* pHeader, FILE* fp, + NuDataSource** ppDataSource) +{ + return NuCreateDataSourceForFP(kNuThreadFormatUncompressed, 0, fp, + pHeader->dataOffset, pHeader->dataLen, FcloseCallback,ppDataSource); +} + +/* + * Create a data source for a DOS-ordered image. This is a little harder, + * since we have to reorder the blocks into ProDOS ordering for ShrinkIt. + */ +NuError CreateDosSource(const ImgHeader* pHeader, FILE* fp, + NuDataSource** ppDataSource) +{ + NuError err; + char* diskBuffer = NULL; + long offset; + + if (pHeader->dataLen % 4096) { + fprintf(stderr, + "ERROR: image size must be multiple of 4096 (%u isn't)\n", + pHeader->dataLen); + err = kNuErrGeneric; + goto bail; + } + + if (fseek(fp, pHeader->dataOffset, SEEK_SET) < 0) { + err = errno; + perror("fseek failed"); + goto bail; + } + + diskBuffer = malloc(pHeader->dataLen); + if (diskBuffer == NULL) { + fprintf(stderr, "ERROR: malloc(%u) failed\n", pHeader->dataLen); + err = kNuErrMalloc; + goto bail; + } + + /* + * Run through the image, reordering each track. This is a + * reversible transformation, i.e. if you do this twice you're back + * to ProDOS ordering. + */ + for (offset = 0; offset < (long) pHeader->dataLen; offset += 4096) { + size_t ignored; + ignored = fread(diskBuffer + offset + 0x0000, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0e00, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0d00, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0c00, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0b00, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0a00, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0900, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0800, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0700, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0600, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0500, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0400, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0300, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0200, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0100, 256, 1, fp); + ignored = fread(diskBuffer + offset + 0x0f00, 256, 1, fp); + (void) ignored; + } + if (feof(fp) || ferror(fp)) { + err = errno ? errno : -1; + fprintf(stderr, "ERROR: failed while reading source file\n"); + goto bail; + } + + /* + * Create a data source for the buffer. We set the "doClose" flag to + * "true", so NufxLib will free the buffer for us. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0, + (const uint8_t*) diskBuffer, 0, pHeader->dataLen, + FreeCallback, ppDataSource); + if (err == kNuErrNone) + diskBuffer = NULL; + +bail: + if (diskBuffer != NULL) + free(diskBuffer); + return err; +} + + + +/* + * Convert a 2IMG file into a new SHK archive. + * + * This requires opening up the 2IMG file, verifying that it's okay, and + * then creating a new disk image record and thread. + */ +int ConvertFromImgToShk(const char* srcName, const char* dstName) +{ + NuError err; + NuArchive* pArchive = NULL; + NuDataSource* pDataSource = NULL; + NuRecordIdx recordIdx; + NuFileDetails fileDetails; + ImgHeader header; + FILE* fp = NULL; + uint32_t flushStatus; + char* storageName = NULL; + char* cp; + + printf("Converting 2IMG file '%s' to ShrinkIt archive '%s'\n\n", + srcName, dstName); + + err = kNuErrGeneric; + + fp = fopen(srcName, kNuFileOpenReadOnly); + if (fp == NULL) { + perror("fopen failed"); + goto bail; + } + + if (ReadImgHeader(fp, &header) < 0) { + fprintf(stderr, "ERROR: header read failed\n"); + goto bail; + } + + DumpImgHeader(&header); + + if (header.imageFormat != kImageFormatDOS && + header.imageFormat != kImageFormatProDOS) + { + fprintf(stderr, "ERROR: can only handle DOS and ProDOS images\n"); + goto bail; + } + + if (header.numBlocks > 1600) + printf("WARNING: that's a big honking image!\n"); + + /* + * Open a new archive read-write. This refuses to overwrite an + * existing file. + */ + (void) unlink(kTempFile); + err = NuOpenRW(dstName, kTempFile, kNuOpenCreat|kNuOpenExcl, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create archive (err=%d)\n", err); + goto bail; + } + + /* create the name that will be stored in the archive */ + storageName = strdup(dstName); + cp = strrchr(storageName, '.'); + if (cp != NULL) + *cp = '\0'; + cp = strrchr(storageName, kLocalFssep); + if (cp != NULL && *(cp+1) != '\0') + cp++; + else + cp = storageName; + + /* + * We can't say "add file", because NufxLib doesn't know what a 2MG + * archive is. However, we can point a DataSource at the data in + * the file, and construct the record manually. + */ + + /* set up the contents of the NuFX Record */ + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageNameMOR = cp; + fileDetails.fileSysID = kNuFileSysUnknown; /* DOS? ProDOS? */ + fileDetails.fileSysInfo = kLocalFssep; + fileDetails.access = kNuAccessUnlocked; + fileDetails.extraType = header.numBlocks; + fileDetails.storageType = 512; + /* FIX - ought to set the file dates */ + + /* add a new record */ + err = NuAddRecord(pArchive, &fileDetails, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create record (err=%d)\n", err); + goto bail; + } + + /* + * Create a data source for the 2IMG file. We do this differently + * for DOS and ProDOS, because we have to rearrange the sector + * ordering for DOS-ordered images (ShrinkIt always uses ProDOS order). + */ + switch (header.imageFormat) { + case kImageFormatDOS: + err = CreateDosSource(&header, fp, &pDataSource); + fp = NULL; + break; + case kImageFormatProDOS: + err = CreateProdosSource(&header, fp, &pDataSource); + fp = NULL; + break; + default: + fprintf(stderr, "How the heck did I get here?"); + err = kNuErrInternal; + goto bail; + } + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + + /* add a disk image thread */ + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDiskImage, pDataSource, + NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create thread (err=%d)\n", err); + goto bail; + } + pDataSource = NULL; /* library owns it now */ + + /* nothing happens until we Flush */ + err = NuFlush(pArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=0x%04x)\n", + err, flushStatus); + goto bail; + } + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: close failed (err=%d)\n", err); + goto bail; + } + pArchive = NULL; + +bail: + if (pArchive != NULL) { + (void)NuAbort(pArchive); + (void)NuClose(pArchive); + } + NuFreeDataSource(pDataSource); + if (storageName != NULL) + free(storageName); + if (fp != NULL) + fclose(fp); + return (err == kNuErrNone) ? 0 : -1; +} + + +/* + * Convert an SHK archive into a 2IMG file. + * + * This takes a simple-minded approach and assumes that the first record + * in the archive has the disk image in it. If it doesn't, we give up. + */ +int ConvertFromShkToImg(const char* srcName, const char* dstName) +{ + NuError err; + NuArchive* pArchive = NULL; + NuDataSink* pDataSink = NULL; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread = NULL; + ImgHeader header; + FILE* fp = NULL; + int idx; + + printf("Converting ShrinkIt archive '%s' to 2IMG file '%s'\n\n", + srcName, dstName); + + /* + * Open the archive. + */ + err = NuOpenRO(srcName, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open archive (err=%d)\n", err); + goto bail; + } + + /* get the first record */ + err = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get first recordIdx (err=%d)\n", err); + goto bail; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get first record (err=%d)\n", err); + goto bail; + } + + /* find a disk image thread */ + for (idx = 0; idx < (int)NuRecordGetNumThreads(pRecord); idx++) { + pThread = NuGetThread(pRecord, idx); + + if (NuGetThreadID(pThread) == kNuThreadIDDiskImage) + break; + } + if (idx == (int)NuRecordGetNumThreads(pRecord)) { + fprintf(stderr, "ERROR: no disk image found in first record\n"); + err = -1; + goto bail; + } + + /* + * Looks good. Open the 2IMG file, and create the header. + */ + if (access(dstName, F_OK) == 0) { + fprintf(stderr, "ERROR: output file already exists\n"); + err = -1; + goto bail; + } + + fp = fopen(dstName, kNuFileOpenWriteTrunc); + if (fp == NULL) { + perror("fopen failed"); + goto bail; + } + + /* set up the 2MG header, based on the NuFX Record */ + memset(&header, 0, sizeof(header)); + memcpy(header.magic, kImgMagic, sizeof(header.magic)); + memcpy(header.creator, kMyCreator, sizeof(header.creator)); + header.headerLen = 64; + header.version = 1; + header.imageFormat = kImageFormatProDOS; /* always ProDOS-order */ + header.numBlocks = pRecord->recExtraType; + header.dataOffset = 64; + /* old versions of ShrinkIt blew the threadEOF, so use NufxLib's "actual" */ + header.dataLen = pThread->actualThreadEOF; + DumpImgHeader(&header); + if (WriteImgHeader(fp, &header) < 0) { + fprintf(stderr, "ERROR: header write failed\n"); + err = -1; + goto bail; + } + + /* + * We want to expand the disk image thread into "fp" at the current + * offset. + */ + err = NuCreateDataSinkForFP(true, kNuConvertOff, fp, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", err); + goto bail; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread (err=%d)\n", err); + goto bail; + } + +bail: + if (pArchive != NULL) + NuClose(pArchive); + NuFreeDataSink(pDataSink); + if (fp != NULL) + fclose(fp); + return (err == kNuErrNone) ? 0 : -1; +} + + +/* + * Figure out what kind of archive this is by looking at the filename. + */ +ArchiveKind DetermineKind(const char* filename) +{ + const char* dot; + + dot = strrchr(filename, '.'); + if (dot == NULL) + return kKindUnknown; + + if (strcasecmp(dot, ".shk") == 0 || strcasecmp(dot, ".sdk") == 0) + return kKindShk; + else if (strcasecmp(dot, ".2mg") == 0) + return kKindImg; + + return kKindUnknown; +} + + + +/* + * Figure out what we want to do. + */ +int main(int argc, char** argv) +{ + ArchiveKind kind; + int cc; + + if (argc != 3) { + fprintf(stderr, "Usage: %s (input.2mg|input.shk) output\n", argv[0]); + exit(2); + } + + kind = DetermineKind(argv[1]); + if (kind == kKindUnknown) { + fprintf(stderr, "ERROR: input name must end in '.shk' or '.2mg'\n"); + exit(2); + } + + if (kind == kKindShk) + cc = ConvertFromShkToImg(argv[1], argv[2]); + else + cc = ConvertFromImgToShk(argv[1], argv[2]); + + if (cc) + fprintf(stderr, "Failed\n"); + else + printf("Done!\n"); + + exit(cc != 0); +} + diff --git a/nufxlib/samples/Launder.c b/nufxlib/samples/Launder.c new file mode 100644 index 0000000..fba4913 --- /dev/null +++ b/nufxlib/samples/Launder.c @@ -0,0 +1,701 @@ +/* + * 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. + * + * Run an archive through the laundry. The net result is a duplicate + * archive that matches the original in most respects. Extracting the + * files from the duplicate will yield the same results as if they were + * extracted from the original, but the duplicate archive may differ + * in subtle ways (e.g. filename threads may be added, data may be + * recompressed). + * + * This demonstrates copying threads around, both with and without + * recompressing, between two archives that are open simultaneously. This + * also tests NufxLib's thread ordering and verifies that you can abort + * frequently with no adverse effects. + * + * NOTE: depending on the options you select, you may need to have enough + * memory to hold the entire uncompressed contents of the original archive. + * The memory requirements are reduced if you use the "copy only" flag, and + * are virtually eliminated if you use "frequent flush". + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + + +#define kTempFile "tmp-laundry" + +#define kFlagCopyOnly (1) +#define kFlagReverseThreads (1 << 1) +#define kFlagFrequentFlush (1 << 2) +#define kFlagFrequentAbort (1 << 3) /* implies FrequentFlush */ +#define kFlagUseTmp (1 << 4) + + +/* + * Globals. + */ +char gSentRecordWarning = false; + + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + +/* + * Copy a thread, expanding and recompressing it. + * + * This assumes the library is configured for compression (it defaults + * to LZW/2, so this is a reasonable assumption). + */ +NuError CopyThreadRecompressed(NuArchive* pInArchive, NuArchive* pOutArchive, + long flags, const NuThread* pThread, long newRecordIdx) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = NULL; + NuDataSink* pDataSink = NULL; + uint8_t* buffer = NULL; + + /* + * Allocate a buffer large enough to hold all the uncompressed data, and + * wrap a data sink around it. + * + * If the thread is zero bytes long, we can skip this part. + */ + if (pThread->actualThreadEOF) { + buffer = malloc(pThread->actualThreadEOF); + if (buffer == NULL) { + err = kNuErrMalloc; + goto bail; + } + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buffer, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", + err); + goto bail; + } + + /* + * Expand the data. For a pre-sized thread, this grabs only the + * interesting part of the buffer. + */ + err = NuExtractThread(pInArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread %u (err=%d)\n", + pThread->threadIdx, err); + goto bail; + } + } + + /* + * The expanded data is in the buffer, now create a data source that + * describes it. + * + * This is complicated by the existence of pre-sized threads, which + * require us to set "otherLen". + * + * We always use "actualThreadEOF" because "thThreadEOF" is broken + * for disk archives created by certain versions of ShrinkIt. + * + * It's okay to pass in a NULL value for "buffer", so long as the + * amount of data in the buffer is also zero. The library will do + * the right thing. + */ + if (NuIsPresizedThreadID(NuGetThreadID(pThread))) { + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + pThread->thCompThreadEOF, buffer, 0, + pThread->actualThreadEOF, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create pre-sized data source (err=%d)\n",err); + goto bail; + } + } else { + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, buffer, 0, pThread->actualThreadEOF, + FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + } + buffer = NULL; /* doClose was set, so it's owned by the data source */ + + /* + * Schedule the data for addition to the record. + */ + err = NuAddThread(pOutArchive, newRecordIdx, NuGetThreadID(pThread), + pDataSource, NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err); + goto bail; + } + pDataSource = NULL; /* library owns it now */ + +bail: + if (pDataSource != NULL) + NuFreeDataSource(pDataSource); + if (pDataSink != NULL) + NuFreeDataSink(pDataSink); + if (buffer != NULL) + free(buffer); + return err; +} + +/* + * Copy a thread from one archive to another without disturbing the + * compression. + * + * There is a much more efficient way to do this: create an FP + * data source using an offset within the archive file itself. + * Since pInArchive->archiveFp isn't exposed, we can't use that, + * but under most operating systems you aren't prevented from + * opening the same file twice in read-only mode. The file offset + * in pThread tells us where the data is. + * + * The method used below is less memory-efficient but more portable. + * + * This always extracts based on the compThreadEOF, which is + * reliable but extracts a little more than we need on pre-sized + * threads (filenames, comments). + */ +NuError CopyThreadUncompressed(NuArchive* pInArchive, NuArchive* pOutArchive, + long flags, const NuThread* pThread, long newRecordIdx) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = NULL; + NuDataSink* pDataSink = NULL; + uint8_t* buffer = NULL; + + /* + * If we have some data files that were left uncompressed, perhaps + * because of GSHK's "don't compress anything smaller than 512 bytes" + * rule, NufxLib will try to compress them. We disable this + * behavior by disabling compression. That way, stuff that is + * already compressed will remain that way, and stuff that isn't + * compressed won't be. (We really only need to do this once, at + * the start of the program, but it's illustrative to do it here.) + * + * [ I don't understand this comment. It's necessary to disable + * compression, but I don't see why uncompressed files are + * special. ++ATM 20040821 ] + */ + err = NuSetValue(pOutArchive, kNuValueDataCompression, kNuCompressNone); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set compression (err=%d)\n", err); + goto bail; + } + + /* + * Allocate a buffer large enough to hold all the compressed data, and + * wrap a data sink around it. + */ + buffer = malloc(pThread->thCompThreadEOF); + if (buffer == NULL) { + err = kNuErrMalloc; + goto bail; + } + err = NuCreateDataSinkForBuffer(false, kNuConvertOff, buffer, + pThread->thCompThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", + err); + goto bail; + } + + /* + * Get the compressed data. For a pre-sized thread, this grabs the + * entire contents of the buffer, including the padding. + */ + err = NuExtractThread(pInArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread %u (err=%d)\n", + pThread->threadIdx, err); + goto bail; + } + + /* + * The (perhaps compressed) data is in the buffer, now create a data + * source that describes it. + * + * This is complicated by the existence of pre-sized threads. There + * are two possibilities: + * 1. We have a certain amount of non-pre-sized data (thCompThreadEOF) + * that will expand out to a certain length (actualThreadEOF). + * 2. We have a certain amount of pre-sized data (actualThreadEOF) + * that will fit within a buffer (thCompThreadEOF). + * As you can see, the arguments need to be reversed for pre-sized + * threads. + * + * We always use "actualThreadEOF" because "thThreadEOF" is broken + * for disk archives created by certain versions of ShrinkIt. + */ + if (NuIsPresizedThreadID(NuGetThreadID(pThread))) { + err = NuCreateDataSourceForBuffer(pThread->thThreadFormat, + pThread->thCompThreadEOF, buffer, 0, + pThread->actualThreadEOF, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create pre-sized data source (err=%d)\n",err); + goto bail; + } + } else { + err = NuCreateDataSourceForBuffer(pThread->thThreadFormat, + pThread->actualThreadEOF, buffer, 0, + pThread->thCompThreadEOF, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + } + buffer = NULL; /* doClose was set, so it's owned by the data source */ + + /* yes, this is a kluge... sigh */ + err = NuDataSourceSetRawCrc(pDataSource, pThread->thThreadCRC); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: can't set source CRC (err=%d)\n", err); + goto bail; + } + + /* + * Schedule the data for addition to the record. + * + * Note that NuAddThread makes a copy of the data source, and clears + * "doClose" on our copy, so we are free to dispose of pDataSource. + */ + err = NuAddThread(pOutArchive, newRecordIdx, NuGetThreadID(pThread), + pDataSource, NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err); + goto bail; + } + pDataSource = NULL; /* library owns it now */ + +bail: + if (pDataSource != NULL) + NuFreeDataSource(pDataSource); + if (pDataSink != NULL) + NuFreeDataSink(pDataSink); + if (buffer != NULL) + free(buffer); + return err; +} + + +/* + * Copy a thread from one archive to another. + * + * Depending on "flags", this will either copy it raw or uncompress and + * recompress. + */ +NuError CopyThread(NuArchive* pInArchive, NuArchive* pOutArchive, long flags, + const NuThread* pThread, long newRecordIdx) +{ + if (flags & kFlagCopyOnly) { + return CopyThreadUncompressed(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + } else { + return CopyThreadRecompressed(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + } +} + + +/* + * Copy a record from the input to the output. + * + * This runs through the list of threads and copies each one individually. + * It will copy them in the original order or in reverse order (the latter + * of which will not usually have any effect since NufxLib imposes a + * specific thread ordering on most common types) depending on "flags". + */ +NuError CopyRecord(NuArchive* pInArchive, NuArchive* pOutArchive, long flags, + NuRecordIdx recordIdx) +{ + NuError err = kNuErrNone; + const NuRecord* pRecord; + const NuThread* pThread; + NuFileDetails fileDetails; + NuRecordIdx newRecordIdx; + long numThreads; + int idx; + + /* + * Grab the original record and see how many threads it has. + */ + err = NuGetRecord(pInArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get recordIdx %u\n", recordIdx); + goto bail; + } + + /* + * Pre-v3 records didn't put CRCs in the thread headers. If we just + * copy the thread over without reprocessing the data, we won't compute + * a CRC for the thread, and we will get CRC failures. + */ + if (!gSentRecordWarning && (flags & kFlagCopyOnly) && + pRecord->recVersionNumber < 3) + { + printf("WARNING: pre-v3 records that aren't recompressed may exhibit CRC failures\n"); + gSentRecordWarning = true; + } + + numThreads = NuRecordGetNumThreads(pRecord); + if (!numThreads) { + fprintf(stderr, "WARNING: recordIdx=%u was empty\n", recordIdx); + goto bail; + } + + /* + * Create a new record that looks just like the original. + */ + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageNameMOR = pRecord->filenameMOR; + fileDetails.fileSysID = pRecord->recFileSysID; + fileDetails.fileSysInfo = pRecord->recFileSysInfo; + fileDetails.access = pRecord->recAccess; + fileDetails.fileType = pRecord->recFileType; + fileDetails.extraType = pRecord->recExtraType; + fileDetails.storageType = pRecord->recStorageType; + fileDetails.createWhen = pRecord->recCreateWhen; + fileDetails.modWhen = pRecord->recModWhen; + fileDetails.archiveWhen = pRecord->recArchiveWhen; + + err = NuAddRecord(pOutArchive, &fileDetails, &newRecordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuAddRecord failed (err=%d)\n", err); + goto bail; + } + + /* + * Copy the threads. + */ + if (flags & kFlagReverseThreads) { + for (idx = numThreads-1; idx >= 0; idx--) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != NULL); + + err = CopyThread(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + if (err != kNuErrNone) + goto bail; + } + } else { + for (idx = 0; idx < numThreads; idx++) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != NULL); + + err = CopyThread(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + if (err != kNuErrNone) + goto bail; + } + } + +bail: + return err; +} + + +/* + * Launder an archive from inFile to outFile. + * + * Returns 0 on success, nonzero on failure. + */ +int LaunderArchive(const char* inFile, const char* outFile, + NuValue compressMethod, long flags) +{ + NuError err = kNuErrNone; + NuArchive* pInArchive = NULL; + NuArchive* pOutArchive = NULL; + const NuMasterHeader* pMasterHeader; + NuRecordIdx recordIdx; + uint32_t idx, flushStatus; + + err = NuOpenRO(inFile, &pInArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't open input archive '%s' (err=%d)\n", + inFile, err); + goto bail; + } + err = NuOpenRW(outFile, kTempFile, kNuOpenCreat|kNuOpenExcl, &pOutArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't open output archive '%s' (err=%d)\n", + outFile, err); + goto bail; + } + + /* turn off "mimic GSHK" */ + err = NuSetValue(pInArchive, kNuValueMimicSHK, true); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to disable GSHK quirks (err=%d)\n", + err); + goto bail; + } + + /* allow duplicates, in case the original archive has them */ + err = NuSetValue(pOutArchive, kNuValueAllowDuplicates, true); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't allow duplicates (err=%d)\n", err); + goto bail; + } + + /* set the compression method */ + err = NuSetValue(pOutArchive, kNuValueDataCompression, compressMethod); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set compression (err=%d)\n", + err); + goto bail; + } + + if (flags & kFlagUseTmp) { + err = NuSetValue(pOutArchive, kNuValueModifyOrig, false); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: couldn't disable modify orig (err=%d)\n", err); + goto bail; + } + } + + err = NuGetMasterHeader(pInArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto bail; + } + + /* + * Iterate through the set of records. + */ + for (idx = 0; idx < pMasterHeader->mhTotalRecords; idx++) { + err = NuGetRecordIdxByPosition(pInArchive, idx, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%u (err=%d)\n", + idx, err); + goto bail; + } + + err = CopyRecord(pInArchive, pOutArchive, flags, recordIdx); + if (err != kNuErrNone) + goto bail; + + /* + * If "frequent abort" is set, abort what we just did and redo it. + */ + if (flags & kFlagFrequentAbort) { + /*printf("(abort)\n");*/ + err = NuAbort(pOutArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: abort failed (err=%d)\n", err); + goto bail; + } + + err = CopyRecord(pInArchive, pOutArchive, flags, recordIdx); + if (err != kNuErrNone) + goto bail; + + } + + /* + * If "frequent abort" or "frequent flush" is set, flush after + * each record is copied. + */ + if ((flags & kFlagFrequentAbort) || (flags & kFlagFrequentFlush)) { + /*printf("(flush)\n");*/ + err = NuFlush(pOutArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: flush failed (err=%d, status=0x%04x)\n", + err, flushStatus); + goto bail; + } + } + } + + /* first and only flush if frequent-flushing wasn't enabled */ + err = NuFlush(pOutArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=0x%04x)\n", + err, flushStatus); + goto bail; + } + +bail: + if (pInArchive != NULL) + NuClose(pInArchive); + if (pOutArchive != NULL) { + if (err != kNuErrNone) + NuAbort(pOutArchive); + NuClose(pOutArchive); /* flush pending changes and close */ + } + return (err != kNuErrNone); +} + + + +/* + * Can't count on having getopt on non-UNIX platforms, so just use this + * quick version instead. May not work exactly like getopt(), but it + * does everything we need here. + */ +int myoptind = 0; +char* myoptarg = NULL; +const char* curchar = NULL; +int skipnext = false; + +int mygetopt(int argc, char** argv, const char* optstr) +{ + if (!myoptind) { + myoptind = 1; + if (argc <= 1) + return EOF; + curchar = argv[myoptind]; + if (*curchar != '-') + return EOF; + } + + curchar++; + if (*curchar == '\0') { + myoptind++; + if (skipnext) + myoptind++; + if (myoptind >= argc) + return EOF; + curchar = argv[myoptind]; + if (*curchar != '-') + return EOF; + curchar++; + } + + while (*optstr != '\0') { + if (*optstr == *curchar) { + /*printf("MATCHED '%c'\n", *optstr);*/ + if (*(optstr+1) == ':') { + skipnext = true; + myoptarg = argv[myoptind+1]; + /*printf("ATTACHED '%s'\n", myoptarg);*/ + } + return *curchar; + } + + optstr++; + } + + fprintf(stderr, "Unrecognized option '%c'\n", *curchar); + return *curchar; +} + +/* + * Print usage info. + */ +void Usage(const char* argv0) +{ + fprintf(stderr, "Usage: %s [-crfat] [-m method] infile.shk outfile.shk\n", + argv0); + fprintf(stderr, "\t-c : copy only, does not recompress data\n"); + fprintf(stderr, "\t-r : copy threads in reverse order to test ordering\n"); + fprintf(stderr, "\t-f : call Flush frequently to reduce memory usage\n"); + fprintf(stderr, "\t-a : exercise nufxlib Abort code frequently\n"); + fprintf(stderr, "\t-t : write to temp file instead of directly to outfile.shk\n"); + fprintf(stderr, + "\t[method] is one of {sq,lzw1,lzw2,lzc12,lzc16,deflate,bzip2}\n"); + fprintf(stderr, "\tIf not specified, method defaults to lzw2\n"); +} + +/* + * Grab the name of an archive to read. + */ +int main(int argc, char** argv) +{ + NuValue compressMethod = kNuCompressLZW2; + int32_t major, minor, bug; + const char* pBuildDate; + long flags = 0; + int errorFlag; + int ic; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, NULL); + printf("Using NuFX lib %d.%d.%d built on or after %s\n", + major, minor, bug, pBuildDate); + + errorFlag = false; + while ((ic = mygetopt(argc, argv, "crfatm:")) != EOF) { + switch (ic) { + case 'c': flags |= kFlagCopyOnly; break; + case 'r': flags |= kFlagReverseThreads; break; + case 'f': flags |= kFlagFrequentFlush; break; + case 'a': flags |= kFlagFrequentAbort; break; + case 't': flags |= kFlagUseTmp; break; + case 'm': + { + struct { + const char* str; + NuValue val; + NuFeature feature; + } methods[] = { + { "sq", kNuCompressSQ, kNuFeatureCompressSQ }, + { "lzw1", kNuCompressLZW1, kNuFeatureCompressLZW }, + { "lzw2", kNuCompressLZW2, kNuFeatureCompressLZW }, + { "lzc12", kNuCompressLZC12, kNuFeatureCompressLZC }, + { "lzc16", kNuCompressLZC16, kNuFeatureCompressLZC }, + { "deflate", kNuCompressDeflate, kNuFeatureCompressDeflate}, + { "bzip2", kNuCompressBzip2, kNuFeatureCompressBzip2 }, + }; + char* methodStr = myoptarg; + int i; + + for (i = 0; i < NELEM(methods); i++) { + if (strcmp(methods[i].str, methodStr) == 0) { + compressMethod = methods[i].val; + break; + } + } + if (i == NELEM(methods)) { + fprintf(stderr, "ERROR: unknown method '%s'\n", methodStr); + errorFlag++; + break; + } + if (NuTestFeature(methods[i].feature) != kNuErrNone) { + fprintf(stderr, + "ERROR: compression method '%s' not supported\n", + methodStr); + errorFlag++; + break; + } + } + break; + default: + errorFlag++; + break; + } + } + + if (errorFlag || argc != myoptind+2) { + Usage(argv[0]); + exit(2); + } + + cc = LaunderArchive(argv[myoptind], argv[myoptind+1], compressMethod,flags); + + if (cc == 0) + printf("Success!\n"); + else + printf("Failed.\n"); + exit(cc != 0); +} + diff --git a/nufxlib/samples/Makefile.in b/nufxlib/samples/Makefile.in new file mode 100644 index 0000000..7f436bf --- /dev/null +++ b/nufxlib/samples/Makefile.in @@ -0,0 +1,81 @@ +# +# 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, see the file COPYING. +# +# Makefile for nufxlib tests (should work with non-GNU make). +# +# This is normally invoked from the nufxlib makefile. +# +# If you invoke this directly, LIB_PRODUCT won't be defined, and it +# won't automatically detect changes to the library. However, any +# changes to the library should cause a re-build in here anyway if +# you're running "make" from the library directory. +# +SHELL = /bin/sh +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. -I.. @DEFS@ + +#ALL_SRCS = $(wildcard *.c *.cpp) +ALL_SRCS = Exerciser.c ImgConv.c Launder.c TestBasic.c \ + TestExtract.c TestSimple.c TestTwirl.c + +NUFXLIB = -L.. -lnufx + +PRODUCTS = exerciser imgconv launder test-basic test-extract test-names \ + test-simple test-twirl + +all: $(PRODUCTS) + @true + +exerciser: Exerciser.o $(LIB_PRODUCT) + $(CC) -o $@ Exerciser.o $(NUFXLIB) @LIBS@ + +imgconv: ImgConv.o $(LIB_PRODUCT) + $(CC) -o $@ ImgConv.o $(NUFXLIB) @LIBS@ + +launder: Launder.o $(LIB_PRODUCT) + $(CC) -o $@ Launder.o $(NUFXLIB) @LIBS@ + +test-basic: TestBasic.o $(LIB_PRODUCT) + $(CC) -o $@ TestBasic.o $(NUFXLIB) @LIBS@ + +test-extract: TestExtract.o $(LIB_PRODUCT) + $(CC) -o $@ TestExtract.o $(NUFXLIB) @LIBS@ + +test-names: TestNames.o $(LIB_PRODUCT) + $(CC) -o $@ TestNames.o $(NUFXLIB) @LIBS@ + +test-simple: TestSimple.o $(LIB_PRODUCT) + $(CC) -o $@ TestSimple.o $(NUFXLIB) @LIBS@ + +test-twirl: TestTwirl.o $(LIB_PRODUCT) + $(CC) -o $@ TestTwirl.o $(NUFXLIB) @LIBS@ + +tags:: + ctags --totals -R ../* + @#ctags *.cpp ../*.c *.h ../*.h + +clean: + -rm -f *.o core + -rm -f $(PRODUCTS) + +distclean: clean + -rm -f tags + -rm -f Makefile Makefile.bak + +COMMON_HDRS = ../NufxLibPriv.h ../NufxLib.h ../MiscStuff.h ../SysDefs.h +Exerciser.o: Exerciser.c $(COMMON_HDRS) +ImgConv.o: ImgConv.c $(COMMON_HDRS) +Launder.o: Launder.c $(COMMON_HDRS) +TestBasic.o: TestBasic.c $(COMMON_HDRS) +TestExtract.o: TestExtract.c $(COMMON_HDRS) +TestNames.o: TestNames.c $(COMMON_HDRS) +TestSimple.o: TestSimple.c $(COMMON_HDRS) +TestTwirl.o: TestTwirl.c $(COMMON_HDRS) diff --git a/nufxlib/samples/Makefile.msc b/nufxlib/samples/Makefile.msc new file mode 100644 index 0000000..e8a4372 --- /dev/null +++ b/nufxlib/samples/Makefile.msc @@ -0,0 +1,113 @@ +# +# Makefile for Microsoft C compilers. Tested against Visual C++ 6.0. +# Not pretty but it seems to work. +# +# Run with "nmake /f Makefile.msc". Expects NufxLib to have been built +# in "..". +# +# To build without debugging info, use "nmake nodebug=1". +# To build with libz, use "nmake libz=1". +# To build with libbz2, use "nmake libbz2=1". +# If you're linking against nufxlib as a DLL, you don't need to specify +# libraries. You probably need to specify DLL=1 and the same setting +# of the NODEBUG flag as you used when building the DLL. If you don't, +# "test-extract" will fail in the fwrite() call in Nu_FWrite, because +# the non-debug /MD libc does something peculiar with FILE*. +# +# For libz/libbz2, you need to have the appropriate library either +# in this directory or in a standard location that the linker can find. +# + +# Windows magic +TARGETOS = BOTH +!include + +NUFXSRCDIR = .. +LIB_PRODUCT = $(NUFXSRCDIR)\nufxlib2.lib + +!ifdef DLL +### build using the same libc as the DLL +!ifdef NODEBUG +#OPT = /D NUFXLIB_DLL /D NDEBUG /MD /Ogityb2 +OPT = /D NUFXLIB_DLL /MD /Ogityb2 +LIB_FLAGS = /nodefaultlib:libcd.lib /nologo setargv.obj +!else +#OPT = /D NUFXLIB_DLL /MDd /Od +OPT = /D NUFXLIB_DLL /D DEBUG_MSGS /MDd /Od +LIB_FLAGS = /nodefaultlib:libc.lib /nologo setargv.obj +!endif +!else + +### build against static lib +!ifdef NODEBUG +#OPT = /D NDEBUG /ML /Ogityb2 +OPT = /ML /Ogityb2 +LIB_FLAGS = /nodefaultlib:libcd.lib /nologo libc.lib setargv.obj +!else +#OPT = /MLd /Od +OPT = /D DEBUG_MSGS /MLd /Od +LIB_FLAGS = /nodefaultlib:libc.lib /nologo libcd.lib setargv.obj +!endif +!endif + +BUILD_FLAGS = /W3 /GX /D "WIN32" /D "_CONSOLE" /I "$(NUFXSRCDIR)" +!MESSAGE Using OPT = $(OPT) + +!ifdef LIBZ +LIB_FLAGS = zlib.lib $(LIB_FLAGS) +!endif +!ifdef LIBBZ2 +LIB_FLAGS = libbz2.lib $(LIB_FLAGS) +!endif + +# how to compile sources +.c.obj: + @$(cc) $(cdebug) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $< + + +PRODUCTS = exerciser.exe imgconv.exe launder.exe test-basic.exe test-extract.exe test-simple.exe test-twirl.exe + +all: $(PRODUCTS) + +exerciser.exe: Exerciser.obj $(LIB_PRODUCT) + $(link) $(ldebug) Exerciser.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +imgconv.exe: ImgConv.obj $(LIB_PRODUCT) + $(link) $(ldebug) ImgConv.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +launder.exe: Launder.obj $(LIB_PRODUCT) + $(link) $(ldebug) Launder.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +test-basic.exe: TestBasic.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestBasic.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +test-simple.exe: TestSimple.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestSimple.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +test-extract.exe: TestExtract.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestExtract.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +test-twirl.exe: TestTwirl.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestTwirl.obj -out:$@ $(NUFXSRCDIR)\nufxlib2.lib $(LIB_FLAGS) + +clean: + -del *.obj + -del *.pdb + -del *.ilk + -del *.exp + -del exerciser.exe + -del imgconv.exe + -del launder.exe + -del test-basic.exe + -del test-simple.exe + -del test-extract.exe + -del test-twirl.exe + +Exerciser.obj: Exerciser.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +ImgConv.obj: ImgConv.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +Launder.obj: Launder.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestBasic.obj: TestBasic.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestSimple.obj: TestSimple.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestExtract.obj: TestExtract.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestTwirl.obj: TestTwirl.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h + diff --git a/nufxlib/samples/README-S.txt b/nufxlib/samples/README-S.txt new file mode 100644 index 0000000..e325638 --- /dev/null +++ b/nufxlib/samples/README-S.txt @@ -0,0 +1,131 @@ +NufxLib "samples" README + +This directory contains some test programs and useful sample code. + + +test-basic +========== + +Basic tests. Run this to verify that things are working. + +On Win32 there will be a second executable, test-basic-d, that links against +the DLL rather than the static library. + + +exerciser +========= + +This program allows you to exercise all of NufxLib's basic functions. +Run it without arguments and hit "?" for a list of commands. + +If you think you have found a bug in NufxLib, you can use this to narrow +it down to a repeatable case. + + +imgconv +======= + +A 2IMG disk image converter. You can convert ".2MG" files to ShrinkIt +disk archives, and ShrinkIt disk archives to 2IMG format. imgconv uses +a creator type of "NFXL". + +You can use it like this: + + % imgconv file.shk file.2mg +or + % imgconv file.2mg file.shk + +It figures out what to do based on the filename. It will recognize ".sdk" +as a ShrinkIt archive. + +Limitations: works for DOS-ordered and ProDOS-ordered 2MG images, but +not for raw nibble images. Converting from .shk only works if the first +record in the archive is a disk image; you don't get to pick the one you +want from an archive with several in it. + + +launder +======= + +Run an archive through the laundry. This copies the entire contents of +an archive thread-by-thread, reconstructing it such that the data +matches the original even if the archive contents don't (e.g. records +are updated to version 3, files may be recompressed with LZW/2, option +lists are stripped out, etc). + +The basic usage is: + + % launder [-crfa] [-m method] infile.shk outfile.shk + +The flags are: + + -c Just copy data threads rather than recompressing them + -r Add threads in reverse order + -f Call NuFlush after every record + -a Call NuAbort after every record, then re-do the record and call NuFlush + -t Write to temp file, instead of writing directly into outfile.shk + +The "-m method" flag allows you to specify the compression method. Valid +values are sq (SQueeze), lzw1 (ShrinkIt LZW/1), lzw2 (ShrinkIt LZW/2), +lzc12 (12-bit UNIX "compress"), lzc16 (16-bit UNIX "compress"), deflate +(zlib deflate), and bzip2 (libbz2 compression). The default is lzw2. + +If you use the "-c" flag with an archive created by P8 ShrinkIt or NuLib, +the laundered archive may have CRC failures when you try to extract +from it. This is because "launder" creates version 3 records, which +are expected to have a valid CRC in the thread header. The only way +to compute the CRC is to uncompress the data, which "launder" doesn't +do when "-c" is set. The data itself is fine, it's just the thread CRC +that's wrong (if the data were hosed, the LZW/1 CRC would be bad too). +"launder" will issue a warning when it detects this situation. + +By default, launder will try to keep the entire archive in memory and flush +all of the operations at the end. If you find that you're running out +of memory on very large archives, you can reduce the memory requirements +by specifying the "-f" flag. + + +test-names +========== + +Tests Unicode filename handling. Run without arguments. + +(This currently fails on Win32 because the Unicode filename support is +incomplete there.) + + +test-simple +=========== + +Simple test program. Give it the name of an archive, and it will display +the contents. + + +test-extract +============ + +Simple test program. Give it the name of an archive, and it will write +all filename threads into "out.buf", "out.fp", and "out.file" using three +different kinds of NuDataSinks. + + +test-twirl +========== + +Like "launder", but not meant to be useful. This recompresses the file "in +place", deleting and adding threads within existing records several times. +The changes are periodically flushed, but the archive is never closed. +The goal is to test repeated updates on an open archive. + +The CRC verification mechanism will fail on archives created with ProDOS +8 ShrinkIt. The older "version 1" records didn't have CRCs in the thread +headers, so you will get a series of messages that look like this: + +ERROR: CRC mismatch: 0 old=0x0000 new=0x681b +ERROR: CRC mismatch: 1 old=0x0000 new=0x5570 +ERROR: CRC mismatch: 2 old=0x0000 new=0x4ec5 + +This will leave the original archive alone, making a copy of it named +"TwirlCopy678" in the current directory. It overwrites its temp file, +"TwirlTmp789", without prompting. + diff --git a/nufxlib/samples/TestBasic.c b/nufxlib/samples/TestBasic.c new file mode 100644 index 0000000..19fea41 --- /dev/null +++ b/nufxlib/samples/TestBasic.c @@ -0,0 +1,1176 @@ +/* + * 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. + * + * Test basic features of the library. Run this without arguments. + */ +#include +#include +#include "NufxLib.h" +#include "Common.h" + +#define kTestArchive "nlbt.shk" +#define kTestTempFile "nlbt.tmp" + +#define kNumEntries 3 /* how many records are we going to add? */ + +/* stick to ASCII characters for these -- not doing conversions just yet */ +#define kTestEntryBytes "bytes" +#define kTestEntryBytesUPPER "BYTES" +#define kTestEntryEnglish "English" +#define kTestEntryLong "three|is a fairly long filename, complete with" \ + "punctuation and other nifty/bad stuff" +#define kLocalFssep '|' + +/* + * Globals. + */ +char gSuppressError = false; +#define FAIL_OK gSuppressError = true; +#define FAIL_BAD gSuppressError = false; + + +/* + * =========================================================================== + * Helper functions + * =========================================================================== + */ + +/* + * Get a single character of input from the user. + */ +static char TGetReplyChar(char defaultReply) +{ + char tmpBuf[32]; + + if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == NULL) + return defaultReply; + if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r') + return defaultReply; + + return tmpBuf[0]; +} + +NuError AddSimpleRecord(NuArchive* pArchive, const char* filenameMOR, + NuRecordIdx* pRecordIdx) +{ + NuFileDetails fileDetails; + + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageNameMOR = filenameMOR; + fileDetails.fileSysInfo = kLocalFssep; + fileDetails.access = kNuAccessUnlocked; + + return NuAddRecord(pArchive, &fileDetails, pRecordIdx); +} + + +/* + * Display error messages... or not. + */ +NuResult ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage) +{ + const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage; + + if (gSuppressError) + return kNuOK; + + if (pErrorMessage->isDebug) { + fprintf(stderr, "%sNufxLib says: [%s:%d %s] %s\n", + pArchive == NULL ? "GLOBAL>" : "", + pErrorMessage->file, pErrorMessage->line, pErrorMessage->function, + pErrorMessage->message); + } else { + fprintf(stderr, "%sNufxLib says: %s\n", + pArchive == NULL ? "GLOBAL>" : "", + pErrorMessage->message); + } + + return kNuOK; +} + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + +/* + * If the test file currently exists, ask the user if it's okay to remove + * it. + * + * Returns 0 if the file was successfully removed, -1 if the file could not + * be removed (because the unlink failed, or the user refused). + */ +int RemoveTestFile(const char* title, const char* fileName) +{ + char answer; + + if (access(fileName, F_OK) == 0) { + printf("%s '%s' exists, remove (y/n)? ", title, fileName); + fflush(stdout); + answer = TGetReplyChar('n'); + if (tolower(answer) != 'y') + return -1; + if (unlink(fileName) < 0) { + perror("unlink"); + return -1; + } + } + return 0; +} + + +/* + * =========================================================================== + * Tests + * =========================================================================== + */ + +/* + * Make sure the flags that control how we open the file work right, + * and verify that we handle existing zero-byte archive files correctly. + */ +int Test_OpenFlags(void) +{ + NuError err; + FILE* fp = NULL; + NuArchive* pArchive = NULL; + + printf("... open zero-byte existing\n"); + fp = fopen(kTestArchive, kNuFileOpenWriteTrunc); + if (fp == NULL) { + perror("fopen kTestArchive"); + goto failed; + } + fclose(fp); + fp = NULL; + + FAIL_OK; + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat|kNuOpenExcl, + &pArchive); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: file opened when it shouldn't have\n"); + goto failed; + } + + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: file didn't open when it should have\n"); + goto failed; + } + + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: close failed\n"); + goto failed; + } + pArchive = NULL; + + if (access(kTestArchive, F_OK) == 0) { + fprintf(stderr, "ERROR: archive should have been removed but wasn't\n"); + goto failed; + } + + return 0; + +failed: + if (pArchive != NULL) { + NuAbort(pArchive); + NuClose(pArchive); + } + return -1; +} + + +/* + * Add some files to the archive. These will be used by later tests. + */ +int Test_AddStuff(NuArchive* pArchive) +{ + NuError err; + uint8_t* buf = NULL; + NuDataSource* pDataSource = NULL; + NuRecordIdx recordIdx; + uint32_t status; + int i; + static const char* testMsg = + "This is a nice test message that has linefeeds in it so we can\n" + "see if the line conversion stuff is actually doing anything at\n" + "all. It's certainly nice to know that everything works the way\n" + "it's supposed to, which I suppose is why we have this nifty test\n" + "program available. It sure would be nice if everybody tested\n" + "their code, but where would Microsoft be without endless upgrades\n" + "and service packs? Bugs are what America was built on, and\n" + "anybody who says otherwise is a pinko commie lowlife. Verily.\n"; + + printf("... add 'bytes' record\n"); + buf = malloc(131072); + if (buf == NULL) + goto failed; + for (i = 0; i < 131072; i++) + *(buf+i) = i & 0xff; + + FAIL_OK; + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, NULL, 0, 131072, FreeCallback, &pDataSource); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: that should've failed!\n"); + goto failed; + } + + /* + * Create a data source for the big batch of bytes. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, buf, 0, 131072, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'bytes' data source create failed (err=%d)\n", err); + goto failed; + } + buf = NULL; /* now owned by library */ + + err = AddSimpleRecord(pArchive, kTestEntryBytes, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'bytes' record failed (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'bytes' thread add failed (err=%d)\n", err); + goto failed; + } + pDataSource = NULL; /* now owned by library */ + + + /* + * Create a data source for our lovely text message. + */ + printf("... add 'English' record\n"); + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, (const uint8_t*)testMsg, 0, strlen(testMsg), NULL, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'English' source create failed (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + NULL); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: 'English' add should've conflicted!\n"); + goto failed; + } + + FAIL_OK; + err = AddSimpleRecord(pArchive, kTestEntryBytes, &recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: duplicates not allowed, should've failed\n"); + goto failed; + } + + err = AddSimpleRecord(pArchive, kTestEntryEnglish, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'English' record failed (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'English' thread add failed (err=%d)\n", err); + goto failed; + } + pDataSource = NULL; /* now owned by library */ + + + /* + * Create an empty file with a rather non-empty name. Add it as + * a resource fork. + */ + printf("... add 'long' record\n"); + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, NULL, 0, 0, NULL, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'English' source create failed (err=%d)\n", err); + goto failed; + } + + err = AddSimpleRecord(pArchive, kTestEntryLong, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'long' record failed (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDRsrcFork, pDataSource, + NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'long' thread add failed (err=%d)\n", err); + goto failed; + } + pDataSource = NULL; /* now owned by library */ + + + /* + * Flush changes. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't flush after add (err=%d, status=%u)\n", + err, status); + goto failed; + } + + /* + * Flush again; should succeed since it doesn't have to do anything. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: second add flush failed (err=%d, status=%u)\n", + err, status); + goto failed; + } + + return 0; +failed: + if (pDataSource != NULL) + NuFreeDataSource(pDataSource); + if (buf != NULL) + free(buf); + return -1; +} + + +/* + * Make sure that what we're seeing makes sense. + */ +NuResult TestContentsCallback(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (NuRecord*) vpRecord; + + if (strcmp(pRecord->filenameMOR, kTestEntryBytes) == 0 || + strcmp(pRecord->filenameMOR, kTestEntryEnglish) == 0 || + strcmp(pRecord->filenameMOR, kTestEntryLong) == 0) + { + return kNuOK; + } + + fprintf(stderr, "ERROR: found mystery entry '%s'\n", pRecord->filenameMOR); + return kNuAbort; +} + + +/* + * Verify that the contents look about right. + */ +int Test_Contents(NuArchive* pArchive) +{ + NuError err; + long posn; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + int cc; + + /* + * First, do it with a callback. + */ + err = NuContents(pArchive, TestContentsCallback); + if (err != kNuErrNone) + goto failed; + + /* + * Now step through the records with get-by-position and verify that + * they're in the expected order. + */ + for (posn = 0; posn < kNumEntries; posn++) { + err = NuGetRecordIdxByPosition(pArchive, posn, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + posn, err); + goto failed; + } + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + + switch (posn) { + case 0: + cc = strcmp(pRecord->filenameMOR, kTestEntryBytes); + break; + case 1: + cc = strcmp(pRecord->filenameMOR, kTestEntryEnglish); + break; + case 2: + cc = strcmp(pRecord->filenameMOR, kTestEntryLong); + if (!cc) + cc = !(pRecord->recStorageType == kNuStorageExtended); + break; + default: + fprintf(stderr, "ERROR: somebody forgot to put a case here (%ld)\n", + posn); + cc = -1; + } + + if (cc) { + fprintf(stderr, "ERROR: got '%s' for %ld (%u), not expected\n", + pRecord->filenameMOR, posn, recordIdx); + goto failed; + } + } + + /* + * Read one more past the end, should fail. + */ + FAIL_OK; + err = NuGetRecordIdxByPosition(pArchive, posn, &recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: too many records (%ld was ok)\n", posn); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Selection callback filter for "test". This gets called once per record, + * twice per record for forked files. + */ +NuResult VerifySelectionCallback(NuArchive* pArchive, void* vpProposal) +{ + NuError err; + const NuSelectionProposal* pProposal = vpProposal; + long count; + + if (pProposal->pRecord == NULL || pProposal->pThread == NULL || + pProposal->pRecord->filenameMOR == NULL) + { + fprintf(stderr, "ERROR: unexpected NULL in proposal\n"); + goto failed; + } + + err = NuGetExtraData(pArchive, (void**) &count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get extra data (err=%d)\n", err); + goto failed; + } + + count++; + + err = NuSetExtraData(pArchive, (void*) count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to inc extra data (err=%d)\n", err); + goto failed; + } + + return kNuOK; +failed: + return kNuAbort; +} + +/* + * Verify the archive contents. + */ +int Test_Verify(NuArchive* pArchive) +{ + NuError err; + long count; + + printf("... verifying CRCs\n"); + + if (NuSetSelectionFilter(pArchive, VerifySelectionCallback) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: unable to set selection filter\n"); + goto failed; + } + + err = NuSetExtraData(pArchive, (void*) 0); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set extra data (err=%d)\n", err); + goto failed; + } + + err = NuTest(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: verify failed (err=%d)\n", err); + goto failed; + } + + err = NuGetExtraData(pArchive, (void**) &count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: last extra data get failed (err=%d)\n", err); + goto failed; + } + + /* the count will be one higher than the number of records because + the last entry is forked, and each fork is tested separately */ + if (count != kNumEntries + 1) { + fprintf(stderr, "ERROR: verified %ld when expecting %d\n", count, + kNumEntries); + goto failed; + } + + return 0; +failed: + return -1; +} + +/* + * Extract stuff. + */ +int Test_Extract(NuArchive* pArchive) +{ + NuError err; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread; + NuDataSink* pDataSink = NULL; + uint8_t* buf = NULL; + + printf("... extracting files\n"); + + /* + * Tell it the current system uses CRLF, so it'll bloat up when we do + * a text conversion. + */ + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); + + /* + * Extract "bytes". + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryBytesUPPER, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", kTestEntryBytes, + err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != NULL); + if (NuGetThreadID(pThread) != kNuThreadIDDataFork) { + fprintf(stderr, "ERROR: 'bytes' had unexpected threadID 0x%08x\n", + NuGetThreadID(pThread)); + goto failed; + } + + buf = malloc(pThread->actualThreadEOF); + if (buf == NULL) { + fprintf(stderr, "ERROR: malloc(%u) failed\n",pThread->actualThreadEOF); + goto failed; + } + + /* + * Try to extract it with text conversion off. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + /* + * Try to extract with "on" conversion, which should fail because the + * buffer is too small. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOn, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: managed to extract bloated 'bytes'?\n"); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + /* + * Try to extract with "auto" conversion, which should conclude that + * the input is text and not try to convert. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertAuto, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (auto) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + + + free(buf); + buf = NULL; + + + + /* + * Extract "English". + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryEnglish, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", + kTestEntryEnglish, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != NULL); + if (NuGetThreadID(pThread) != kNuThreadIDDataFork) { + fprintf(stderr, "ERROR: 'English' had unexpected threadID 0x%08x\n", + NuGetThreadID(pThread)); + goto failed; + } + + buf = malloc(pThread->actualThreadEOF); + if (buf == NULL) { + fprintf(stderr, "ERROR: malloc(%u) failed\n", pThread->actualThreadEOF); + goto failed; + } + + /* + * Try to extract it with text conversion off. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + /* + * Try to extract with "auto" conversion, which should fail because the + * buffer is too small, and the input looks like text. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertAuto, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: managed to extract bloated 'English'?\n"); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + + + /*Free(buf);*/ + /*buf = NULL;*/ + + + + /* + * Extract "long" (which is zero bytes). + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryLong, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", + kTestEntryLong, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != NULL); + if (NuGetThreadID(pThread) != kNuThreadIDRsrcFork) { + fprintf(stderr, "ERROR: 'Long' had unexpected threadID 0x%08x\n", + NuGetThreadID(pThread)); + goto failed; + } + + /* + * Try it with text conversion on; shouldn't matter. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOn, buf, + 1, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'Long' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = NULL; + + + + free(buf); + buf = NULL; + + + + return 0; +failed: + if (buf != NULL) + free(buf); + if (pDataSink != NULL) + (void) NuFreeDataSink(pDataSink); + return -1; +} + +/* + * Delete the first and last records. Does *not* flush the archive. + */ +int Test_Delete(NuArchive* pArchive) +{ + NuError err; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread = NULL; + long count; + int idx; + + printf("... deleting first and last\n"); + + /* + * Delete all threads from the first record ("bytes"). + */ + err = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find #%d (err=%d)\n", 0, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + assert(pRecord->recTotalThreads > 0); + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != NULL); + + err = NuDeleteThread(pArchive, pThread->threadIdx); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: couldn't delete thread #%d (%u) (err=%d)\n", + idx, recordIdx, err); + goto failed; + } + } + + /* try to re-delete the same thread */ + assert(pThread != NULL); + FAIL_OK; + err = NuDeleteThread(pArchive, pThread->threadIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: allowed to re-delete thread (%u) (err=%d)\n", + recordIdx, err); + goto failed; + } + + /* try to delete the modified record */ + FAIL_OK; + err = NuDeleteRecord(pArchive, recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, + "ERROR: able to delete modified record (%u) (err=%d)\n", + recordIdx, err); + goto failed; + } + + /* + * Make sure the attr hasn't been updated yet. + */ + count = 0; + err = NuGetAttr(pArchive, kNuAttrNumRecords, (uint32_t*) &count); + if (count != kNumEntries) { + fprintf(stderr, "ERROR: kNuAttrNumRecords %ld vs %d\n", + count, kNumEntries); + goto failed; + } + + /* + * Delete the last record ("long"). + */ + err = NuGetRecordIdxByPosition(pArchive, kNumEntries-1, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find #%d (err=%d)\n", 0, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != NULL); + + /* grab the first thread before we whack the record */ + pThread = NuGetThread(pRecord, 0); + assert(pThread != NULL); + + err = NuDeleteRecord(pArchive, recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to delete record #%d (%u) (err=%d)\n", + kNumEntries-1, recordIdx, err); + goto failed; + } + + /* try to delete a thread from the deleted record */ + FAIL_OK; + err = NuDeleteThread(pArchive, pThread->threadIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, + "ERROR: allowed to delete from deleted (%u) (err=%d)\n", + pThread->threadIdx, err); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Verify that the count in the master header has been updated. + */ +int Test_MasterCount(NuArchive* pArchive, long expected) +{ + NuError err; + const NuMasterHeader* pMasterHeader; + + printf("... checking master count\n"); + + err = NuGetMasterHeader(pArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto failed; + } + + if (pMasterHeader->mhTotalRecords != (uint32_t)expected) { + fprintf(stderr, "ERROR: unexpected MH count (%u vs %ld)\n", + pMasterHeader->mhTotalRecords, expected); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Run some tests. + * + * Returns 0 on success, -1 on error. + */ +int DoTests(void) +{ + NuError err; + NuArchive* pArchive = NULL; + uint32_t status; + int cc, result = 0; + + /* + * Make sure we're starting with a clean slate. + */ + if (RemoveTestFile("Test archive", kTestArchive) < 0) { + goto failed; + } + if (RemoveTestFile("Test temp file", kTestTempFile) < 0) { + goto failed; + } + + /* + * Test some of the open flags. + */ + if (Test_OpenFlags() != 0) + goto failed; + + /* + * Create a new archive to play with. + */ + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat|kNuOpenExcl, + &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRW failed (err=%d)\n", err); + goto failed; + } + if (NuSetErrorMessageHandler(pArchive, ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: couldn't set message handler\n"); + goto failed; + } + + /* + * Add some test entries. + */ + if (Test_AddStuff(pArchive) != 0) + goto failed; + + /* + * Check the archive contents. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Reopen it read-only. + */ + printf("... reopening archive read-only\n"); + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: mid NuClose failed (err=%d)\n", err); + goto failed; + } + pArchive = NULL; + + err = NuOpenRO(kTestArchive, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRO failed (err=%d)\n", err); + goto failed; + } + if (NuSetErrorMessageHandler(pArchive, ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: couldn't set message handler\n"); + goto failed; + } + + /* + * Make sure the TOC (i.e. list of files) is still what we expect. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Verify the archive data. + */ + if (Test_Verify(pArchive) != 0) + goto failed; + + /* + * Extract the files. + */ + if (Test_Extract(pArchive) != 0) + goto failed; + + /* + * Reopen it read-write. + */ + printf("... reopening archive read-write\n"); + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: late NuClose failed (err=%d)\n", err); + goto failed; + } + pArchive = NULL; + + err = NuOpenRW(kTestArchive, kTestTempFile, 0, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: re-NuOpenRW failed (err=%d)\n", err); + goto failed; + } + if (NuSetErrorMessageHandler(pArchive, ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: couldn't set message handler\n"); + goto failed; + } + + /* + * Contents shouldn't have changed. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Test deletion. + */ + if (Test_Delete(pArchive) != 0) + goto failed; + + /* + * Abort the changes and verify that nothing has changed. + */ + printf("... aborting changes\n"); + err = NuAbort(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: abort failed (err=%d)\n", err); + goto failed; + } + + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Delete them again. + */ + if (Test_Delete(pArchive) != 0) + goto failed; + + /* + * Flush the deletions. This should remove the first and last records. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=%d)\n", + err, status); + goto failed; + } + + /* + * Check count in master header. + */ + if (Test_MasterCount(pArchive, kNumEntries-2) != 0) + goto failed; + + /* + * That's all, folks... + */ + NuClose(pArchive); + pArchive = NULL; + + printf("... removing '%s'\n", kTestArchive); + cc = unlink(kTestArchive); + if (cc < 0) { + perror("unlink kTestArchive"); + goto failed; + } + + +leave: + if (pArchive != NULL) { + NuAbort(pArchive); + NuClose(pArchive); + } + return result; + +failed: + result = -1; + goto leave; +} + + +/* + * Crank away. + */ +int main(void) +{ + int32_t major, minor, bug; + const char* pBuildDate; + const char* pBuildFlags; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, &pBuildFlags); + printf("Using NuFX library v%d.%d.%d, built on or after\n" + " %s with [%s]\n\n", + major, minor, bug, pBuildDate, pBuildFlags); + + if (NuSetGlobalErrorMessageHandler(ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: can't set the global message handler"); + exit(1); + } + + printf("... starting tests\n"); + + cc = DoTests(); + + printf("... tests ended, %s\n", cc == 0 ? "SUCCESS" : "FAILURE"); + exit(cc != 0); +} + diff --git a/nufxlib/samples/TestExtract.c b/nufxlib/samples/TestExtract.c new file mode 100644 index 0000000..4e8bfb2 --- /dev/null +++ b/nufxlib/samples/TestExtract.c @@ -0,0 +1,473 @@ +/* + * 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. + * + * Test extraction of individual threads in various ways. The net result + * of this is three files (out.file, out.fp, out.buf) that contain the + * result of writing all filenames in an archive to the same data sink. + * + * This gathers up information on the contents of the archive via a + * callback, and then emits all of the data at once. + * + * (This was originally written in C++, and converted to C after I repented.) + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + + +/*#define false 0*/ +/*#define true (!false)*/ + +#define kHappySize 2408 + + +/* + * =========================================================================== + * ArchiveRecord + * =========================================================================== + */ + +/* + * Track an archive record. + */ +typedef struct ArchiveRecord { + char* filenameMOR; + NuRecordIdx recordIdx; + + long numThreads; + NuThread* pThreads; + + struct ArchiveRecord* pNext; +} ArchiveRecord; + + +/* + * Alloc a new ArchiveRecord. + */ +ArchiveRecord* ArchiveRecord_New(const NuRecord* pRecord) +{ + ArchiveRecord* pArcRec = NULL; + + pArcRec = malloc(sizeof(*pArcRec)); + if (pArcRec == NULL) + return NULL; + + if (pRecord->filenameMOR == NULL) + pArcRec->filenameMOR = strdup(""); + else + pArcRec->filenameMOR = strdup(pRecord->filenameMOR); + + pArcRec->recordIdx = pRecord->recordIdx; + pArcRec->numThreads = NuRecordGetNumThreads(pRecord); + (void) NuRecordCopyThreads(pRecord, &pArcRec->pThreads); + + pArcRec->pNext = NULL; + + return pArcRec; +} + +/* + * Free up an ArchiveRecord. + */ +void ArchiveRecord_Free(ArchiveRecord* pArcRec) +{ + if (pArcRec == NULL) + return; + + if (pArcRec->filenameMOR != NULL) + free(pArcRec->filenameMOR); + if (pArcRec->pThreads != NULL) + free(pArcRec->pThreads); + free(pArcRec); +} + +/* + * Find a thread with a matching NuThreadID. + */ +const NuThread* ArchiveRecord_FindThreadByID(const ArchiveRecord* pArcRec, + NuThreadID threadID) +{ + const NuThread* pThread; + int i; + + for (i = 0; i < pArcRec->numThreads; i++) { + pThread = NuThreadGetByIdx(pArcRec->pThreads, i); + if (NuGetThreadID(pThread) == threadID) + return pThread; + } + + return NULL; +} + + +const char* ArchiveRecord_GetFilename(const ArchiveRecord* pArcRec) +{ + return pArcRec->filenameMOR; +} + +NuRecordIdx ArchiveRecord_GetRecordIdx(const ArchiveRecord* pArcRec) +{ + return pArcRec->recordIdx; +} + +long ArchiveRecord_GetNumThreads(const ArchiveRecord* pArcRec) +{ + return pArcRec->numThreads; +} + +const NuThread* ArchiveRecord_GetThread(const ArchiveRecord* pArcRec, int idx) +{ + if (idx < 0 || idx >= pArcRec->numThreads) + return NULL; + return NuThreadGetByIdx(pArcRec->pThreads, idx); +} + +void ArchiveRecord_SetNext(ArchiveRecord* pArcRec, ArchiveRecord* pNextRec) +{ + pArcRec->pNext = pNextRec; +} + +ArchiveRecord* ArchiveRecord_GetNext(const ArchiveRecord* pArcRec) +{ + return pArcRec->pNext; +} + + +/* + * =========================================================================== + * ArchiveData + * =========================================================================== + */ + +/* + * A collection of records. + */ +typedef struct ArchiveData { + long numRecords; + ArchiveRecord* pRecordHead; + ArchiveRecord* pRecordTail; +} ArchiveData; + + +ArchiveData* ArchiveData_New(void) +{ + ArchiveData* pArcData; + + pArcData = malloc(sizeof(*pArcData)); + if (pArcData == NULL) + return NULL; + + pArcData->numRecords = 0; + pArcData->pRecordHead = pArcData->pRecordTail = NULL; + + return pArcData; +} + +void ArchiveData_Free(ArchiveData* pArcData) +{ + ArchiveRecord* pNext; + + if (pArcData == NULL) + return; + + printf("*** Deleting %ld records!\n", pArcData->numRecords); + while (pArcData->pRecordHead != NULL) { + pNext = ArchiveRecord_GetNext(pArcData->pRecordHead); + ArchiveRecord_Free(pArcData->pRecordHead); + pArcData->pRecordHead = pNext; + } + + free(pArcData); +} + + +ArchiveRecord* ArchiveData_GetRecordHead(const ArchiveData* pArcData) +{ + return pArcData->pRecordHead; +} + + +/* add an ArchiveRecord to the list pointed at by ArchiveData */ +void ArchiveData_AddRecord(ArchiveData* pArcData, ArchiveRecord* pRecord) +{ + assert(pRecord != NULL); + assert((pArcData->pRecordHead == NULL && pArcData->pRecordTail == NULL) || + (pArcData->pRecordHead != NULL && pArcData->pRecordTail != NULL)); + + if (pArcData->pRecordHead == NULL) { + /* first */ + pArcData->pRecordHead = pArcData->pRecordTail = pRecord; + } else { + /* not first, add to end */ + ArchiveRecord_SetNext(pArcData->pRecordTail, pRecord); + pArcData->pRecordTail = pRecord; + } + + pArcData->numRecords++; +} + +/* dump the contents of the ArchiveData to stdout */ +void ArchiveData_DumpContents(const ArchiveData* pArcData) +{ + ArchiveRecord* pArcRec; + + pArcRec = pArcData->pRecordHead; + while (pArcRec != NULL) { + const NuThread* pThread; + int i, count; + + printf("%5u '%s'\n", + ArchiveRecord_GetRecordIdx(pArcRec), + ArchiveRecord_GetFilename(pArcRec)); + + count = ArchiveRecord_GetNumThreads(pArcRec); + for (i = 0; i < count; i++) { + pThread = ArchiveRecord_GetThread(pArcRec, i); + printf(" %5u 0x%04x 0x%04x\n", pThread->threadIdx, + pThread->thThreadClass, pThread->thThreadKind); + } + + pArcRec = ArchiveRecord_GetNext(pArcRec); + } +} + + +/* + * =========================================================================== + * Main stuff + * =========================================================================== + */ + +/* + * Callback function to collect archive information. + */ +NuResult GatherContents(NuArchive* pArchive, void* vpRecord) +{ + NuRecord* pRecord = (NuRecord*) vpRecord; + ArchiveData* pArchiveData = NULL; + ArchiveRecord* pArchiveRecord = ArchiveRecord_New(pRecord); + + NuGetExtraData(pArchive, (void**)&pArchiveData); + assert(pArchiveData != NULL); + + printf("*** Filename = '%s'\n", + pRecord->filenameMOR == NULL ? + "" : pRecord->filenameMOR); + + ArchiveData_AddRecord(pArchiveData, pArchiveRecord); + + return kNuOK; +} + + +/* + * Copy the filename thread from every record to "pDataSink". + */ +NuError ReadAllFilenameThreads(NuArchive* pArchive, ArchiveData* pArchiveData, + NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + ArchiveRecord* pArchiveRecord; + const NuThread* pThread; + + pArchiveRecord = ArchiveData_GetRecordHead(pArchiveData); + while (pArchiveRecord != NULL) { + pThread = ArchiveRecord_FindThreadByID(pArchiveRecord, + kNuThreadIDFilename); + if (pThread != NULL) { + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "*** Extract failed (%d)\n", err); + goto bail; + } + } + pArchiveRecord = ArchiveRecord_GetNext(pArchiveRecord); + } + +bail: + return err; +} + + +/* extract every filename thread into a single file, overwriting each time */ +NuError ExtractToFile(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + NuDataSink* pDataSink = NULL; + + err = NuCreateDataSinkForFile(true, kNuConvertOff, "out.file", PATH_SEP, + &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = NuSetValue(pArchive, kNuValueHandleExisting, kNuAlwaysOverwrite); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) + goto bail; + +bail: + (void) NuFreeDataSink(pDataSink); + if (err == kNuErrNone) + printf("*** File write complete\n"); + return err; +} + +/* extract every filename thread into a FILE*, appending */ +NuError ExtractToFP(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + FILE* fp = NULL; + NuDataSink* pDataSink = NULL; + + if ((fp = fopen("out.fp", kNuFileOpenWriteTrunc)) == NULL) + return kNuErrFileOpen; + + err = NuCreateDataSinkForFP(true, kNuConvertOff, fp, &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) + goto bail; + +bail: + (void) NuFreeDataSink(pDataSink); + if (fp != NULL) + fclose(fp); + if (err == kNuErrNone) + printf("*** FP write complete\n"); + return err; +} + +/* extract every filename thread into a buffer, advancing as we go */ +NuError ExtractToBuffer(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + uint8_t buffer[kHappySize]; + NuDataSink* pDataSink = NULL; + uint32_t count; + + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buffer, kHappySize, + &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) { + if (err == kNuErrBufferOverrun) + fprintf(stderr, "*** Hey, buffer wasn't big enough!\n"); + goto bail; + } + + /* write the buffer to a file */ + (void) NuDataSinkGetOutCount(pDataSink, &count); + if (count > 0) { + FILE* fp; + if ((fp = fopen("out.buf", kNuFileOpenWriteTrunc)) != NULL) { + + printf("*** Writing %u bytes\n", count); + if (fwrite(buffer, count, 1, fp) != 1) + err = kNuErrFileWrite; + fclose(fp); + } + } else { + printf("*** No data found!\n"); + } + +bail: + (void) NuFreeDataSink(pDataSink); + return err; +} + + +/* + * Do file stuff. + */ +int DoFileStuff(const UNICHAR* filenameUNI) +{ + NuError err; + NuArchive* pArchive = NULL; + ArchiveData* pArchiveData = ArchiveData_New(); + + err = NuOpenRO(filenameUNI, &pArchive); + if (err != kNuErrNone) + goto bail; + + NuSetExtraData(pArchive, pArchiveData); + + printf("*** Gathering contents!\n"); + err = NuContents(pArchive, GatherContents); + if (err != kNuErrNone) + goto bail; + + printf("*** Dumping contents!\n"); + ArchiveData_DumpContents(pArchiveData); + + err = ExtractToFile(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + err = ExtractToFP(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + err = ExtractToBuffer(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + +bail: + if (err != kNuErrNone) + fprintf(stderr, "*** ERROR: got error %d\n", err); + + if (pArchive != NULL) { + NuError err2 = NuClose(pArchive); + if (err == kNuErrNone && err2 != kNuErrNone) + err = err2; + } + + ArchiveData_Free(pArchiveData); + + return err; +} + + +/* + * Grab the name of an archive to read. If no name was provided, use stdin. + */ +int main(int argc, char** argv) +{ + int32_t major, minor, bug; + const char* pBuildDate; + FILE* infp = NULL; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, NULL); + printf("Using NuFX lib %d.%d.%d built on or after %s\n", + major, minor, bug, pBuildDate); + + if (argc == 2) { + infp = fopen(argv[1], kNuFileOpenReadOnly); + if (infp == NULL) { + perror("fopen failed"); + exit(1); + } + } else { + fprintf(stderr, "ERROR: you have to specify a filename\n"); + exit(2); + } + + cc = DoFileStuff(argv[1]); + + if (infp != NULL) + fclose(infp); + + exit(cc != 0); +} + diff --git a/nufxlib/samples/TestNames.c b/nufxlib/samples/TestNames.c new file mode 100644 index 0000000..61ea479 --- /dev/null +++ b/nufxlib/samples/TestNames.c @@ -0,0 +1,580 @@ +/* + * 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. + * + * Test local and storage names with Unicode and Mac OS Roman content. + * + * On Windows, opening files with fancy filenames requires UTF-16 and + * special functions. On Linux and Mac OS X we're just writing UTF-8 data, + * so they don't really need to do anything special other than be 8-bit + * clean. NufxLib functions take UTF-8 strings, so on Windows we define + * everything in UTF-16 and convert to UTF-8. (We need the UTF-16 form so + * we can use "wide" I/O functions to confirm that the file was created + * with the correct name.) + * + * To see files with the correct appearance with "ls", you may need to + * do something like: + * + * % LC_ALL=en_US.UTF-8 ls + * + * (Many users set LC_ALL=POSIX to avoid GNU grep slowdowns and altered + * sort ordering in ls.) + */ +#include +#include +#include "NufxLib.h" +#include "Common.h" + +/* + * Test filenames. + * + * The local filename (kTestArchive) contains non-MOR Unicode values + * (two Japanese characters that Google Translate claims form the verb + * "shrink"). The temp file name is similar. + * + * The entry name uses a mix of simple ASCII, CP1252 MOR, and + * non-CP1252 MOR characters: fl ligature, double dagger, copyright symbol, + * Apple logo (the latter of which doesn't have a glyph on Windows or Linux). + * All of the characters have MOR translations. + */ +#ifdef USE_UTF16 +const UNICHAR kTestArchive[] = L"nlTest\u7e2e\u3080.shk"; +const UNICHAR kTestEntryName[] = L"nl-test\u2013\ufb01_\u2021_\u00a9\uf8ff!"; +const UNICHAR kTestTempFile[] = L"nlTest\4e00\u6642\u30d5\u30a1\u30a4\u30eb.tmp"; +#else +const UNICHAR kTestArchive[] = "nlTest\xe7\xb8\xae\xe3\x82\x80.shk"; +const UNICHAR kTestEntryName[] = "nl-test\xe2\x80\x93\xef\xac\x81_\xe2\x80\xa1_" + "\xc2\xa9\xef\xa3\xbf!"; +const UNICHAR kTestTempFile[] = "nlTest\xe4\xb8\x80\xe6\x99\x82\xe3\x83\x95" + "\xe3\x82\xa1\xe3\x82\xa4\xe3\x83\xab.tmp"; +#endif + +const UNICHAR kLocalFssep = '|'; + + +/* + * =========================================================================== + * Helper functions + * =========================================================================== + */ + +/* + * Get a single character of input from the user. + */ +static char TGetReplyChar(char defaultReply) +{ + char tmpBuf[32]; + + if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == NULL) + return defaultReply; + if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r') + return defaultReply; + + return tmpBuf[0]; +} + +NuError AddSimpleRecord(NuArchive* pArchive, const char* fileNameMOR, + NuRecordIdx* pRecordIdx) +{ + NuFileDetails fileDetails; + + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageNameMOR = fileNameMOR; + fileDetails.fileSysInfo = kLocalFssep; + fileDetails.access = kNuAccessUnlocked; + + return NuAddRecord(pArchive, &fileDetails, pRecordIdx); +} + +/* + * Display error messages... or not. + */ +NuResult ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage) +{ + const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage; + + //if (gSuppressError) + // return kNuOK; + + if (pErrorMessage->isDebug) { + fprintf(stderr, "%sNufxLib says: [%s:%d %s] %s\n", + pArchive == NULL ? "GLOBAL>" : "", + pErrorMessage->file, pErrorMessage->line, pErrorMessage->function, + pErrorMessage->message); + } else { + fprintf(stderr, "%sNufxLib says: %s\n", + pArchive == NULL ? "GLOBAL>" : "", + pErrorMessage->message); + } + + return kNuOK; +} + +#ifdef USE_UTF16 +TODO - use _waccess, _wunlink, etc. +#else +int RemoveTestFile(const char* title, const char* fileName) +{ + char answer; + + if (access(fileName, F_OK) == 0) { + printf("%s '%s' exists, remove (y/n)? ", title, fileName); + fflush(stdout); + answer = TGetReplyChar('n'); + if (tolower(answer) != 'y') + return -1; + if (unlink(fileName) < 0) { + perror("unlink"); + return -1; + } + } + return 0; +} +#endif + +/* + * Utility function that wraps NuConvertUNIToMOR, allocating a new + * buffer to hold the converted string. The caller must free the result. + */ +char* CopyUNIToMOR(const UNICHAR* stringUNI) +{ + size_t morLen; + char* morBuf; + + morLen = NuConvertUNIToMOR(stringUNI, NULL, 0); + if (morLen == (size_t) -1) { + return NULL; + } + morBuf = (char*) malloc(morLen); + (void) NuConvertUNIToMOR(stringUNI, morBuf, morLen); + return morBuf; +} + +/* + * Utility function that wraps NuConvertMORToUNI, allocating a new + * buffer to hold the converted string. The caller must free the result. + */ +UNICHAR* CopyMORToUNI(const char* stringMOR) +{ + size_t uniLen; + char* uniBuf; + + uniLen = NuConvertMORToUNI(stringMOR, NULL, 0); + if (uniLen == (size_t) -1) { + return NULL; + } + uniBuf = (UNICHAR*) malloc(uniLen); + (void) NuConvertMORToUNI(stringMOR, uniBuf, uniLen); + return uniBuf; +} + + +/* + * =========================================================================== + * Tests + * =========================================================================== + */ + +void DumpMorString(const char* str) +{ + printf("(%d) ", (int) strlen(str)); + while (*str != '\0') { + if (*str >= 0x20 && *str < 0x7f) { + putchar(*str); + } else { + printf("\\x%02x", (uint8_t) *str); + } + str++; + } + putchar('\n'); +} + +void DumpUnicharString(const UNICHAR* str) +{ + printf("(%d) ", (int) strlen(str)); + while (*str != '\0') { + if (*str >= 0x20 && *str < 0x7f) { + putchar(*str); + } else { + if (sizeof(UNICHAR) == 1) { + printf("\\x%02x", (uint8_t) *str); + } else { + printf("\\u%04x", (uint16_t) *str); + } + } + str++; + } + putchar('\n'); +} + +/* + * Some basic string conversion unit tests. + * + * TODO: test with short buffer, make sure we don't get partial code + * points when converting to Unicode + */ +int TestStringConversion(void) +{ + static const char kMORTest[] = "test\xe0\xe9\xed\xf3\xfa#\xf0\xb0"; + + size_t outLen; + char morBuf[512]; + UNICHAR uniBuf[512]; + + // convert test string to Unicode + memset(uniBuf, 0xcc, sizeof(uniBuf)); + //printf("MOR: "); DumpMorString(kMORTest); + + outLen = NuConvertMORToUNI(kMORTest, NULL, 0); + //printf("outLen is %u\n", (unsigned int) outLen); + if (NuConvertMORToUNI(kMORTest, uniBuf, sizeof(uniBuf)) != outLen) { + fprintf(stderr, "Inconsistent MORToUNI len\n"); + return -1; + } + //printf("UNI: "); DumpUnicharString(uniBuf); + if (strlen(uniBuf) + 1 != outLen) { + fprintf(stderr, "Expected length != actual length\n"); + return -1; + } + + // convert Unicode back to MOR + memset(morBuf, 0xcc, sizeof(morBuf)); + + outLen = NuConvertUNIToMOR(uniBuf, NULL, 0); + //printf("outLen is %u\n", (unsigned int) outLen); + if (NuConvertUNIToMOR(uniBuf, morBuf, sizeof(morBuf)) != outLen) { + fprintf(stderr, "Inconsistent UNIToMOR len\n"); + return -1; + } + //printf("MOR: "); DumpMorString(morBuf); + if (strlen(morBuf) + 1 != outLen) { + fprintf(stderr, "Expected length != actual length\n"); + return -1; + } + + // check vs. original + if (strcmp(kMORTest, morBuf) != 0) { + fprintf(stderr, "Test string corrupted by double conversion\n"); + return -1; + } + +#ifdef USE_UTF16 + static const UNICHAR kNonMorUniStr[] = L"nlTest\u7e2e\u3080.shk"; + static const UNICHAR kBadUniStr[] = L"nlTest\u7e2e\x30"; +#else + static const UNICHAR kNonMorUniStr[] = "nlTest\xe7\xb8\xae\xe3\x82\x80.shk"; + static const UNICHAR kBadUniStr[] = "nlTest\x81\xe7"; +#endif + static const char kNonMorExpected[] = "nlTest??.shk"; + static const char kBadExpected[] = "nlTest??"; + + NuConvertUNIToMOR(kNonMorUniStr, morBuf, sizeof(morBuf)); + if (strcmp(morBuf, kNonMorExpected) != 0) { + fprintf(stderr, "Non-MOR string conversion failed\n"); + return -1; + } + + NuConvertUNIToMOR(kBadUniStr, morBuf, sizeof(morBuf)); + if (strcmp(morBuf, kBadExpected) != 0) { + fprintf(stderr, "Bad UNI string conversion failed\n"); + return -1; + } + + printf("... string conversion tests successful\n"); + + return 0; +} + +/* + * Create a new entry and give it a trivial data fork. + */ +int AddTestEntry(NuArchive* pArchive, const char* entryNameMOR) +{ + NuDataSource* pDataSource = NULL; + NuRecordIdx recordIdx; + static const char* kTestMsg = "Hello, world!\n"; + uint32_t status; + NuError err; + + /* + * Add our test entry. + */ + err = AddSimpleRecord(pArchive, entryNameMOR, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: add record failed (err=%d)\n", err); + goto failed; + } + + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, (const uint8_t*)kTestMsg, 0, strlen(kTestMsg), NULL, + &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: source create failed (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: thread add failed (err=%d)\n", err); + goto failed; + } + pDataSource = NULL; /* now owned by library */ + + /* + * Flush changes. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't flush after add (err=%d, status=%u)\n", + err, status); + goto failed; + } + + return 0; +failed: + if (pDataSource != NULL) + NuFreeDataSource(pDataSource); + return -1; +} + +/* + * Extract the file we created. + */ +int TestExtract(NuArchive* pArchive, const char* entryNameMOR) +{ + const NuRecord* pRecord; + NuRecordIdx recordIdx; + NuError err; + + err = NuGetRecordIdxByName(pArchive, entryNameMOR, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", + entryNameMOR, err); + return -1; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %u (err=%d)\n", + recordIdx, err); + return -1; + } + assert(pRecord != NULL); + + const NuThread* pThread = NULL; + uint32_t idx; + for (idx = 0; idx < NuRecordGetNumThreads(pRecord); idx++) { + pThread = NuGetThread(pRecord, idx); + + if (NuGetThreadID(pThread) == kNuThreadIDDataFork) + break; + } + if (pThread == NULL) { + fprintf(stderr, "ERROR: no data thread?\n"); + return -1; + } + + /* + * Prepare the output file. + */ + UNICHAR* entryNameUNI = CopyMORToUNI(entryNameMOR); + NuDataSink* pDataSink = NULL; + err = NuCreateDataSinkForFile(true, kNuConvertOff, entryNameUNI, + kLocalFssep, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink for file (err=%d)\n", + err); + free(entryNameUNI); + return -1; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: extract failed (err=%d)\n", err); + (void) NuFreeDataSink(pDataSink); + free(entryNameUNI); + return -1; + } + + (void) NuFreeDataSink(pDataSink); + + printf("... confirming extraction of '%s'\n", entryNameUNI); + if (access(entryNameUNI, F_OK) != 0) { + fprintf(stderr, "ERROR: unable to read '%s' (err=%d)\n", + entryNameUNI, errno); + free(entryNameUNI); + return -1; + } + + if (unlink(entryNameUNI) < 0) { + perror("unlink test entry"); + free(entryNameUNI); + return -1; + } + + free(entryNameUNI); + return 0; +} + +/* + * Run some tests. + * + * Returns 0 on success, -1 on error. + */ +int DoTests(void) +{ + NuError err; + NuArchive* pArchive = NULL; + char* testEntryNameMOR = NULL; + int result = 0; + + if (TestStringConversion() < 0) { + goto failed; + } + + /* + * Make sure we're starting with a clean slate. + */ + if (RemoveTestFile("Test archive", kTestArchive) < 0) { + goto failed; + } + if (RemoveTestFile("Test temp file", kTestTempFile) < 0) { + goto failed; + } + if (RemoveTestFile("Test entry", kTestEntryName) < 0) { + goto failed; + } + + testEntryNameMOR = CopyUNIToMOR(kTestEntryName); + + /* + * Create a new archive to play with. + */ + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat|kNuOpenExcl, + &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRW failed (err=%d)\n", err); + goto failed; + } + if (NuSetErrorMessageHandler(pArchive, ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: couldn't set message handler\n"); + goto failed; + } + + /* + * Add a single entry. + */ + if (AddTestEntry(pArchive, testEntryNameMOR) != 0) { + goto failed; + } + + printf("... checking presence of '%s' and '%s'\n", + kTestArchive, kTestTempFile); + + if (access(kTestTempFile, F_OK) != 0) { + /* in theory, NufxLib doesn't need to use the temp file we provide, + so this test isn't entirely sound */ + fprintf(stderr, "ERROR: did not find %s (err=%d)\n", + kTestTempFile, err); + goto failed; + } + + /* + * Close it and confirm that the file has the expected name. + */ + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: mid NuClose failed (err=%d)\n", err); + goto failed; + } + pArchive = NULL; + + if (access(kTestArchive, F_OK) != 0) { + fprintf(stderr, "ERROR: did not find %s (err=%d)\n", kTestArchive, err); + goto failed; + } + + /* + * Reopen it read-only. + */ + printf("... reopening archive read-only\n"); + err = NuOpenRO(kTestArchive, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRO failed (err=%d)\n", err); + goto failed; + } + if (NuSetErrorMessageHandler(pArchive, ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: couldn't set message handler\n"); + goto failed; + } + + /* + * Extract the file. + */ + if (TestExtract(pArchive, testEntryNameMOR) < 0) { + goto failed; + } + + /* + * That's all, folks... + */ + NuClose(pArchive); + pArchive = NULL; + + printf("... removing '%s'\n", kTestArchive); + if (unlink(kTestArchive) < 0) { + perror("unlink kTestArchive"); + goto failed; + } + + +leave: + if (pArchive != NULL) { + NuAbort(pArchive); + NuClose(pArchive); + } + free(testEntryNameMOR); + return result; + +failed: + result = -1; + goto leave; +} + + +/* + * Start here. + */ +int main(void) +{ + int32_t major, minor, bug; + const char* pBuildDate; + const char* pBuildFlags; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, &pBuildFlags); + printf("Using NuFX library v%d.%d.%d, built on or after\n" + " %s with [%s]\n\n", + major, minor, bug, pBuildDate, pBuildFlags); + + if (NuSetGlobalErrorMessageHandler(ErrorMessageHandler) == + kNuInvalidCallback) + { + fprintf(stderr, "ERROR: can't set the global message handler"); + exit(1); + } + + printf("... starting tests\n"); + + cc = DoTests(); + + printf("... tests ended, %s\n", cc == 0 ? "SUCCESS" : "FAILURE"); + exit(cc != 0); +} + diff --git a/nufxlib/samples/TestSimple.c b/nufxlib/samples/TestSimple.c new file mode 100644 index 0000000..cc77e8b --- /dev/null +++ b/nufxlib/samples/TestSimple.c @@ -0,0 +1,111 @@ +/* + * 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. + * + * Simple test program. Opens an archive, dumps the contents. + * + * If the first argument is "-", this will read from stdin. Otherwise, + * the first argument is taken to be an archive filename, and opened. + */ +#include +#include "NufxLib.h" +#include "Common.h" + + +/* + * Callback function to display the contents of a single record. + * + * "pRecord->filename" is the record's filename, whether from the record + * header, a filename thread, or a default value ("UNKNOWN", stuffed in + * when a record has no filename at all). + */ +NuResult ShowContents(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (NuRecord*) vpRecord; + + size_t bufLen = NuConvertMORToUNI(pRecord->filenameMOR, NULL, 0); + if (bufLen == (size_t) -1) { + fprintf(stderr, "GLITCH: unable to convert '%s'\n", + pRecord->filenameMOR); + } else { + UNICHAR* buf = (UNICHAR*) malloc(bufLen); + NuConvertMORToUNI(pRecord->filenameMOR, buf, bufLen); + printf("*** Filename = '%s'\n", buf); + free(buf); + } + + return kNuOK; +} + + +/* + * Dump the contents from the streaming input. + * + * If we're not interested in handling an archive on stdin, we could just + * pass the filename in here and use NuOpenRO instead. + */ +int DoStreamStuff(FILE* fp) +{ + NuError err; + NuArchive* pArchive = NULL; + + err = NuStreamOpenRO(fp, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open stream archive (err=%d)\n", err); + goto bail; + } + + printf("*** Streaming contents!\n"); + + err = NuContents(pArchive, ShowContents); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuContents failed (err=%d)\n", err); + goto bail; + } + +bail: + if (pArchive != NULL) { + NuError err2 = NuClose(pArchive); + if (err == kNuErrNone) + err = err2; + } + + return err; +} + + +/* + * Grab the name of an archive to read. If "-" was given, use stdin. + */ +int main(int argc, char** argv) +{ + int32_t major, minor, bug; + const char* pBuildDate; + FILE* infp = NULL; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, NULL); + printf("Using NuFX lib %d.%d.%d built on or after %s\n", + major, minor, bug, pBuildDate); + + if (argc != 2) { + fprintf(stderr, "Usage: %s (archive-name|-)\n", argv[0]); + exit(2); + } + + if (strcmp(argv[1], "-") == 0) + infp = stdin; + else { + infp = fopen(argv[1], kNuFileOpenReadOnly); + if (infp == NULL) { + fprintf(stderr, "ERROR: unable to open '%s'\n", argv[1]); + exit(1); + } + } + + cc = DoStreamStuff(infp); + exit(cc != 0); +} + diff --git a/nufxlib/samples/TestTwirl.c b/nufxlib/samples/TestTwirl.c new file mode 100644 index 0000000..1712172 --- /dev/null +++ b/nufxlib/samples/TestTwirl.c @@ -0,0 +1,709 @@ +/* + * 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. + * + * Recompress records in place, several times, possibly deleting records + * or threads as we go. The goal is to perform a large number of operations + * that modify the archive without closing and reopening it. + * + * Depending on which #defines are enabled, this can be very destructive, + * so a copy of the archive is made before processing begins. + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + +/* copy the archive to this file before starting */ +static const char* kWorkFileName = "TwirlCopy678"; +static const char* kTempFileName = "TwirlTmp789"; + +/* after loading this much stuff into memory, flush changes */ +const int kMaxHeldLen = 1024 * 1024; + +/* + * A list of CRCs. + */ +typedef struct CRCList { + int numEntries; + uint16_t* entries; +} CRCList; + + +/* + * Returns true if the compression type is supported, false otherwise. + */ +int CompressionSupported(NuValue compression) +{ + int result; + + switch (compression) { + case kNuCompressNone: + result = true; + break; + case kNuCompressSQ: + result = (NuTestFeature(kNuFeatureCompressSQ) == kNuErrNone); + break; + case kNuCompressLZW1: + case kNuCompressLZW2: + result = (NuTestFeature(kNuFeatureCompressLZW) == kNuErrNone); + break; + case kNuCompressLZC12: + case kNuCompressLZC16: + result = (NuTestFeature(kNuFeatureCompressLZC) == kNuErrNone); + break; + case kNuCompressDeflate: + result = (NuTestFeature(kNuFeatureCompressDeflate) == kNuErrNone); + break; + case kNuCompressBzip2: + result = (NuTestFeature(kNuFeatureCompressBzip2) == kNuErrNone); + break; + default: + assert(false); + result = false; + } + + /*printf("Returning %d for %ld\n", result, compression);*/ + + return result; +} + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + + +/* + * Dump a CRC list. + */ +void DumpCRCs(const CRCList* pCRCList) +{ + int i; + + printf(" NumEntries: %d\n", pCRCList->numEntries); + + for (i = 0; i < pCRCList->numEntries; i++) + printf(" %5d: 0x%04x\n", i, pCRCList->entries[i]); +} + +/* + * Free a CRC list. + */ +void FreeCRCs(CRCList* pCRCList) +{ + if (pCRCList == NULL) + return; + + free(pCRCList->entries); + free(pCRCList); +} + +/* + * Gather a list of CRCs from the archive. + * + * We assume there are at most two data threads (e.g. data fork and rsrc + * fork) in a record. + * + * Returns the list on success, NULL on failure. + */ +CRCList* GatherCRCs(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + const NuMasterHeader* pMasterHeader; + CRCList* pCRCList = NULL; + uint16_t* pEntries = NULL; + long recCount, maxCRCs; + long recIdx, crcIdx; + int i; + + pCRCList = malloc(sizeof(*pCRCList)); + if (pCRCList == NULL) { + fprintf(stderr, "ERROR: couldn't alloc CRC list\n"); + err = kNuErrGeneric; + goto bail; + } + memset(pCRCList, 0, sizeof(*pCRCList)); + + /* get record count out of master header, just for fun */ + err = NuGetMasterHeader(pArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto bail; + } + recCount = pMasterHeader->mhTotalRecords; + maxCRCs = recCount * 2; + + pEntries = malloc(sizeof(*pEntries) * maxCRCs); + if (pEntries == NULL) { + fprintf(stderr, "ERROR: unable to alloc CRC list (%ld entries)\n", + maxCRCs); + err = kNuErrGeneric; + goto bail; + } + pCRCList->entries = pEntries; + + for (i = 0; i < maxCRCs; i++) + pEntries[i] = 0xdead; + + /* + * Enumerate our way through the records. If something was disturbed + * we should end up in a different place and the CRCs will be off. + */ + crcIdx = 0; + for (recIdx = 0; recIdx < recCount; recIdx++) { + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread; + + err = NuGetRecordIdxByPosition(pArchive, recIdx, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + recIdx, err); + goto bail; + } + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get recordIdx %u\n", recordIdx); + goto bail; + } + + if (NuRecordGetNumThreads(pRecord) == 0) { + fprintf(stderr, "ERROR: not expecting empty record (%u)!\n", + recordIdx); + err = kNuErrGeneric; + goto bail; + } + + int rsrcCrcIdx = -1; + for (i = 0; i < (int)NuRecordGetNumThreads(pRecord); i++) { + pThread = NuGetThread(pRecord, i); + if (pThread->thThreadClass == kNuThreadClassData) { + if (crcIdx >= maxCRCs) { + fprintf(stderr, "ERROR: CRC buffer exceeded\n"); + assert(false); + err = kNuErrGeneric; + goto bail; + } + + /* + * Ensure that the data fork CRC comes first. Otherwise + * we can fail if it gets rearranged. This is only a + * problem for GSHK-created archives that don't have + * threads for every fork, so "mask dataless" is create + * fake entries. + * + * The correct way to do this is to store a tuple + * { thread-kind, crc }, but that's more work. + */ + if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + rsrcCrcIdx = crcIdx; + } + + if (pThread->thThreadKind == kNuThreadKindDataFork && + rsrcCrcIdx != -1) + { + /* this is the data fork, we've already seen the + resource fork; swap entries */ + pEntries[crcIdx++] = pEntries[rsrcCrcIdx]; + pEntries[rsrcCrcIdx] = pThread->thThreadCRC; + } else { + pEntries[crcIdx++] = pThread->thThreadCRC; + } + } + } + } + + pCRCList->numEntries = crcIdx; + + DumpCRCs(pCRCList); + +bail: + if (err != kNuErrNone) { + FreeCRCs(pCRCList); + pCRCList = NULL; + } + return pCRCList; +} + + +/* + * Compare the current set of CRCs against our saved list. If any of + * the records or threads were deleted or rearranged, this will fail. + * I happen to think this is a *good* thing: if something is the least + * bit screwy, I want to know about it. + * + * Unfortunately, if we *deliberately* delete records, this can't + * help us with the survivors. + * + * Returns 0 on success, nonzero on failure. + */ +int CompareCRCs(NuArchive* pArchive, const CRCList* pOldCRCList) +{ + CRCList* pNewCRCList = NULL; + int result = -1; + int badCrc = 0; + int i; + + pNewCRCList = GatherCRCs(pArchive); + if (pNewCRCList == NULL) { + fprintf(stderr, "ERROR: unable to gather new list\n"); + goto bail; + } + + if (pOldCRCList->numEntries != pNewCRCList->numEntries) { + fprintf(stderr, "ERROR: numEntries mismatch: %d vs %d\n", + pOldCRCList->numEntries, pNewCRCList->numEntries); + goto bail; + } + + for (i = 0; i < pNewCRCList->numEntries; i++) { + if (pOldCRCList->entries[i] != pNewCRCList->entries[i]) { + fprintf(stderr, "ERROR: CRC mismatch: %5d old=0x%04x new=0x%04x\n", + i, pOldCRCList->entries[i], pNewCRCList->entries[i]); + badCrc = 1; + } + } + if (!badCrc) { + printf(" Matched %d CRCs\n", pOldCRCList->numEntries); + result = 0; + } + +bail: + FreeCRCs(pNewCRCList); + return result; +} + + +/* + * Recompress a single thread. + * + * This entails (1) extracting the existing thread, (2) deleting the + * thread, and (3) adding the extracted data. + * + * All of this good stuff gets queued up until the next NuFlush call. + */ +NuError RecompressThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = NULL; + NuDataSink* pDataSink = NULL; + uint8_t* buf = NULL; + + if (pThread->actualThreadEOF == 0) { + buf = malloc(1); + if (buf == NULL) { + fprintf(stderr, "ERROR: failed allocating trivial buffer\n"); + err = kNuErrGeneric; + goto bail; + } + } else { + /* + * Create a buffer and data sink to hold the data. + */ + buf = malloc(pThread->actualThreadEOF); + if (buf == NULL) { + fprintf(stderr, "ERROR: failed allocating %u bytes\n", + pThread->actualThreadEOF); + err = kNuErrGeneric; + goto bail; + } + + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n",err); + goto bail; + } + + /* + * Extract the data. + */ + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed extracting thread %u in '%s': %s\n", + pThread->threadIdx, pRecord->filenameMOR, NuStrError(err)); + goto bail; + } + } + + /* + * Delete the existing thread. + */ + err = NuDeleteThread(pArchive, pThread->threadIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to delete thread %u\n", + pThread->threadIdx); + goto bail; + } + + /* + * Create a data source for the new thread. Specify a callback to free + * the buffer when NufxLib is done with it. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, buf, 0, pThread->actualThreadEOF, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + buf = NULL; + + /* + * Create replacement thread. + */ + err = NuAddThread(pArchive, pRecord->recordIdx, NuGetThreadID(pThread), + pDataSource, NULL); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add new thread ID=0x%08x (err=%d)\n", + NuGetThreadID(pThread), err); + goto bail; + } + pDataSource = NULL; /* now owned by NufxLib */ + +bail: + NuFreeDataSink(pDataSink); + NuFreeDataSource(pDataSource); + free(buf); + return err; +} + +/* + * Recompress a single record. + * + * The amount of data we're holding in memory as a result of the + * recompression is placed in "*pLen". + */ +NuError RecompressRecord(NuArchive* pArchive, NuRecordIdx recordIdx, long* pLen) +{ + NuError err = kNuErrNone; + const NuRecord* pRecord; + const NuThread* pThread; + int i; + + printf(" Recompressing %u\n", recordIdx); + + *pLen = 0; + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get record %u (err=%d)\n", + recordIdx, err); + goto bail; + } + + for (i = 0; i < (int)NuRecordGetNumThreads(pRecord); i++) { + pThread = NuGetThread(pRecord, i); + if (pThread->thThreadClass == kNuThreadClassData) { + /*printf(" Recompressing %d (threadID=0x%08lx)\n", i, + NuGetThreadID(pThread));*/ + err = RecompressThread(pArchive, pRecord, pThread); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed recompressing thread %u " + " in record %u (err=%d)\n", + pThread->threadIdx, pRecord->recordIdx, err); + goto bail; + } + *pLen += pThread->actualThreadEOF; + } else { + /*printf(" Skipping %d (threadID=0x%08lx)\n", i, + NuGetThreadID(pThread));*/ + } + } + +bail: + return err; +} + +/* + * Recompress every data thread in the archive. + */ +NuError RecompressArchive(NuArchive* pArchive, NuValue compression) +{ + NuError err = kNuErrNone; + NuRecordIdx* pIndices = NULL; + NuAttr countAttr; + long heldLen; + long idx; + + err = NuSetValue(pArchive, kNuValueDataCompression, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set compression to %u (err=%d)\n", + compression, err); + goto bail; + } + + printf("Recompressing threads with compression type %u\n", compression); + + err = NuGetAttr(pArchive, kNuAttrNumRecords, &countAttr); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get numRecords (err=%d)\n", err); + goto bail; + } + + if (countAttr == 0) { + printf("No records found!\n"); + goto bail; + } + + /* + * Get all of the indices up front. This way, if something causes a + * record to "disappear" during processing, we will know about it. + */ + pIndices = malloc(countAttr * sizeof(*pIndices)); + if (pIndices == NULL) { + fprintf(stderr, "ERROR: malloc on %u indices failed\n", countAttr); + err = kNuErrGeneric; + goto bail; + } + + for (idx = 0; idx < (int)countAttr; idx++) { + err = NuGetRecordIdxByPosition(pArchive, idx, &pIndices[idx]); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + idx, err); + goto bail; + } + } + + /* + * Walk through the index list, handling each record individually. + */ + heldLen = 0; + for (idx = 0; idx < (int)countAttr; idx++) { + long recHeldLen; + + err = RecompressRecord(pArchive, pIndices[idx], &recHeldLen); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed recompressing record %u (err=%d)\n", + pIndices[idx], err); + goto bail; + } + + heldLen += recHeldLen; + + if (heldLen > kMaxHeldLen) { + uint32_t statusFlags; + + printf(" (flush)\n"); + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: intra-recompress flush failed: %s\n", + NuStrError(err)); + goto bail; + } + + heldLen = 0; + } + } + +bail: + free(pIndices); + return err; +} + +/* + * Initiate the twirling. + */ +int TwirlArchive(const char* filename) +{ + NuError err = kNuErrNone; + NuArchive* pArchive = NULL; + CRCList* pCRCList = NULL; + int compression; + int cc; + + /* + * Open the archive after removing any temp file remnants. + */ + cc = unlink(kTempFileName); + if (cc == 0) + printf("Removed stale temp file '%s'\n", kTempFileName); + + err = NuOpenRW(filename, kTempFileName, 0, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open archive '%s': %s\n", + filename, NuStrError(err)); + goto bail; + } + + /* + * Mask records with no data threads, so we don't have to + * special-case them. + */ + err = NuSetValue(pArchive, kNuValueMaskDataless, true); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't mask dataless (err=%d)\n", err); + goto bail; + } + + pCRCList = GatherCRCs(pArchive); + if (pCRCList == NULL) { + fprintf(stderr, "ERROR: unable to get CRC list\n"); + goto bail; + } + + /* + * For each type of compression, recompress the entire archive. + */ + for (compression = kNuCompressNone; compression <= kNuCompressBzip2; + compression++) + { + uint32_t statusFlags; + + if (!CompressionSupported(compression)) + continue; + + err = RecompressArchive(pArchive, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: recompress failed: %s\n", NuStrError(err)); + goto bail; + } + + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: post-recompress flush failed: %s\n", + NuStrError(err)); + goto bail; + } + } + + /* + * Same thing, reverse order. We want to start with the same one we + * ended on above, so we can practice skipping over things. + */ + for (compression = kNuCompressBzip2; compression >= kNuCompressNone; + compression--) + { + uint32_t statusFlags; + + if (!CompressionSupported(compression)) + continue; + + err = RecompressArchive(pArchive, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: recompress2 failed: %s\n", NuStrError(err)); + goto bail; + } + + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: post-recompress flush2 failed: %s\n", + NuStrError(err)); + goto bail; + } + } + + if (CompareCRCs(pArchive, pCRCList) != 0) { + fprintf(stderr, "ERROR: CRCs didn't match\n"); + goto bail; + } + + printf("Done!\n"); + +bail: + FreeCRCs(pCRCList); + if (pArchive != NULL) { + NuAbort(pArchive); + NuClose(pArchive); + } + + return (err != kNuErrNone); +} + + +/* + * Copy from the current offset in "srcfp" to a new file called + * "outFileName". Returns a writable file descriptor for the new file + * on success, or NULL on error. + * + * (Note "CopyFile()" exists under Win32.) + */ +FILE* MyCopyFile(const char* outFileName, FILE* srcfp) +{ + char buf[24576]; + FILE* outfp; + size_t count; + + outfp = fopen(outFileName, kNuFileOpenWriteTrunc); + if (outfp == NULL) { + fprintf(stderr, "ERROR: unable to open '%s' (err=%d)\n", outFileName, + errno); + return NULL; + } + + while (!feof(srcfp)) { + count = fread(buf, 1, sizeof(buf), srcfp); + if (count == 0) + break; + if (fwrite(buf, 1, count, outfp) != count) { + fprintf(stderr, "ERROR: failed writing outfp (err=%d)\n", errno); + fclose(outfp); + return NULL; + } + } + + if (ferror(srcfp)) { + fprintf(stderr, "ERROR: failed reading srcfp (err=%d)\n", errno); + fclose(outfp); + return NULL; + } + + return outfp; +} + +/* + * Let's get started. + */ +int main(int argc, char** argv) +{ + int32_t major, minor, bug; + const char* pBuildDate; + FILE* srcfp = NULL; + FILE* infp = NULL; + int cc; + + /* don't buffer output */ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, NULL); + printf("Using NuFX lib %d.%d.%d built on or after %s\n\n", + major, minor, bug, pBuildDate); + + if (argc == 2) { + srcfp = fopen(argv[1], kNuFileOpenReadOnly); + if (srcfp == NULL) { + perror("fopen failed"); + exit(1); + } + } else { + fprintf(stderr, "ERROR: you have to specify a filename\n"); + exit(2); + } + + printf("Copying '%s' to '%s'\n", argv[1], kWorkFileName); + + infp = MyCopyFile(kWorkFileName, srcfp); + if (infp == NULL) { + fprintf(stderr, "Copy failed, bailing.\n"); + exit(1); + } + fclose(srcfp); + fclose(infp); + + cc = TwirlArchive(kWorkFileName); + + exit(cc != 0); +} + diff --git a/qasm.json b/qasm.json new file mode 100644 index 0000000..d7c21ab --- /dev/null +++ b/qasm.json @@ -0,0 +1,23 @@ +{ + "general": { + "prefix": [ + { + "0": "{PWD}" + }, + { + "1": "0/source" + }, + { + "2": "0/object" + } + ] + }, + "assembler": {}, + "linker": {}, + "format": { + "tabs": "12,18,30" + }, + "diskimg": { + "script": "./disk_commands.txt" + } +} \ No newline at end of file