mirror of https://github.com/fadden/nulib2.git
Imported sources.
This commit is contained in:
commit
5615fa90d4
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,420 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Read and write
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read one little-endian bytes, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
uchar
|
||||||
|
Nu_ReadOneC(NuArchive* pArchive, FILE* fp, ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
|
||||||
|
return ic;
|
||||||
|
}
|
||||||
|
|
||||||
|
uchar
|
||||||
|
Nu_ReadOne(NuArchive* pArchive, FILE* fp)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
return Nu_ReadOneC(pArchive, fp, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write one byte, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uchar val, ushort* pCrc)
|
||||||
|
{
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
putc(val, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_WriteOne(NuArchive* pArchive, FILE* fp, uchar val)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
Nu_WriteOneC(pArchive, fp, val, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read two little-endian bytes, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
ushort
|
||||||
|
Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic1, ic2;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic1 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc);
|
||||||
|
ic2 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc);
|
||||||
|
|
||||||
|
return ic1 | ic2 << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort
|
||||||
|
Nu_ReadTwo(NuArchive* pArchive, FILE* fp)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
return Nu_ReadTwoC(pArchive, fp, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write two little-endian bytes, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, ushort val, ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic1, ic2;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic1 = val & 0xff;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc);
|
||||||
|
ic2 = val >> 8;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc);
|
||||||
|
|
||||||
|
putc(ic1, fp);
|
||||||
|
putc(ic2, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_WriteTwo(NuArchive* pArchive, FILE* fp, ushort val)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
Nu_WriteTwoC(pArchive, fp, val, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read four little-endian bytes, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
ulong
|
||||||
|
Nu_ReadFourC(NuArchive* pArchive, FILE* fp, ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic1, ic2, ic3, ic4;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic1 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc);
|
||||||
|
ic2 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc);
|
||||||
|
ic3 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic3, *pCrc);
|
||||||
|
ic4 = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic4, *pCrc);
|
||||||
|
|
||||||
|
return ic1 | ic2 << 8 | (ulong)ic3 << 16 | (ulong)ic4 << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong
|
||||||
|
Nu_ReadFour(NuArchive* pArchive, FILE* fp)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
return Nu_ReadFourC(pArchive, fp, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write four little-endian bytes, optionally computing a CRC.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_WriteFourC(NuArchive* pArchive, FILE* fp, ulong val, ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic1, ic2, ic3, ic4;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic1 = val & 0xff;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc);
|
||||||
|
ic2 = (val >> 8) & 0xff;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc);
|
||||||
|
ic3 = (val >> 16) & 0xff;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic3, *pCrc);
|
||||||
|
ic4 = val >> 24;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic4, *pCrc);
|
||||||
|
|
||||||
|
putc(ic1, fp);
|
||||||
|
putc(ic2, fp);
|
||||||
|
putc(ic3, fp);
|
||||||
|
putc(ic4, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_WriteFour(NuArchive* pArchive, FILE* fp, ulong val)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*=0*/;
|
||||||
|
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, ushort* pCrc)
|
||||||
|
{
|
||||||
|
NuDateTime temp;
|
||||||
|
int ic;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.second = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.minute = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.hour = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.year = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.day = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.month = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.extra = ic;
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
temp.weekDay = ic;
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuDateTime
|
||||||
|
Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, ushort* pCrc)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
return Nu_ReadDateTimeC(pArchive, fp, &dummyCrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write an 8-byte NuFX Date/Time structure.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime,
|
||||||
|
ushort* pCrc)
|
||||||
|
{
|
||||||
|
int ic;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
|
||||||
|
ic = dateTime.second;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.minute;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.hour;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.year;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.day;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.month;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.extra;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
ic = dateTime.weekDay;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
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,
|
||||||
|
ushort* pCrc)
|
||||||
|
{
|
||||||
|
uchar* buffer = vbuffer;
|
||||||
|
int ic;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
Assert(buffer != nil);
|
||||||
|
Assert(count > 0);
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
ic = getc(fp);
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
*buffer++ = ic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
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,
|
||||||
|
ushort* pCrc)
|
||||||
|
{
|
||||||
|
const uchar* buffer = vbuffer;
|
||||||
|
int ic;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(pCrc != nil);
|
||||||
|
Assert(buffer != nil);
|
||||||
|
Assert(count > 0);
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
ic = *buffer++;
|
||||||
|
*pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc);
|
||||||
|
putc(ic, fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_WriteBytes(NuArchive* pArchive, FILE* fp, const void* vbuffer, long count)
|
||||||
|
{
|
||||||
|
ushort dummyCrc /*= 0*/;
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* 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 != nil);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,482 @@
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the library GPL. It is
|
||||||
|
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
Licenses are intended to guarantee your freedom to share and change
|
||||||
|
free software--to make sure the software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Library General Public License, applies to some
|
||||||
|
specially designated Free Software Foundation software, and to any
|
||||||
|
other libraries whose authors decide to use it. You can use it for
|
||||||
|
your libraries, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if
|
||||||
|
you distribute copies of the library, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave
|
||||||
|
you. You must make sure that they, too, receive or can get the source
|
||||||
|
code. If you link a program with the library, you must provide
|
||||||
|
complete object files to the recipients so that they can relink them
|
||||||
|
with the library, after making changes to the library and recompiling
|
||||||
|
it. And you must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
Our method of protecting your rights has two steps: (1) copyright
|
||||||
|
the library, and (2) offer you this license which gives you legal
|
||||||
|
permission to copy, distribute and/or modify the library.
|
||||||
|
|
||||||
|
Also, for each distributor's protection, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
library. If the library is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original
|
||||||
|
version, so that any problems introduced by others will not reflect on
|
||||||
|
the original authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that companies distributing free
|
||||||
|
software will individually obtain patent licenses, thus in effect
|
||||||
|
transforming the program into proprietary software. To prevent this,
|
||||||
|
we have made it clear that any patent must be licensed for everyone's
|
||||||
|
free use or not licensed at all.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the ordinary
|
||||||
|
GNU General Public License, which was designed for utility programs. This
|
||||||
|
license, the GNU Library General Public License, applies to certain
|
||||||
|
designated libraries. This license is quite different from the ordinary
|
||||||
|
one; be sure to read it in full, and don't assume that anything in it is
|
||||||
|
the same as in the ordinary license.
|
||||||
|
|
||||||
|
The reason we have a separate public license for some libraries is that
|
||||||
|
they blur the distinction we usually make between modifying or adding to a
|
||||||
|
program and simply using it. Linking a program with a library, without
|
||||||
|
changing the library, is in some sense simply using the library, and is
|
||||||
|
analogous to running a utility program or application program. However, in
|
||||||
|
a textual and legal sense, the linked executable is a combined work, a
|
||||||
|
derivative of the original library, and the ordinary General Public License
|
||||||
|
treats it as such.
|
||||||
|
|
||||||
|
Because of this blurred distinction, using the ordinary General
|
||||||
|
Public License for libraries did not effectively promote software
|
||||||
|
sharing, because most developers did not use the libraries. We
|
||||||
|
concluded that weaker conditions might promote sharing better.
|
||||||
|
|
||||||
|
However, unrestricted linking of non-free programs would deprive the
|
||||||
|
users of those programs of all benefit from the free status of the
|
||||||
|
libraries themselves. This Library General Public License is intended to
|
||||||
|
permit developers of non-free programs to use free libraries, while
|
||||||
|
preserving your freedom as a user of such programs to change the free
|
||||||
|
libraries that are incorporated in them. (We have not seen how to achieve
|
||||||
|
this as regards changes in header files, but we have achieved it as regards
|
||||||
|
changes in the actual functions of the Library.) The hope is that this
|
||||||
|
will lead to faster development of free libraries.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow. Pay close attention to the difference between a
|
||||||
|
"work based on the library" and a "work that uses the library". The
|
||||||
|
former contains code derived from the library, while the latter only
|
||||||
|
works together with the library.
|
||||||
|
|
||||||
|
Note that it is possible for a library to be covered by the ordinary
|
||||||
|
General Public License rather than by this special one.
|
||||||
|
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library which
|
||||||
|
contains a notice placed by the copyright holder or other authorized
|
||||||
|
party saying it may be distributed under the terms of this Library
|
||||||
|
General Public License (also called "this License"). Each licensee is
|
||||||
|
addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data
|
||||||
|
prepared so as to be conveniently linked with application programs
|
||||||
|
(which use some of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work
|
||||||
|
which has been distributed under these terms. A "work based on the
|
||||||
|
Library" means either the Library or any derivative work under
|
||||||
|
copyright law: that is to say, a work containing the Library or a
|
||||||
|
portion of it, either verbatim or with modifications and/or translated
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is
|
||||||
|
included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For a library, complete source code means
|
||||||
|
all the source code for all modules it contains, plus any associated
|
||||||
|
interface definition files, plus the scripts used to control compilation
|
||||||
|
and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running a program using the Library is not restricted, and output from
|
||||||
|
such a program is covered only if its contents constitute a work based
|
||||||
|
on the Library (independent of the use of the Library in a tool for
|
||||||
|
writing it). Whether that is true depends on what the Library does
|
||||||
|
and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's
|
||||||
|
complete source code as you receive it, in any medium, provided that
|
||||||
|
you conspicuously and appropriately publish on each copy an
|
||||||
|
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||||
|
all the notices that refer to this License and to the absence of any
|
||||||
|
warranty; and distribute a copy of this License along with the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy,
|
||||||
|
and you may at your option offer warranty protection in exchange for a
|
||||||
|
fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion
|
||||||
|
of it, thus forming a work based on the Library, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no
|
||||||
|
charge to all third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a
|
||||||
|
table of data to be supplied by an application program that uses
|
||||||
|
the facility, other than as an argument passed when the facility
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,
|
||||||
|
in the event an application does not supply such function or
|
||||||
|
table, the facility still operates, and performs whatever part of
|
||||||
|
its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has
|
||||||
|
a purpose that is entirely well-defined independent of the
|
||||||
|
application. Therefore, Subsection 2d requires that any
|
||||||
|
application-supplied function or table used by this function must
|
||||||
|
be optional: if the application does not supply it, the square
|
||||||
|
root function must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Library,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Library, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library
|
||||||
|
with the Library (or with a work based on the Library) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||||
|
License instead of this License to a given copy of the Library. To do
|
||||||
|
this, you must alter all the notices that refer to this License, so
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,
|
||||||
|
instead of to this License. (If a newer version than version 2 of the
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
|
that version instead if you wish.) Do not make any other change in
|
||||||
|
these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all
|
||||||
|
subsequent copies and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of
|
||||||
|
the Library into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or
|
||||||
|
derivative of it, under Section 2) in object code or executable form
|
||||||
|
under the terms of Sections 1 and 2 above provided that you accompany
|
||||||
|
it with the complete corresponding machine-readable source code, which
|
||||||
|
must be distributed under the terms of Sections 1 and 2 above on a
|
||||||
|
medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy
|
||||||
|
from a designated place, then offering equivalent access to copy the
|
||||||
|
source code from the same place satisfies the requirement to
|
||||||
|
distribute the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the
|
||||||
|
Library, but is designed to work with the Library by being compiled or
|
||||||
|
linked with it, is called a "work that uses the Library". Such a
|
||||||
|
work, in isolation, is not a derivative work of the Library, and
|
||||||
|
therefore falls outside the scope of this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library
|
||||||
|
creates an executable that is a derivative of the Library (because it
|
||||||
|
contains portions of the Library), rather than a "work that uses the
|
||||||
|
library". The executable is therefore covered by this License.
|
||||||
|
Section 6 states terms for distribution of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file
|
||||||
|
that is part of the Library, the object code for the work may be a
|
||||||
|
derivative work of the Library even though the source code is not.
|
||||||
|
Whether this is true is especially significant if the work can be
|
||||||
|
linked without the Library, or if the work is itself a library. The
|
||||||
|
threshold for this to be true is not precisely defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data
|
||||||
|
structure layouts and accessors, and small macros and small inline
|
||||||
|
functions (ten lines or less in length), then the use of the object
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative
|
||||||
|
work. (Executables containing this object code plus portions of the
|
||||||
|
Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may
|
||||||
|
distribute the object code for the work under the terms of Section 6.
|
||||||
|
Any executables containing that work also fall under Section 6,
|
||||||
|
whether or not they are linked directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also compile or
|
||||||
|
link a "work that uses the Library" with the Library to produce a
|
||||||
|
work containing portions of the Library, and distribute that work
|
||||||
|
under terms of your choice, provided that the terms permit
|
||||||
|
modification of the work for the customer's own use and reverse
|
||||||
|
engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the
|
||||||
|
Library is used in it and that the Library and its use are covered by
|
||||||
|
this License. You must supply a copy of this License. If the work
|
||||||
|
during execution displays copyright notices, you must include the
|
||||||
|
copyright notice for the Library among them, as well as a reference
|
||||||
|
directing the user to the copy of this License. Also, you must do one
|
||||||
|
of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding
|
||||||
|
machine-readable source code for the Library including whatever
|
||||||
|
changes were used in the work (which must be distributed under
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked
|
||||||
|
with the Library, with the complete machine-readable "work that
|
||||||
|
uses the Library", as object code and/or source code, so that the
|
||||||
|
user can modify the Library and then relink to produce a modified
|
||||||
|
executable containing the modified Library. (It is understood
|
||||||
|
that the user who changes the contents of definitions files in the
|
||||||
|
Library will not necessarily be able to recompile the application
|
||||||
|
to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Accompany the work with a written offer, valid for at
|
||||||
|
least three years, to give the same user the materials
|
||||||
|
specified in Subsection 6a, above, for a charge no more
|
||||||
|
than the cost of performing this distribution.
|
||||||
|
|
||||||
|
c) If distribution of the work is made by offering access to copy
|
||||||
|
from a designated place, offer equivalent access to copy the above
|
||||||
|
specified materials from the same place.
|
||||||
|
|
||||||
|
d) Verify that the user has already received a copy of these
|
||||||
|
materials or that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the
|
||||||
|
Library" must include any data and utility programs needed for
|
||||||
|
reproducing the executable from it. However, as a special exception,
|
||||||
|
the source code distributed need not include anything that is normally
|
||||||
|
distributed (in either source or binary form) with the major
|
||||||
|
components (compiler, kernel, and so on) of the operating system on
|
||||||
|
which the executable runs, unless that component itself accompanies
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license
|
||||||
|
restrictions of other proprietary libraries that do not normally
|
||||||
|
accompany the operating system. Such a contradiction means you cannot
|
||||||
|
use both them and the Library together in an executable that you
|
||||||
|
distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the
|
||||||
|
Library side-by-side in a single library together with other library
|
||||||
|
facilities not covered by this License, and distribute such a combined
|
||||||
|
library, provided that the separate distribution of the work based on
|
||||||
|
the Library and of the other library facilities is otherwise
|
||||||
|
permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work
|
||||||
|
based on the Library, uncombined with any other library
|
||||||
|
facilities. This must be distributed under the terms of the
|
||||||
|
Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact
|
||||||
|
that part of it is a work based on the Library, and explaining
|
||||||
|
where to find the accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute
|
||||||
|
the Library except as expressly provided under this License. Any
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or
|
||||||
|
distribute the Library is void, and will automatically terminate your
|
||||||
|
rights under this License. However, parties who have received copies,
|
||||||
|
or rights, from you under this License will not have their licenses
|
||||||
|
terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Library or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Library (or any work based on the
|
||||||
|
Library), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the
|
||||||
|
Library), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute, link with or modify the Library
|
||||||
|
subject to these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Library at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Library by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Library under this License may add
|
||||||
|
an explicit geographical distribution limitation excluding those countries,
|
||||||
|
so that distribution is permitted only in or among countries not thus
|
||||||
|
excluded. In such case, this License incorporates the limitation as if
|
||||||
|
written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new
|
||||||
|
versions of the Library General Public License from time to time.
|
||||||
|
Such new versions will be similar in spirit to the present version,
|
||||||
|
but may differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library
|
||||||
|
specifies a version number of this License which applies to it and
|
||||||
|
"any later version", you have the option of following the terms and
|
||||||
|
conditions either of that version or of any later version published by
|
||||||
|
the Free Software Foundation. If the Library does not specify a
|
||||||
|
license version number, you may choose any version ever published by
|
||||||
|
the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free
|
||||||
|
programs whose distribution conditions are incompatible with these,
|
||||||
|
write to the author to ask for permission. For software which is
|
||||||
|
copyrighted by the Free Software Foundation, write to the Free
|
||||||
|
Software Foundation; we sometimes make exceptions for this. Our
|
||||||
|
decision will be guided by the two goals of preserving the free status
|
||||||
|
of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||||
|
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||||
|
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||||
|
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||||
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||||
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Appendix: How to Apply These Terms to Your New Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest
|
||||||
|
possible use to the public, we recommend making it free software that
|
||||||
|
everyone can redistribute and change. You can do so by permitting
|
||||||
|
redistribution under these terms (or, alternatively, under the terms of the
|
||||||
|
ordinary General Public License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. It is
|
||||||
|
safest to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least the
|
||||||
|
"copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the library's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Library General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Library General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Library General Public
|
||||||
|
License along with this library; if not, write to the Free
|
||||||
|
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||||
|
MA 02111-1307, USA
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
|
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1990
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
|
@ -0,0 +1,154 @@
|
||||||
|
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
|
||||||
|
|
|
@ -0,0 +1,363 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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, ulong srcLen, ulong* pDstLen, ushort *pCrc)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
/*uchar* buffer = nil;*/
|
||||||
|
ulong count, getsize;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pStraw != nil);
|
||||||
|
Assert(fp != nil);
|
||||||
|
Assert(srcLen > 0);
|
||||||
|
|
||||||
|
*pDstLen = srcLen; /* get this over with */
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
if (pCrc != nil)
|
||||||
|
*pCrc = kNuInitialThreadCRC;
|
||||||
|
count = srcLen;
|
||||||
|
|
||||||
|
while (count) {
|
||||||
|
getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count;
|
||||||
|
|
||||||
|
err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getsize);
|
||||||
|
BailError(err);
|
||||||
|
if (pCrc != nil)
|
||||||
|
*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.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* 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 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".
|
||||||
|
*
|
||||||
|
* 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 = nil;
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
ulong srcLen, dstLen;
|
||||||
|
ushort threadCrc;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
/* okay if pProgressData is nil */
|
||||||
|
Assert(dstFp != nil);
|
||||||
|
Assert(pThread != nil);
|
||||||
|
|
||||||
|
/* 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 = (ulong)-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! */
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* GSHK doesn't compress anything under 512 bytes */
|
||||||
|
if (pArchive->valMimicSHK && srcLen < kNuSHKLZWThreshold)
|
||||||
|
targetFormat = kNuThreadFormatUncompressed;
|
||||||
|
|
||||||
|
if (pProgressData != nil) {
|
||||||
|
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;
|
||||||
|
case kNuThreadFormatLZW1:
|
||||||
|
err = Nu_CompressLZW1(pArchive, pStraw, dstFp, srcLen, &dstLen,
|
||||||
|
&threadCrc);
|
||||||
|
break;
|
||||||
|
case kNuThreadFormatLZW2:
|
||||||
|
err = Nu_CompressLZW2(pArchive, pStraw, dstFp, srcLen, &dstLen,
|
||||||
|
&threadCrc);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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 != nil)
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* [didn't need to recompute CRC, but I was being paranoid] */
|
||||||
|
Assert(threadCrc == pThread->thThreadCRC);
|
||||||
|
|
||||||
|
pThread->thThreadEOF = srcLen;
|
||||||
|
pThread->thCompThreadEOF = dstLen;
|
||||||
|
pThread->thThreadFormat = kNuThreadFormatUncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Copy the already-compressed input.
|
||||||
|
*/
|
||||||
|
if (pProgressData != nil)
|
||||||
|
Nu_StrawSetProgressState(pStraw, kNuProgressCopying);
|
||||||
|
err = Nu_ProgressDataCompressPrep(pArchive, pStraw,
|
||||||
|
kNuThreadFormatUncompressed, srcLen);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen,
|
||||||
|
&dstLen, nil);
|
||||||
|
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 != nil) {
|
||||||
|
(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 = nil;
|
||||||
|
/*uchar* buffer = nil;*/
|
||||||
|
ulong srcLen, bufferLen;
|
||||||
|
ulong 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 = bufferLen;
|
||||||
|
/* 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, nil, &pStraw);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
count = srcLen;
|
||||||
|
/*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/
|
||||||
|
/*BailAlloc(buffer);*/
|
||||||
|
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 != nil && *ppSavedCopy == nil) {
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* Compute 16-bit CRCs.
|
||||||
|
*/
|
||||||
|
#define __Crc16_c__ 1
|
||||||
|
#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 ushort 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.
|
||||||
|
*/
|
||||||
|
ushort
|
||||||
|
Nu_CalcCRC16(ushort seed, const uchar* ptr, int count)
|
||||||
|
{
|
||||||
|
ushort CRC = seed;
|
||||||
|
#ifndef CRC_TAB
|
||||||
|
int x;
|
||||||
|
Assert(sizeof(ushort) == 2); /* I think this is assumed */
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,385 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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) ? "<unknown>" : 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",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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 (MFS)", "Macintosh (HFS)",
|
||||||
|
"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, " <invalid> ");
|
||||||
|
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 uchar* 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 != nil);
|
||||||
|
|
||||||
|
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 = "<unknown>"; break;
|
||||||
|
}
|
||||||
|
printf("%sThreadKind: 0x%04x (%s)\n", kInd,
|
||||||
|
pThread->thThreadKind, descr);
|
||||||
|
|
||||||
|
printf("%sThreadCRC: 0x%04x ThreadEOF: %lu CompThreadEOF: %lu\n", kInd,
|
||||||
|
pThread->thThreadCRC, pThread->thThreadEOF, pThread->thCompThreadEOF);
|
||||||
|
printf("%s*File data offset: %ld actualThreadEOF: %ld\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;
|
||||||
|
ulong idx;
|
||||||
|
|
||||||
|
Assert(pRecord != nil);
|
||||||
|
|
||||||
|
printf("%s%s%sFilename: '%s' (idx=%lu)\n", kInd,
|
||||||
|
isDeleted ? "[DEL] " : "",
|
||||||
|
pXrefRecord != nil && pXrefRecord->pThreadMods != nil ? "[MOD] " : "",
|
||||||
|
pRecord->filename == nil ? "<not specified>" : pRecord->filename,
|
||||||
|
pRecord->recordIdx);
|
||||||
|
printf("%sHeaderID: '%.4s' VersionNumber: 0x%04x HeaderCRC: 0x%04x\n",
|
||||||
|
kInd,
|
||||||
|
pRecord->recNufxID, pRecord->recVersionNumber, pRecord->recHeaderCRC);
|
||||||
|
printf("%sAttribCount: %u TotalThreads: %lu\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%08lx ExtraType: 0x%08lx Access: 0x%08lx\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 != nil);
|
||||||
|
ConvertToHexStr(pRecord->recOptionList, pRecord->recOptionSize, outBuf);
|
||||||
|
printf("%sOptionList: [%s]\n", kInd, outBuf);
|
||||||
|
Nu_Free(pArchive, outBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s*ExtraCount: %ld RecFileOffset: %ld RecHeaderLength: %ld\n",
|
||||||
|
kInd,
|
||||||
|
pRecord->extraCount, pRecord->fileOffset, pRecord->recHeaderLength);
|
||||||
|
|
||||||
|
for (idx = 0; idx < pRecord->recTotalThreads; idx++) {
|
||||||
|
pThread = Nu_GetThread(pRecord, idx);
|
||||||
|
Assert(pThread != nil);
|
||||||
|
|
||||||
|
printf("%s--Thread #%lu (idx=%lu)\n", kInd, idx, pThread->threadIdx);
|
||||||
|
Nu_DebugDumpThread(pThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pXrefRecord != nil)
|
||||||
|
pThreadMod = pXrefRecord->pThreadMods;
|
||||||
|
else
|
||||||
|
pThreadMod = pRecord->pThreadMods; /* probably empty */
|
||||||
|
|
||||||
|
if (pThreadMod != nil)
|
||||||
|
printf("%s*ThreadMods -----\n", kInd);
|
||||||
|
while (pThreadMod != nil) {
|
||||||
|
switch (pThreadMod->entry.kind) {
|
||||||
|
case kNuThreadModAdd:
|
||||||
|
printf("%s *-ThreadMod ADD 0x%08lx 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 %06ld\n", kInd,
|
||||||
|
pThreadMod->entry.update.threadIdx);
|
||||||
|
break;
|
||||||
|
case kNuThreadModDelete:
|
||||||
|
printf("%s *-ThreadMod DELETE %06ld\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: %ld\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 = nil;
|
||||||
|
if (pXrefSet != nil && 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 != nil) {
|
||||||
|
Assert(count != 0);
|
||||||
|
while (count--) {
|
||||||
|
Assert(pRecord != nil);
|
||||||
|
|
||||||
|
if (pXrefRecord != nil &&
|
||||||
|
pRecord->recordIdx == pXrefRecord->recordIdx)
|
||||||
|
{
|
||||||
|
Nu_DebugDumpRecord(pArchive, pRecord, pXrefRecord, false);
|
||||||
|
pXrefRecord = pXrefRecord->pNext;
|
||||||
|
} else {
|
||||||
|
Nu_DebugDumpRecord(pArchive, pRecord, nil, 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 != nil);
|
||||||
|
|
||||||
|
printf("%sNufileID: '%.6s' MasterCRC: 0x%04x TotalRecords: %lu\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: %lu\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 != nil);
|
||||||
|
|
||||||
|
printf("*Archive pathname: '%s'\n", pArchive->archivePathname);
|
||||||
|
printf("*Archive type: %d\n", pArchive->archiveType);
|
||||||
|
printf("*Header offset: %ld\n", pArchive->headerOffset);
|
||||||
|
printf("*Num records: %ld orig, %ld copy, %ld new\n",
|
||||||
|
Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet),
|
||||||
|
Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet),
|
||||||
|
Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet));
|
||||||
|
printf("*NuRecordIdx seed: %lu NuRecordIdx next: %lu\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, nil);
|
||||||
|
|
||||||
|
if (!Nu_RecordSet_GetLoaded(&pArchive->origRecordSet) &&
|
||||||
|
!Nu_RecordSet_GetLoaded(&pArchive->newRecordSet))
|
||||||
|
{
|
||||||
|
printf("*** DEBUG: original records not loaded yet? ***\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*DEBUG_MSGS*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,794 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
|
||||||
|
pArchive = pArchive;
|
||||||
|
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) != nil);
|
||||||
|
Assert(Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet) ==
|
||||||
|
pArchive->masterHeader.mhTotalRecords);
|
||||||
|
} else {
|
||||||
|
Assert(Nu_RecordSet_GetListHead(&pArchive->origRecordSet) == nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure we have open files to work with */
|
||||||
|
Assert(pArchive->archivePathname == nil || pArchive->archiveFp != nil);
|
||||||
|
if (pArchive->archivePathname != nil && pArchive->archiveFp == nil)
|
||||||
|
return kNuErrInternal;
|
||||||
|
Assert(pArchive->tmpPathname == nil || pArchive->tmpFp != nil);
|
||||||
|
if (pArchive->tmpPathname != nil && pArchive->tmpFp == nil)
|
||||||
|
return kNuErrInternal;
|
||||||
|
|
||||||
|
/* further validations */
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Streaming and non-streaming read-only
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuStreamOpenRO(FILE* infp, NuArchive** ppArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if (infp == nil || ppArchive == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
|
||||||
|
err = Nu_StreamOpenRO(infp, (NuArchive**) ppArchive);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Strictly non-streaming read-only
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuOpenRO(const char* filename, NuArchive** ppArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
err = Nu_OpenRO(filename, (NuArchive**) ppArchive);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetRecordIdxByName(NuArchive* pArchive, const char* name,
|
||||||
|
NuRecordIdx* pRecordIdx)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_GetRecordIdxByName(pArchive, name, pRecordIdx);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetRecordIdxByPosition(NuArchive* pArchive, unsigned long 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
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuOpenRW(const char* archivePathname, const char* tmpPathname,
|
||||||
|
unsigned long flags, NuArchive** ppArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
err = Nu_OpenRW(archivePathname, tmpPathname, flags,
|
||||||
|
(NuArchive**) ppArchive);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuFlush(NuArchive* pArchive, long* pStatusFlags)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_Flush(pArchive, pStatusFlags);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuAbort(NuArchive* pArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_Abort(pArchive);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, nil);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuAddFile(NuArchive* pArchive, const char* pathname,
|
||||||
|
const NuFileDetails* pFileDetails, short isFromRsrcFork,
|
||||||
|
NuRecordIdx* pRecordIdx)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_AddFile(pArchive, pathname, pFileDetails,
|
||||||
|
(Boolean)(isFromRsrcFork != 0), pRecordIdx);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuRename(NuArchive* pArchive, NuRecordIdx recordIdx, const char* pathname,
|
||||||
|
char fssep)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_Rename(pArchive, recordIdx, pathname, fssep);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuUpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx,
|
||||||
|
NuDataSource* pDataSource, long* pMaxLen)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_UpdatePresizedThread(pArchive, threadIdx,
|
||||||
|
pDataSource, pMaxLen);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuDelete(NuArchive* pArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) {
|
||||||
|
Nu_SetBusy(pArchive);
|
||||||
|
err = Nu_Delete(pArchive);
|
||||||
|
Nu_ClearBusy(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetMasterHeader(NuArchive* pArchive, const NuMasterHeader** ppMasterHeader)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
err = Nu_GetMasterHeader(pArchive, ppMasterHeader);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetExtraData(NuArchive* pArchive, void** ppData)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if (ppData == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
*ppData = pArchive->extraData;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetExtraData(NuArchive* pArchive, void* pData)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->extraData = pData;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
return Nu_GetValue(pArchive, ident, pValue);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetValue(NuArchive* pArchive, NuValueID ident, NuValue value)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
return Nu_SetValue(pArchive, ident, value);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
return Nu_GetAttr(pArchive, ident, pAttr);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
NuStrError(NuError err)
|
||||||
|
{
|
||||||
|
return Nu_StrError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuGetVersion(long* pMajorVersion, long* pMinorVersion, long* pBugVersion,
|
||||||
|
const char** ppBuildDate, const char** ppBuildFlags)
|
||||||
|
{
|
||||||
|
return Nu_GetVersion(pMajorVersion, pMinorVersion, pBugVersion,
|
||||||
|
ppBuildDate, ppBuildFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Sources and Sinks
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSourceForFile(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, const char* pathname, short isFromRsrcFork,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
return Nu_DataSourceFile_New(threadFormat, (Boolean)(doClose != 0),
|
||||||
|
otherLen, pathname, (Boolean)(isFromRsrcFork != 0), ppDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSourceForFP(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, FILE* fp, long offset, long length,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
return Nu_DataSourceFP_New(threadFormat, (Boolean)(doClose != 0),
|
||||||
|
otherLen, fp, offset, length, ppDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, const unsigned char* buffer, long offset,
|
||||||
|
long length, NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
return Nu_DataSourceBuffer_New(threadFormat, (Boolean)(doClose != 0),
|
||||||
|
otherLen, buffer, offset, length, ppDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuFreeDataSource(NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
return Nu_DataSourceFree(pDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuDataSourceSetRawCrc(NuDataSource* pDataSource, unsigned short crc)
|
||||||
|
{
|
||||||
|
if (pDataSource == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
Nu_DataSourceSetRawCrc(pDataSource, crc);
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSinkForFile(short doExpand, NuValue convertEOL,
|
||||||
|
const char* pathname, char fssep, NuDataSink** ppDataSink)
|
||||||
|
{
|
||||||
|
return Nu_DataSinkFile_New((Boolean)(doExpand != 0), convertEOL, pathname,
|
||||||
|
fssep, ppDataSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSinkForFP(short doExpand, NuValue convertEOL, FILE* fp,
|
||||||
|
NuDataSink** ppDataSink)
|
||||||
|
{
|
||||||
|
return Nu_DataSinkFP_New((Boolean)(doExpand != 0), convertEOL, fp,
|
||||||
|
ppDataSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuCreateDataSinkForBuffer(short doExpand, NuValue convertEOL,
|
||||||
|
unsigned char* buffer, unsigned long bufLen, NuDataSink** ppDataSink)
|
||||||
|
{
|
||||||
|
return Nu_DataSinkBuffer_New((Boolean)(doExpand != 0), convertEOL, buffer,
|
||||||
|
bufLen, ppDataSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuFreeDataSink(NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
return Nu_DataSinkFree(pDataSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuDataSinkGetOutCount(NuDataSink* pDataSink, ulong* pOutCount)
|
||||||
|
{
|
||||||
|
if (pDataSink == nil || pOutCount == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
|
||||||
|
*pOutCount = Nu_DataSinkGetOutCount(pDataSink);
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Non-archive operations
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuRecordCopyThreads(const NuRecord* pNuRecord, NuThread** ppThreads)
|
||||||
|
{
|
||||||
|
if (pNuRecord == nil || ppThreads == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
|
||||||
|
Assert(pNuRecord->pThreads != nil);
|
||||||
|
|
||||||
|
*ppThreads = Nu_Malloc(nil, pNuRecord->recTotalThreads * sizeof(NuThread));
|
||||||
|
if (*ppThreads == nil)
|
||||||
|
return kNuErrMalloc;
|
||||||
|
|
||||||
|
memcpy(*ppThreads, pNuRecord->pThreads,
|
||||||
|
pNuRecord->recTotalThreads * sizeof(NuThread));
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long
|
||||||
|
NuRecordGetNumThreads(const NuRecord* pNuRecord)
|
||||||
|
{
|
||||||
|
if (pNuRecord == nil)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return pNuRecord->recTotalThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NuThread*
|
||||||
|
NuThreadGetByIdx(const NuThread* pNuThread, long idx)
|
||||||
|
{
|
||||||
|
if (pNuThread == nil)
|
||||||
|
return nil;
|
||||||
|
return &pNuThread[idx]; /* can't range-check here */
|
||||||
|
}
|
||||||
|
|
||||||
|
short
|
||||||
|
NuIsPresizedThreadID(NuThreadID threadID)
|
||||||
|
{
|
||||||
|
return Nu_IsPresizedThreadID(threadID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Callback setters
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetSelectionFilter(NuArchive* pArchive, NuCallback filterFunc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
/*Assert(!((ulong)filterFunc % 4));*/
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->selectionFilterFunc = filterFunc;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetOutputPathnameFilter(NuArchive* pArchive, NuCallback filterFunc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
/*Assert(!((ulong)filterFunc % 4));*/
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->outputPathnameFunc = filterFunc;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetProgressUpdater(NuArchive* pArchive, NuCallback updateFunc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
/*Assert(!((ulong)updateFunc % 4));*/
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->progressUpdaterFunc = updateFunc;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetErrorHandler(NuArchive* pArchive, NuCallback errorFunc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
/*Assert(!((ulong)errorFunc % 4));*/
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->errorHandlerFunc = errorFunc;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetErrorMessageHandler(NuArchive* pArchive, NuCallback messageHandlerFunc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
/*Assert(!((ulong)messageHandlerFunc % 4));*/
|
||||||
|
|
||||||
|
if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone)
|
||||||
|
pArchive->messageHandlerFunc = messageHandlerFunc;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuError
|
||||||
|
NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc)
|
||||||
|
{
|
||||||
|
/*Assert(!((ulong)messageHandlerFunc % 4));*/
|
||||||
|
|
||||||
|
gNuGlobalErrorMessageHandler = messageHandlerFunc;
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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, ushort* pCrc)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
/*uchar* buffer = nil;*/
|
||||||
|
ulong count, getsize;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pThread != nil);
|
||||||
|
Assert(infp != nil);
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
|
||||||
|
/* 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 != nil)
|
||||||
|
*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;
|
||||||
|
/*uchar* buffer = nil;*/
|
||||||
|
ulong count, getsize;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pThread != nil);
|
||||||
|
Assert(infp != nil);
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
ushort calcCrc;
|
||||||
|
ushort* 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 = nil;
|
||||||
|
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;
|
||||||
|
case kNuThreadFormatHuffmanSQ:
|
||||||
|
err = kNuErrBadFormat;
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrNone,
|
||||||
|
"Huffman-compressed threads not supported");
|
||||||
|
break;
|
||||||
|
case kNuThreadFormatLZW1:
|
||||||
|
case kNuThreadFormatLZW2:
|
||||||
|
err = Nu_ExpandLZW(pArchive, pRecord, pThread, infp, pFunnel, pCalcCrc);
|
||||||
|
break;
|
||||||
|
case kNuThreadFormatLZC12:
|
||||||
|
case kNuThreadFormatLZC16:
|
||||||
|
err = kNuErrBadFormat;
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrNone,
|
||||||
|
"LZC-compressed threads not supported");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = kNuErrBadFormat;
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"format %u unknown", pThread->thThreadFormat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have a CRC to check, check it.
|
||||||
|
*/
|
||||||
|
if (pCalcCrc != nil) {
|
||||||
|
if (calcCrc != pThread->thThreadCRC) {
|
||||||
|
if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadThreadCRC)) {
|
||||||
|
err = kNuErrBadThreadCRC;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,804 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 char* origPathname)
|
||||||
|
{
|
||||||
|
const char* cp;
|
||||||
|
|
||||||
|
Assert(pProgressData != nil);
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pRecord != nil);
|
||||||
|
Assert(origPathname != nil);
|
||||||
|
|
||||||
|
pProgressData->pRecord = pRecord;
|
||||||
|
|
||||||
|
pProgressData->origPathname = origPathname;
|
||||||
|
pProgressData->pathname = pRecord->filename;
|
||||||
|
cp = strrchr(pRecord->filename,
|
||||||
|
NuGetSepFromSysInfo(pRecord->recFileSysInfo));
|
||||||
|
if (cp == nil || *(cp+1) == '\0')
|
||||||
|
pProgressData->filename = pProgressData->pathname;
|
||||||
|
else
|
||||||
|
pProgressData->filename = cp+1;
|
||||||
|
|
||||||
|
pProgressData->operation = kNuOpAdd;
|
||||||
|
pProgressData->state = kNuProgressPreparing;
|
||||||
|
/*pProgressData->compressedLength = 0;*/
|
||||||
|
/*pProgressData->compressedProgress = 0;*/
|
||||||
|
pProgressData->uncompressedLength = 0;
|
||||||
|
pProgressData->uncompressedProgress = 0;
|
||||||
|
|
||||||
|
pProgressData->compress.threadFormat = (NuThreadFormat)-1;
|
||||||
|
|
||||||
|
/* ya know... if this is nil, 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 char* newPathname, char newFssep,
|
||||||
|
NuValue convertEOL)
|
||||||
|
{
|
||||||
|
const NuThread* pThreadIter;
|
||||||
|
const char* cp;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
Assert(pProgressData != nil);
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pRecord != nil);
|
||||||
|
Assert(newPathname != nil);
|
||||||
|
Assert(newFssep != 0);
|
||||||
|
|
||||||
|
pProgressData->pRecord = pRecord;
|
||||||
|
pProgressData->expand.pThread = nil;
|
||||||
|
|
||||||
|
pProgressData->origPathname = pRecord->filename;
|
||||||
|
pProgressData->pathname = newPathname;
|
||||||
|
cp = strrchr(newPathname, newFssep);
|
||||||
|
if (cp == nil || *(cp+1) == '\0')
|
||||||
|
pProgressData->filename = newPathname;
|
||||||
|
else
|
||||||
|
pProgressData->filename = 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->expand.compressedLength = 0;*/
|
||||||
|
/*pProgressData->expand.compressedProgress = 0;*/
|
||||||
|
pProgressData->uncompressedLength = 0;
|
||||||
|
pProgressData->uncompressedProgress = 0;
|
||||||
|
|
||||||
|
/* ya know... if this is nil, 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, ulong sourceLen)
|
||||||
|
{
|
||||||
|
NuProgressData* pProgressData;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pStraw != nil);
|
||||||
|
Assert(sourceLen < 32767*65536);
|
||||||
|
|
||||||
|
pProgressData = pStraw->pProgress;
|
||||||
|
if (pProgressData == nil)
|
||||||
|
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 != nil);
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
Assert(pThread != nil);
|
||||||
|
|
||||||
|
pProgressData = pFunnel->pProgress;
|
||||||
|
if (pProgressData == nil)
|
||||||
|
return kNuErrNone;
|
||||||
|
|
||||||
|
/*pProgressData->compressedLength = pThread->thCompThreadEOF;*/
|
||||||
|
pProgressData->uncompressedLength = pThread->actualThreadEOF;
|
||||||
|
pProgressData->expand.pThread = pThread;
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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, const NuProgressData* pProgress)
|
||||||
|
{
|
||||||
|
NuResult result;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pProgress != nil);
|
||||||
|
|
||||||
|
if (pProgress->progressFunc == nil)
|
||||||
|
return kNuErrNone;
|
||||||
|
|
||||||
|
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 = nil;
|
||||||
|
|
||||||
|
Assert(ppFunnel != nil);
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
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->pProgress = pProgress;
|
||||||
|
|
||||||
|
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 == nil)
|
||||||
|
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, ulong maxBytes)
|
||||||
|
{
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
Assert(maxBytes > 0);
|
||||||
|
|
||||||
|
pFunnel->outMax = maxBytes;
|
||||||
|
if (pFunnel->outCount >= pFunnel->outMax)
|
||||||
|
pFunnel->outMaxExceeded = true;
|
||||||
|
else
|
||||||
|
pFunnel->outMaxExceeded = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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 kNuMaxHighASCII 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
static NuValue
|
||||||
|
Nu_DetermineConversion(NuFunnel* pFunnel, const uchar* buffer, ulong count)
|
||||||
|
{
|
||||||
|
ulong bufCount, numBinary, numLF, numCR;
|
||||||
|
uchar val;
|
||||||
|
|
||||||
|
if (count < kNuMinConvThreshold)
|
||||||
|
return kNuConvertOff;
|
||||||
|
|
||||||
|
bufCount = count;
|
||||||
|
numBinary = numLF = numCR = 0;
|
||||||
|
while (bufCount--) {
|
||||||
|
val = *buffer++;
|
||||||
|
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 > kNuMaxHighASCII)
|
||||||
|
return kNuConvertOff;
|
||||||
|
} else if (numBinary > (count / 100) * kNuMaxHighASCII)
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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 uchar* buf, ulong len)
|
||||||
|
{
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
Assert(pFunnel->pDataSink != nil);
|
||||||
|
Assert(buf != nil);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
uchar 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 uchar* buffer, ulong count)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
ulong progressCount = count;
|
||||||
|
|
||||||
|
/*if (pFunnel->outMaxExceeded)
|
||||||
|
return kNuErrOutMax;*/
|
||||||
|
|
||||||
|
if (pFunnel->convertEOL == kNuConvertAuto) {
|
||||||
|
/*
|
||||||
|
* 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 convert this.
|
||||||
|
*/
|
||||||
|
pFunnel->convertEOL = Nu_DetermineConversion(pFunnel, buffer, count);
|
||||||
|
DBUG(("+++ DetermineConversion --> %ld / %ld\n", pFunnel->convertEOL,
|
||||||
|
pFunnel->convertEOLFrom));
|
||||||
|
|
||||||
|
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 != nil)
|
||||||
|
pFunnel->pProgress->expand.convertEOL = pFunnel->convertEOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pFunnel->convertEOL == kNuConvertOff) {
|
||||||
|
/* write it straight */
|
||||||
|
Nu_FunnelPutBlock(pFunnel, buffer, count);
|
||||||
|
} else {
|
||||||
|
/* do the LF conversion */
|
||||||
|
Boolean lastCR = pFunnel->lastCR; /* local copy */
|
||||||
|
uchar uch;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We could get a significant speed improvement here by writing
|
||||||
|
* non-EOL chars as a larger block instead of single bytes.
|
||||||
|
*/
|
||||||
|
while (count--) {
|
||||||
|
if (*buffer == kNuCharCR) {
|
||||||
|
Nu_PutEOL(pFunnel);
|
||||||
|
lastCR = true;
|
||||||
|
} else if (*buffer == kNuCharLF) {
|
||||||
|
if (!lastCR)
|
||||||
|
Nu_PutEOL(pFunnel);
|
||||||
|
lastCR = false;
|
||||||
|
} else {
|
||||||
|
uch = *buffer;
|
||||||
|
Nu_FunnelPutBlock(pFunnel, &uch, 1);
|
||||||
|
lastCR = false;
|
||||||
|
}
|
||||||
|
buffer++;
|
||||||
|
}
|
||||||
|
pFunnel->lastCR = lastCR;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (pFunnel->outMaxExceeded)
|
||||||
|
err = kNuErrOutMax;*/
|
||||||
|
|
||||||
|
err = Nu_DataSinkGetError(pFunnel->pDataSink);
|
||||||
|
|
||||||
|
/* update progress counter with pre-LFCR count */
|
||||||
|
if (err == kNuErrNone && pFunnel->pProgress != nil)
|
||||||
|
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 uchar* buffer,
|
||||||
|
ulong count)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
|
||||||
|
/*pFunnel->inCount += count;*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If it will fit into the buffer, just copy it in.
|
||||||
|
*/
|
||||||
|
if (pFunnel->bufCount + count < kNuFunnelBufSize) {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
err = Nu_FunnelFlush(pArchive, pFunnel);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
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:
|
||||||
|
/*if (err == kNuErrNone)
|
||||||
|
err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel);*/
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the Funnel's progress state.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state)
|
||||||
|
{
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
|
||||||
|
if (pFunnel->pProgress == nil)
|
||||||
|
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 != nil);
|
||||||
|
Assert(pFunnel != nil);
|
||||||
|
|
||||||
|
pProgress = pFunnel->pProgress;
|
||||||
|
if (pProgress == nil)
|
||||||
|
return kNuErrNone; /* no progress meter attached */
|
||||||
|
|
||||||
|
/* don't continue if they're not accepting progress messages */
|
||||||
|
if (pProgress->progressFunc == nil)
|
||||||
|
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 != nil);
|
||||||
|
Assert(pFunnel->pDataSink != nil);
|
||||||
|
|
||||||
|
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 = nil;
|
||||||
|
|
||||||
|
Assert(ppStraw != nil);
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
|
||||||
|
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 == nil)
|
||||||
|
return kNuErrNone;
|
||||||
|
|
||||||
|
/* we don't own the data source or progress meter */
|
||||||
|
Nu_Free(pArchive, pStraw);
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the Funnel's progress state.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state)
|
||||||
|
{
|
||||||
|
Assert(pStraw != nil);
|
||||||
|
Assert(pStraw->pProgress != nil);
|
||||||
|
|
||||||
|
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 != nil);
|
||||||
|
Assert(pStraw != nil);
|
||||||
|
|
||||||
|
pProgress = pStraw->pProgress;
|
||||||
|
if (pProgress == nil)
|
||||||
|
return kNuErrNone; /* no progress meter attached */
|
||||||
|
|
||||||
|
/* don't continue if they're not accepting progress messages */
|
||||||
|
if (pProgress->progressFunc == nil)
|
||||||
|
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, uchar* buffer, long len)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
Assert(pArchive != nil);
|
||||||
|
Assert(pStraw != nil);
|
||||||
|
Assert(buffer != nil);
|
||||||
|
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 != nil) {
|
||||||
|
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 != nil);
|
||||||
|
assert(pStraw->pDataSource != nil);
|
||||||
|
|
||||||
|
pStraw->lastProgress = 0;
|
||||||
|
pStraw->lastDisplayed = 0;
|
||||||
|
|
||||||
|
return Nu_DataSourceRewind(pStraw->pDataSource);
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,110 @@
|
||||||
|
#
|
||||||
|
# Makefile for nufxlib stuff (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)
|
||||||
|
#
|
||||||
|
|
||||||
|
# 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@
|
||||||
|
|
||||||
|
SRCS = Archive.c ArchiveIO.c Compress.c Crc16.c Debug.c Deferred.c \
|
||||||
|
Entry.c Expand.c FileIO.c Funnel.c Lzw.c MiscStuff.c MiscUtils.c \
|
||||||
|
Record.c SourceSink.c Thread.c Value.c Version.c
|
||||||
|
OBJS = Archive.o ArchiveIO.o Compress.o Crc16.o Debug.o Deferred.o \
|
||||||
|
Entry.o Expand.o FileIO.o Funnel.o Lzw.o MiscStuff.o MiscUtils.o \
|
||||||
|
Record.o SourceSink.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 $(SHARED_PRODUCT)
|
||||||
|
$(AR) $@ $(OBJS)
|
||||||
|
@RANLIB@ $@
|
||||||
|
|
||||||
|
$(SHARED_PRODUCT): $(OBJS)
|
||||||
|
-rm -f $(STATIC_PRODUCT)
|
||||||
|
$(CC) @SHARE_FLAGS@ -o $@ $(OBJS)
|
||||||
|
|
||||||
|
# the build date is approximate, the build flags are accurate
|
||||||
|
Version.c: Version.c.in Makefile
|
||||||
|
(sed -e "s/BUILT/`date`/" -e "s/OPTFLAGS/$(OPT)/" < Version.c.in > Version.c)
|
||||||
|
|
||||||
|
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 Version.c
|
||||||
|
-rm -f Makefile Makefile.bak
|
||||||
|
-rm -f config.log config.cache config.status config.h
|
||||||
|
-rm -f tags
|
||||||
|
|
||||||
|
baktar:
|
||||||
|
@tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \
|
||||||
|
Makefile.msc 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/
|
||||||
|
|
||||||
|
depend:
|
||||||
|
makedepend -- $(CFLAGS) -I/usr/local/include -- $(SRCS)
|
||||||
|
@(cd samples; unset CFLAGS OBJS; @SET_MAKE@ $(MAKE) depend)
|
||||||
|
|
||||||
|
# DO NOT DELETE THIS LINE -- make depend depends on it.
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#
|
||||||
|
# Makefile for Microsoft C compilers. Tested against Visual C++ 6.0.
|
||||||
|
# Not pretty but it seems to work.
|
||||||
|
#
|
||||||
|
# Run with "nmake /f Makefile.msc".
|
||||||
|
#
|
||||||
|
# To build without debugging info, use "nmake nodebug=1".
|
||||||
|
#
|
||||||
|
|
||||||
|
# Windows magic
|
||||||
|
TARGETOS = BOTH
|
||||||
|
!include <ntwin32.mak>
|
||||||
|
|
||||||
|
# object files
|
||||||
|
OBJS1 = Archive.obj ArchiveIO.obj Compress.obj Crc16.obj Debug.obj Deferred.obj
|
||||||
|
OBJS2 = Entry.obj Expand.obj FileIO.obj Funnel.obj Lzw.obj MiscStuff.obj
|
||||||
|
OBJS3 = MiscUtils.obj Record.obj SourceSink.obj Thread.obj Value.obj Version.obj
|
||||||
|
OBJS = $(OBJS1) $(OBJS2) $(OBJS3)
|
||||||
|
|
||||||
|
!ifdef NODEBUG
|
||||||
|
#OPT = $(cdebug) /D NDEBUG /ML
|
||||||
|
OPT = $(cdebug) /ML
|
||||||
|
!else
|
||||||
|
OPT = $(cdebug) /MLd
|
||||||
|
!endif
|
||||||
|
|
||||||
|
BUILD_FLAGS = /W3 /GX /D "WIN32"
|
||||||
|
|
||||||
|
# how to compile sources
|
||||||
|
.c.obj:
|
||||||
|
@$(cc) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $<
|
||||||
|
|
||||||
|
|
||||||
|
PRODUCT = nufxlib.lib
|
||||||
|
|
||||||
|
all: $(PRODUCT) samples
|
||||||
|
|
||||||
|
samples::
|
||||||
|
@echo -
|
||||||
|
@echo - Change to the samples directory and run $(MAKE) there.
|
||||||
|
@echo -
|
||||||
|
|
||||||
|
# this isn't great, but it'll have to do
|
||||||
|
Version.c: Version.c.in
|
||||||
|
copy Version.c.in Version.c
|
||||||
|
@echo -
|
||||||
|
@echo - NOTE: you must update Version.c by hand (use flags=$(OPT))
|
||||||
|
@echo -
|
||||||
|
|
||||||
|
nufxlib.lib: $(OBJS)
|
||||||
|
$(link) -lib /out:$(PRODUCT) $(OBJS) /nologo
|
||||||
|
|
||||||
|
clean::
|
||||||
|
del *.obj
|
||||||
|
del $(PRODUCT)
|
||||||
|
|
||||||
|
Archive.obj: Archive.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
ArchiveIO.obj: ArchiveIO.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Compress.obj: Compress.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Crc16.obj: Crc16.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Debug.obj: Debug.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Deferred.obj: Deferred.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Entry.obj: Entry.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Expand.obj: Expand.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
FileIO.obj: FileIO.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Funnel.obj: Funnel.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Lzw.obj: Lzw.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
MiscStuff.obj: MiscStuff.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
MiscUtils.obj: MiscUtils.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Record.obj: Record.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
SourceSink.obj: SourceSink.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Thread.obj: Thread.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Value.obj: Value.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
Version.obj: Version.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 <ctype.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 __MiscStuff__
|
||||||
|
#define __MiscStuff__
|
||||||
|
|
||||||
|
#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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#define nil NULL /* I can't seem to stop typing 'nil' now */
|
||||||
|
|
||||||
|
typedef uchar 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) ( ((uint)(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)
|
||||||
|
#define DebugFill(addr, len) memset(addr, 0xa3, len)
|
||||||
|
#else
|
||||||
|
#define DebugFill(addr, len) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DebugAbort() abort()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define kInvalidFill (0xa3)
|
||||||
|
#define kInvalidPtr ((void*)0xa3a3a3a3)
|
||||||
|
|
||||||
|
#endif /*__MiscStuff__*/
|
|
@ -0,0 +1,358 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* Miscellaneous NufxLib utility functions.
|
||||||
|
*/
|
||||||
|
#define __MiscUtils_c__
|
||||||
|
#include "NufxLibPriv.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Big fat hairy global. Unfortunately this is unavoidable.
|
||||||
|
*/
|
||||||
|
NuCallback gNuGlobalErrorMessageHandler = nil;
|
||||||
|
|
||||||
|
|
||||||
|
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 nil or "unknown error" when the
|
||||||
|
* error value isn't 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 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";
|
||||||
|
|
||||||
|
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 nil/zero.
|
||||||
|
*
|
||||||
|
* Calling here with "pArchive"==nil 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 nil.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_ReportError(NuArchive* pArchive, const char* file, int line,
|
||||||
|
const char* function, Boolean isDebug, NuError err, const char* 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 != nil);
|
||||||
|
|
||||||
|
|
||||||
|
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 = nil;
|
||||||
|
if (err >= 0)
|
||||||
|
msg = strerror(err);
|
||||||
|
if (msg == nil)
|
||||||
|
msg = Nu_StrError(err);
|
||||||
|
|
||||||
|
#if defined(HAVE_SNPRINTF) && defined(SNPRINTF_DECLARED)
|
||||||
|
if (msg == nil)
|
||||||
|
cc = snprintf(buf+count, sizeof(buf) - count,
|
||||||
|
"(unknown err=%d)", err);
|
||||||
|
else
|
||||||
|
cc = snprintf(buf+count, sizeof(buf) - count, "%s", msg);
|
||||||
|
#else
|
||||||
|
#ifdef SPRINTF_RETURNS_INT
|
||||||
|
if (msg == nil)
|
||||||
|
cc = sprintf(buf+count, "(unknown err=%d)", err);
|
||||||
|
else
|
||||||
|
cc = sprintf(buf+count, "%s", msg);
|
||||||
|
Assert(cc > 0);
|
||||||
|
count += cc;
|
||||||
|
#else
|
||||||
|
if (msg == nil)
|
||||||
|
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 != nil && pArchive->messageHandlerFunc == nil) ||
|
||||||
|
(pArchive == nil && gNuGlobalErrorMessageHandler == nil))
|
||||||
|
{
|
||||||
|
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 == nil)
|
||||||
|
(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 == nil) {
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrMalloc, "malloc(%u) failed", (uint) 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 != nil); /* disallow this usage */
|
||||||
|
Assert(size > 0); /* disallow this usage */
|
||||||
|
_result = realloc(ptr, size);
|
||||||
|
if (_result == nil) {
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrMalloc, "realloc(%u) failed",(uint) size);
|
||||||
|
DebugAbort(); /* leave a core dump if we're built for it */
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nu_Free(NuArchive* pArchive, void* ptr)
|
||||||
|
{
|
||||||
|
if (ptr != nil)
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
NufxLib NOTES
|
||||||
|
Last revised: 2000/01/23
|
||||||
|
|
||||||
|
|
||||||
|
The interface is documented in "nufxlibapi.html", available from the
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,733 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* External interface (types, defines, and function prototypes).
|
||||||
|
*/
|
||||||
|
#ifndef __NufxLib__
|
||||||
|
#define __NufxLib__
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Types
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 */
|
||||||
|
|
||||||
|
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 */
|
||||||
|
} 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 unsigned long 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 unsigned long NuThreadIdx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thread ID, a combination of thread_class and thread_kind. Standard
|
||||||
|
* values have explicit identifiers.
|
||||||
|
*/
|
||||||
|
typedef unsigned long NuThreadID;
|
||||||
|
#define NuMakeThreadID(class, kind) /* construct a NuThreadID */ \
|
||||||
|
((unsigned long)class << 16 | (unsigned long)kind)
|
||||||
|
#define NuGetThreadID(pThread) /* pull NuThreadID out of NuThread */ \
|
||||||
|
(NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind))
|
||||||
|
#define NuThreadIDGetClass(threadID) /* get threadClass from NuThreadID */ \
|
||||||
|
((unsigned short) ((unsigned long)(threadID) >> 16))
|
||||||
|
#define NuThreadIDGetKind(threadID) /* get threadKind from NuThreadID */ \
|
||||||
|
((unsigned short) ((threadID) & 0xffff))
|
||||||
|
#define kNuThreadClassMessage 0x0000
|
||||||
|
#define kNuThreadClassControl 0x0001
|
||||||
|
#define kNuThreadClassData 0x0002
|
||||||
|
#define kNuThreadClassFilename 0x0003
|
||||||
|
#define kNuThreadIDOldComment NuMakeThreadID(kNuThreadClassMessage, 0x0000)
|
||||||
|
#define kNuThreadIDComment NuMakeThreadID(kNuThreadClassMessage, 0x0001)
|
||||||
|
#define kNuThreadIDIcon NuMakeThreadID(kNuThreadClassMessage, 0x0002)
|
||||||
|
#define kNuThreadIDMkdir NuMakeThreadID(kNuThreadClassControl, 0x0000)
|
||||||
|
#define kNuThreadIDDataFork NuMakeThreadID(kNuThreadClassData, 0x0000)
|
||||||
|
#define kNuThreadIDDiskImage NuMakeThreadID(kNuThreadClassData, 0x0001)
|
||||||
|
#define kNuThreadIDRsrcFork NuMakeThreadID(kNuThreadClassData, 0x0002)
|
||||||
|
#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
|
||||||
|
} NuThreadFormat;
|
||||||
|
|
||||||
|
|
||||||
|
/* extract the filesystem separator char from the "file_sys_info" field */
|
||||||
|
#define NuGetSepFromSysInfo(sysInfo) \
|
||||||
|
((char) ((sysInfo) & 0xff))
|
||||||
|
/* return a file_sys_info with a replaced filesystem separator */
|
||||||
|
#define NuSetSepInSysInfo(sysInfo, newSep) \
|
||||||
|
((ushort) (((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,
|
||||||
|
kNuStorageSeedling = 1, /* <= 512 bytes */
|
||||||
|
kNuStorageSapling = 2, /* < 128KB */
|
||||||
|
kNuStorageTree = 3, /* < 16MB */
|
||||||
|
kNuStorageExtended = 5, /* forked (any size) */
|
||||||
|
kNuStorageDirectory = 0x0d
|
||||||
|
} NuStorageType;
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
} NuValueID;
|
||||||
|
typedef unsigned long 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,
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
} NuAttrID;
|
||||||
|
typedef unsigned long 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.
|
||||||
|
*/
|
||||||
|
#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 {
|
||||||
|
unsigned char second;
|
||||||
|
unsigned char minute;
|
||||||
|
unsigned char hour;
|
||||||
|
unsigned char year;
|
||||||
|
unsigned char day;
|
||||||
|
unsigned char month;
|
||||||
|
unsigned char extra;
|
||||||
|
unsigned char weekDay;
|
||||||
|
} NuDateTime;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NuFX "thread" definition.
|
||||||
|
*/
|
||||||
|
typedef struct NuThread {
|
||||||
|
/* from the archive */
|
||||||
|
unsigned short thThreadClass;
|
||||||
|
NuThreadFormat thThreadFormat;
|
||||||
|
unsigned short thThreadKind;
|
||||||
|
unsigned short thThreadCRC; /* comp or uncomp data; see rec vers */
|
||||||
|
unsigned long thThreadEOF;
|
||||||
|
unsigned long thCompThreadEOF;
|
||||||
|
|
||||||
|
/* extra goodies */
|
||||||
|
NuThreadIdx threadIdx;
|
||||||
|
unsigned long actualThreadEOF; /* disk images might be off */
|
||||||
|
long fileOffset; /* fseek offset to data in file */
|
||||||
|
|
||||||
|
/* internal use only */
|
||||||
|
unsigned short used; /* mark as uninteresting */
|
||||||
|
} NuThread;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NuFX "record" definition.
|
||||||
|
*/
|
||||||
|
#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+ */
|
||||||
|
unsigned char recNufxID[kNufxIDLen];
|
||||||
|
unsigned short recHeaderCRC;
|
||||||
|
unsigned short recAttribCount;
|
||||||
|
unsigned short recVersionNumber;
|
||||||
|
unsigned long recTotalThreads;
|
||||||
|
NuFileSysID recFileSysID;
|
||||||
|
unsigned short recFileSysInfo;
|
||||||
|
unsigned long recAccess;
|
||||||
|
unsigned long recFileType;
|
||||||
|
unsigned long recExtraType;
|
||||||
|
unsigned short recStorageType; /* NuStorage*,file_sys_block_size */
|
||||||
|
NuDateTime recCreateWhen;
|
||||||
|
NuDateTime recModWhen;
|
||||||
|
NuDateTime recArchiveWhen;
|
||||||
|
|
||||||
|
/* option lists only in version 1+ */
|
||||||
|
unsigned short recOptionSize;
|
||||||
|
unsigned char* recOptionList; /* NULL if v0 or recOptionSize==0 */
|
||||||
|
|
||||||
|
/* data specified by recAttribCount, not accounted for by option list */
|
||||||
|
long extraCount;
|
||||||
|
unsigned char* extraBytes;
|
||||||
|
|
||||||
|
unsigned short recFilenameLength; /* usually zero */
|
||||||
|
char* recFilename; /* doubles as disk volume_name */
|
||||||
|
|
||||||
|
/* extra goodies; "dirtyHeader" does not apply to anything below */
|
||||||
|
NuRecordIdx recordIdx; /* session-unique record index */
|
||||||
|
char* threadFilename; /* extracted from filename thread */
|
||||||
|
char* newFilename; /* memorized during "add file" call */
|
||||||
|
const char* filename; /* points at recFilen or threadFilen */
|
||||||
|
unsigned long recHeaderLength; /* size of rec hdr, incl thread hdrs */
|
||||||
|
unsigned long totalCompLength; /* total len of data in archive file */
|
||||||
|
|
||||||
|
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 {
|
||||||
|
unsigned char mhNufileID[kNufileIDLen];
|
||||||
|
unsigned short mhMasterCRC;
|
||||||
|
unsigned long mhTotalRecords;
|
||||||
|
NuDateTime mhArchiveCreateWhen;
|
||||||
|
NuDateTime mhArchiveModWhen;
|
||||||
|
unsigned short mhMasterVersion;
|
||||||
|
unsigned char mhReserved1[kNufileMasterReserved1Len];
|
||||||
|
unsigned long mhMasterEOF;
|
||||||
|
unsigned char 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;
|
||||||
|
/*unsigned short fileSysInfo;*/
|
||||||
|
unsigned long access;
|
||||||
|
unsigned long fileType;
|
||||||
|
unsigned long extraType;
|
||||||
|
NuDateTime createWhen;
|
||||||
|
NuDateTime modWhen;
|
||||||
|
NuDateTime archiveWhen;
|
||||||
|
} NuRecordAttr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some additional details about a file.
|
||||||
|
*/
|
||||||
|
typedef struct NuFileDetails {
|
||||||
|
/* used during AddFile call */
|
||||||
|
NuThreadID threadID; /* data, rsrc, disk img? */
|
||||||
|
|
||||||
|
/* these go straight into the NuRecord */
|
||||||
|
const char* storageName;
|
||||||
|
NuFileSysID fileSysID;
|
||||||
|
unsigned short fileSysInfo;
|
||||||
|
unsigned long access;
|
||||||
|
unsigned long fileType;
|
||||||
|
unsigned long extraType;
|
||||||
|
unsigned short 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 OuputPathnameFilter callback.
|
||||||
|
*/
|
||||||
|
typedef struct NuPathnameProposal {
|
||||||
|
const char* pathname;
|
||||||
|
char filenameSeparator;
|
||||||
|
const NuRecord* pRecord;
|
||||||
|
const NuThread* pThread;
|
||||||
|
|
||||||
|
const char* newPathname;
|
||||||
|
unsigned char 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 */
|
||||||
|
|
||||||
|
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 */
|
||||||
|
kNuProgressFailed /* all done, failure */
|
||||||
|
} NuProgressState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Passed into the ProgressUpdater callback.
|
||||||
|
*
|
||||||
|
* [ 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 char* origPathname;
|
||||||
|
/* processed pathname (PathnameFilter for expand, in-record for compress) */
|
||||||
|
const char* pathname;
|
||||||
|
/* basename of "pathname" */
|
||||||
|
const char* filename;
|
||||||
|
/* pointer to the record we're expanding from */
|
||||||
|
const NuRecord* pRecord;
|
||||||
|
|
||||||
|
unsigned long uncompressedLength; /* size of uncompressed data */
|
||||||
|
unsigned long uncompressedProgress; /* #of bytes in/out */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuThreadFormat threadFormat; /* compression being applied */
|
||||||
|
} compress;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
unsigned long totalCompressedLength; /* all "data" threads */
|
||||||
|
unsigned long totalUncompressedLength;
|
||||||
|
|
||||||
|
/*unsigned long compressedLength; * size of compressed data */
|
||||||
|
/*unsigned long 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 char* message; /* (optional) message to user */
|
||||||
|
const NuRecord* pRecord; /* relevant record, if any */
|
||||||
|
const char* pathname; /* relevant pathname, if any */
|
||||||
|
char filenameSeparator; /* fssep for this path, 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 */
|
||||||
|
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 */
|
||||||
|
int line; /* line number */
|
||||||
|
const char* function; /* function name (might be nil) */
|
||||||
|
} NuErrorMessage;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Function prototypes
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* streaming and non-streaming read-only interfaces */
|
||||||
|
NuError NuStreamOpenRO(FILE* infp, NuArchive** ppArchive);
|
||||||
|
NuError NuContents(NuArchive* pArchive, NuCallback contentFunc);
|
||||||
|
NuError NuExtract(NuArchive* pArchive);
|
||||||
|
NuError NuTest(NuArchive* pArchive);
|
||||||
|
|
||||||
|
/* strictly non-streaming read-only interfaces */
|
||||||
|
NuError NuOpenRO(const char* archivePathname, NuArchive** ppArchive);
|
||||||
|
NuError NuExtractRecord(NuArchive* pArchive, NuRecordIdx recordIdx);
|
||||||
|
NuError NuExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx,
|
||||||
|
NuDataSink* pDataSink);
|
||||||
|
NuError NuGetRecord(NuArchive* pArchive, NuRecordIdx recordIdx,
|
||||||
|
const NuRecord** ppRecord);
|
||||||
|
NuError NuGetRecordIdxByName(NuArchive* pArchive, const char* name,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
NuError NuGetRecordIdxByPosition(NuArchive* pArchive, unsigned long position,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
|
||||||
|
/* read/write interfaces */
|
||||||
|
NuError NuOpenRW(const char* archivePathname, const char* tempPathname,
|
||||||
|
unsigned long flags, NuArchive** ppArchive);
|
||||||
|
NuError NuFlush(NuArchive* pArchive, long* pStatusFlags);
|
||||||
|
NuError NuAddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
NuError NuAddThread(NuArchive* pArchive, NuRecordIdx recordIdx,
|
||||||
|
NuThreadIdx threadID, NuDataSource* pDataSource,
|
||||||
|
NuThreadIdx* pThreadIdx);
|
||||||
|
NuError NuAddFile(NuArchive* pArchive, const char* pathname,
|
||||||
|
const NuFileDetails* pFileDetails, short fromRsrcFork,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
NuError NuRename(NuArchive* pArchive, NuRecordIdx recordIdx,
|
||||||
|
const char* pathname, char fssep);
|
||||||
|
NuError NuSetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx,
|
||||||
|
const NuRecordAttr* pRecordAttr);
|
||||||
|
NuError NuUpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx,
|
||||||
|
NuDataSource* pDataSource, long* pMaxLen);
|
||||||
|
NuError NuDelete(NuArchive* pArchive);
|
||||||
|
NuError NuDeleteRecord(NuArchive* pArchive, NuRecordIdx recordIdx);
|
||||||
|
NuError NuDeleteThread(NuArchive* pArchive, NuRecordIdx threadIdx);
|
||||||
|
|
||||||
|
/* general interfaces */
|
||||||
|
NuError NuClose(NuArchive* pArchive);
|
||||||
|
NuError NuAbort(NuArchive* pArchive);
|
||||||
|
NuError NuGetMasterHeader(NuArchive* pArchive,
|
||||||
|
const NuMasterHeader** ppMasterHeader);
|
||||||
|
NuError NuGetExtraData(NuArchive* pArchive, void** ppData);
|
||||||
|
NuError NuSetExtraData(NuArchive* pArchive, void* pData);
|
||||||
|
NuError NuGetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue);
|
||||||
|
NuError NuSetValue(NuArchive* pArchive, NuValueID ident, NuValue value);
|
||||||
|
NuError NuGetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr);
|
||||||
|
NuError NuGetVersion(long* pMajorVersion, long* pMinorVersion,
|
||||||
|
long* pBugVersion, const char** ppBuildDate,
|
||||||
|
const char** ppBuildFlags);
|
||||||
|
const char* NuStrError(NuError err);
|
||||||
|
NuError NuDebugDumpArchive(NuArchive* pArchive);
|
||||||
|
|
||||||
|
/* sources and sinks */
|
||||||
|
NuError NuCreateDataSourceForFile(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, const char* pathname, short isFromRsrcFork,
|
||||||
|
NuDataSource** ppDataSource);
|
||||||
|
NuError NuCreateDataSourceForFP(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, FILE* fp, long offset, long length,
|
||||||
|
NuDataSource** ppDataSource);
|
||||||
|
NuError NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, short doClose,
|
||||||
|
unsigned long otherLen, const unsigned char* buffer, long offset,
|
||||||
|
long length, NuDataSource** ppDataSource);
|
||||||
|
NuError NuFreeDataSource(NuDataSource* pDataSource);
|
||||||
|
NuError NuDataSourceSetRawCrc(NuDataSource* pDataSource, unsigned short crc);
|
||||||
|
NuError NuCreateDataSinkForFile(short doExpand, NuValue convertEOL,
|
||||||
|
const char* pathname, char fssep, NuDataSink** ppDataSink);
|
||||||
|
NuError NuCreateDataSinkForFP(short doExpand, NuValue convertEOL,
|
||||||
|
FILE* fp, NuDataSink** ppDataSink);
|
||||||
|
NuError NuCreateDataSinkForBuffer(short doExpand, NuValue convertEOL,
|
||||||
|
unsigned char* buffer, unsigned long bufLen,
|
||||||
|
NuDataSink** ppDataSink);
|
||||||
|
NuError NuFreeDataSink(NuDataSink* pDataSink);
|
||||||
|
NuError NuDataSinkGetOutCount(NuDataSink* pDataSink, unsigned long* pOutCount);
|
||||||
|
|
||||||
|
/* miscellaneous non-archive operations */
|
||||||
|
void NuRecordCopyAttr(NuRecordAttr* pRecordAttr, const NuRecord* pRecord);
|
||||||
|
NuError NuRecordCopyThreads(const NuRecord* pRecord, NuThread** ppThreads);
|
||||||
|
unsigned long NuRecordGetNumThreads(const NuRecord* pRecord);
|
||||||
|
const NuThread* NuThreadGetByIdx(const NuThread* pThread, long idx);
|
||||||
|
short NuIsPresizedThreadID(NuThreadID threadID);
|
||||||
|
|
||||||
|
#define NuGetThread(pRecord, idx) ( (const NuThread*) \
|
||||||
|
((unsigned long) (idx) < (unsigned long) (pRecord)->recTotalThreads ? \
|
||||||
|
&(pRecord)->pThreads[(idx)] : NULL) \
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/* callback setters */
|
||||||
|
NuError NuSetSelectionFilter(NuArchive* pArchive, NuCallback filterFunc);
|
||||||
|
NuError NuSetOutputPathnameFilter(NuArchive* pArchive, NuCallback filterFunc);
|
||||||
|
NuError NuSetProgressUpdater(NuArchive* pArchive, NuCallback updateFunc);
|
||||||
|
NuError NuSetErrorHandler(NuArchive* pArchive, NuCallback errorFunc);
|
||||||
|
NuError NuSetErrorMessageHandler(NuArchive* pArchive,
|
||||||
|
NuCallback messageHandlerFunc);
|
||||||
|
NuError NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /*__NufxLib__*/
|
|
@ -0,0 +1,827 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* Global internal declarations and definitions.
|
||||||
|
*/
|
||||||
|
#ifndef __NufxLibPriv__
|
||||||
|
#define __NufxLibPriv__
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
ulong numRecords;
|
||||||
|
NuRecord* nuRecordHead;
|
||||||
|
NuRecord* nuRecordTail;
|
||||||
|
} NuRecordSet;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Archive state.
|
||||||
|
*/
|
||||||
|
struct NuArchive {
|
||||||
|
ulong structMagic;
|
||||||
|
Boolean busy;
|
||||||
|
|
||||||
|
NuOpenMode openMode;
|
||||||
|
Boolean newlyCreated;
|
||||||
|
char* archivePathname; /* pathname or "(stream)" */
|
||||||
|
FILE* archiveFp;
|
||||||
|
NuArchiveType archiveType;
|
||||||
|
long headerOffset; /* adjustment for BXY/SEA/BSE */
|
||||||
|
|
||||||
|
char* tmpPathname; /* 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 char* lastFileCreated;
|
||||||
|
/* clumsy way to avoid trying to create the same subdir several times */
|
||||||
|
const char* lastDirCreated;
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
uchar* 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 valMimicSHK; /* mimic some ShrinkIt quirks */
|
||||||
|
NuValue valModifyOrig; /* modify original arc in place? */
|
||||||
|
NuValue valOnlyUpdateOlder; /* modify original arc in place? */
|
||||||
|
|
||||||
|
/* callback functions */
|
||||||
|
NuCallback selectionFilterFunc;
|
||||||
|
NuCallback outputPathnameFunc;
|
||||||
|
NuCallback progressUpdaterFunc;
|
||||||
|
NuCallback errorHandlerFunc;
|
||||||
|
NuCallback messageHandlerFunc;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define kNuArchiveStructMagic 0xc0edbabe
|
||||||
|
|
||||||
|
|
||||||
|
#define kNuDefaultRecordName "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* 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 */
|
||||||
|
uchar* buffer; /* kNuFunnelBufSize worth of storage */
|
||||||
|
long bufCount; /* #of bytes in buffer */
|
||||||
|
|
||||||
|
/* EOL 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 lastCR; /* was last char a CR? */
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
ulong inCount; /* total #of bytes in the top */
|
||||||
|
ulong outCount; /* total #of bytes out the bottom */
|
||||||
|
|
||||||
|
ulong 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 */
|
||||||
|
ulong lastProgress;
|
||||||
|
ulong lastDisplayed;
|
||||||
|
} NuStraw;
|
||||||
|
|
||||||
|
/*NuError Nu_CopyStreamToStream(FILE* outfp, FILE* infp, ulong 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? */
|
||||||
|
ushort rawCrc; /* crc for already-compressed data*/
|
||||||
|
Boolean doClose; /* close on completion? */
|
||||||
|
ulong dataLen; /* length of data (var for buf) */
|
||||||
|
ulong otherLen; /* uncomp len or preset buf size */
|
||||||
|
} NuDataSourceCommon;
|
||||||
|
|
||||||
|
union NuDataSource {
|
||||||
|
NuDataSourceType sourceType;
|
||||||
|
|
||||||
|
NuDataSourceCommon common;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSourceCommon common;
|
||||||
|
char* pathname;
|
||||||
|
Boolean fromRsrcFork;
|
||||||
|
|
||||||
|
/* temp storage; only valid when processing in library */
|
||||||
|
FILE* fp;
|
||||||
|
} fromFile;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSourceCommon common;
|
||||||
|
FILE* fp;
|
||||||
|
long offset; /* starting offset */
|
||||||
|
} fromFP;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSourceCommon common;
|
||||||
|
const uchar* buffer; /* non-const if doClose=true */
|
||||||
|
long offset; /* starting offset */
|
||||||
|
|
||||||
|
long curOffset; /* current offset */
|
||||||
|
long curDataLen; /* remaining 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") */
|
||||||
|
ulong outCount;
|
||||||
|
} NuDataSinkCommon;
|
||||||
|
|
||||||
|
union NuDataSink {
|
||||||
|
NuDataSinkType sinkType;
|
||||||
|
|
||||||
|
NuDataSinkCommon common;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSinkCommon common;
|
||||||
|
char* pathname; /* file to open */
|
||||||
|
char fssep;
|
||||||
|
|
||||||
|
/* temp storage; must be nil except when processing in library */
|
||||||
|
FILE* fp;
|
||||||
|
} toFile;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSinkCommon common;
|
||||||
|
FILE* fp;
|
||||||
|
} toFP;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
NuDataSinkCommon common;
|
||||||
|
uchar* buffer;
|
||||||
|
ulong 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.
|
||||||
|
*/
|
||||||
|
#ifdef DEBUG_MSGS
|
||||||
|
#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
|
||||||
|
#define DebugShowError(err) \
|
||||||
|
Nu_ReportError(pArchive, __FILE__, __LINE__, _FUNCTION_, \
|
||||||
|
true, err, "(DEBUG)");
|
||||||
|
#else
|
||||||
|
#define NU_BLOB pArchive, "", 0, "", false
|
||||||
|
#define NU_BLOB_DEBUG pArchive, "", 0, "", true
|
||||||
|
#define NU_NILBLOB NULL, "", 0, "", false
|
||||||
|
#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) == nil) { \
|
||||||
|
err = kNuErrUnexpectedNil; \
|
||||||
|
BailError(err); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
#define BailAlloc(val) { \
|
||||||
|
if ((val) == nil) { \
|
||||||
|
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 char* filename, NuArchive** ppArchive);
|
||||||
|
NuError Nu_OpenRW(const char* archivePathname, const char* tempPathname,
|
||||||
|
ulong 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 */
|
||||||
|
uchar Nu_ReadOneC(NuArchive* pArchive, FILE* fp, ushort* pCrc);
|
||||||
|
uchar Nu_ReadOne(NuArchive* pArchive, FILE* fp);
|
||||||
|
void Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uchar val, ushort* pCrc);
|
||||||
|
void Nu_WriteOne(NuArchive* pArchive, FILE* fp, uchar val);
|
||||||
|
ushort Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, ushort* pCrc);
|
||||||
|
ushort Nu_ReadTwo(NuArchive* pArchive, FILE* fp);
|
||||||
|
void Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, ushort val, ushort* pCrc);
|
||||||
|
void Nu_WriteTwo(NuArchive* pArchive, FILE* fp, ushort val);
|
||||||
|
ulong Nu_ReadFourC(NuArchive* pArchive, FILE* fp, ushort* pCrc);
|
||||||
|
ulong Nu_ReadFour(NuArchive* pArchive, FILE* fp);
|
||||||
|
void Nu_WriteFourC(NuArchive* pArchive, FILE* fp, ulong val, ushort* pCrc);
|
||||||
|
void Nu_WriteFour(NuArchive* pArchive, FILE* fp, ulong val);
|
||||||
|
NuDateTime Nu_ReadDateTimeC(NuArchive* pArchive, FILE* fp, ushort* pCrc);
|
||||||
|
NuDateTime Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, ushort* pCrc);
|
||||||
|
void Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime,
|
||||||
|
ushort* pCrc);
|
||||||
|
void Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime);
|
||||||
|
void Nu_ReadBytesC(NuArchive* pArchive, FILE* fp, void* vbuffer, long count,
|
||||||
|
ushort* pCrc);
|
||||||
|
void Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count);
|
||||||
|
void Nu_WriteBytesC(NuArchive* pArchive, FILE* fp, const void* vbuffer,
|
||||||
|
long count, ushort* 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);
|
||||||
|
|
||||||
|
/* 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 ushort gNuCrc16Table[256];
|
||||||
|
ushort Nu_CalcCRC16(ushort seed, const uchar* ptr, int count);
|
||||||
|
#ifdef __Crc16_c__ /* just doing "static inline" warns def-but-not-used */
|
||||||
|
#define CRC_INLINE /**/
|
||||||
|
#else
|
||||||
|
#define CRC_INLINE extern inline
|
||||||
|
#endif
|
||||||
|
#if defined(inline) && !defined(__Crc16_c__) /* somebody ovrd inline def? */
|
||||||
|
ushort Nu_UpdateCRC16(uchar val, ushort crc);
|
||||||
|
#else
|
||||||
|
CRC_INLINE ushort
|
||||||
|
Nu_UpdateCRC16(uchar val, ushort crc)
|
||||||
|
{
|
||||||
|
return (gNuCrc16Table[((crc >> 8) & 0xFF) ^ val] ^ (crc << 8)) & 0xFFFF;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 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, long* pStatusFlags);
|
||||||
|
|
||||||
|
/* 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 char* newPathname, char newFssep,
|
||||||
|
FILE** pFp);
|
||||||
|
NuError Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord,
|
||||||
|
FILE* fp, const char* pathname);
|
||||||
|
NuError Nu_OpenInputFile(NuArchive* pArchive, const char* pathname,
|
||||||
|
Boolean openRsrc, FILE** pFp);
|
||||||
|
NuError Nu_DeleteFile(const char* pathname);
|
||||||
|
NuError Nu_RenameFile(const char* fromPath, const char* toPath);
|
||||||
|
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 char* origPathname);
|
||||||
|
NuError Nu_ProgressDataInit_Expand(NuArchive* pArchive,
|
||||||
|
NuProgressData* pProgressData, const NuRecord* pRecord,
|
||||||
|
const char* newPathname, char newFssep, NuValue convertEOL);
|
||||||
|
NuError Nu_SendInitialProgress(NuArchive* pArchive,
|
||||||
|
const 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, ulong maxBytes);*/
|
||||||
|
NuError Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel,
|
||||||
|
const uchar* buffer, ulong count);
|
||||||
|
NuError Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel);
|
||||||
|
NuError Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw,
|
||||||
|
NuThreadFormat threadFormat, ulong 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, uchar* buffer,
|
||||||
|
long len);
|
||||||
|
NuError Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw);
|
||||||
|
|
||||||
|
/* Lzw.c */
|
||||||
|
NuError Nu_CompressLZW1(NuArchive* pArchive, NuStraw* pStraw, FILE* fp,
|
||||||
|
ulong srcLen, ulong* pDstLen, ushort* pCrc);
|
||||||
|
NuError Nu_CompressLZW2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp,
|
||||||
|
ulong srcLen, ulong* pDstLen, ushort* pCrc);
|
||||||
|
NuError Nu_ExpandLZW(NuArchive* pArchive, const NuRecord* pRecord,
|
||||||
|
const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc);
|
||||||
|
|
||||||
|
/* 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 char* 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 != nil ? 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
|
||||||
|
|
||||||
|
/* Record.c */
|
||||||
|
void Nu_RecordAddThreadMod(NuRecord* pRecord, NuThreadMod* pThreadMod);
|
||||||
|
Boolean Nu_RecordIsEmpty(NuArchive* pArchive, const NuRecord* pRecord);
|
||||||
|
Boolean Nu_RecordSet_GetLoaded(const NuRecordSet* pRecordSet);
|
||||||
|
ulong Nu_RecordSet_GetNumRecords(const NuRecordSet* pRecordSet);
|
||||||
|
void Nu_RecordSet_SetNumRecords(NuRecordSet* pRecordSet, ulong 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_GetRecord(NuArchive* pArchive, NuRecordIdx recordIdx,
|
||||||
|
const NuRecord** ppRecord);
|
||||||
|
NuError Nu_GetRecordIdxByName(NuArchive* pArchive, const char* name,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
NuError Nu_GetRecordIdxByPosition(NuArchive* pArchive, ulong position,
|
||||||
|
NuRecordIdx* pRecordIdx);
|
||||||
|
NuError Nu_FindRecordForWriteByIdx(NuArchive* pArchive, NuRecordIdx recIdx,
|
||||||
|
NuRecord** ppFoundRecord);
|
||||||
|
NuError Nu_AddFile(NuArchive* pArchive, const char* pathname,
|
||||||
|
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* pathname, char fssep);
|
||||||
|
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, Boolean doClose,
|
||||||
|
ulong otherLen, const char* pathname, Boolean isFromRsrcFork,
|
||||||
|
NuDataSource** ppDataSource);
|
||||||
|
NuError Nu_DataSourceFP_New(NuThreadFormat threadFormat, Boolean doClose,
|
||||||
|
ulong otherLen, FILE* fp, long offset, long length,
|
||||||
|
NuDataSource** ppDataSource);
|
||||||
|
NuError Nu_DataSourceBuffer_New(NuThreadFormat threadFormat,
|
||||||
|
Boolean doClose, ulong otherLen, const uchar* buffer, long offset,
|
||||||
|
long length, NuDataSource** ppDataSource);
|
||||||
|
NuDataSource* Nu_DataSourceCopy(NuDataSource* pDataSource);
|
||||||
|
NuError Nu_DataSourceFree(NuDataSource* pDataSource);
|
||||||
|
NuDataSourceType Nu_DataSourceGetType(const NuDataSource* pDataSource);
|
||||||
|
NuThreadFormat Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource);
|
||||||
|
ulong Nu_DataSourceGetDataLen(const NuDataSource* pDataSource);
|
||||||
|
ulong Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource);
|
||||||
|
void Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen);
|
||||||
|
ushort Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource);
|
||||||
|
void Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, ushort 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, uchar* buf, ulong len);
|
||||||
|
NuError Nu_DataSourceRewind(NuDataSource* pDataSource);
|
||||||
|
NuError Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL,
|
||||||
|
const char* pathname, char fssep, NuDataSink** ppDataSink);
|
||||||
|
NuError Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp,
|
||||||
|
NuDataSink** ppDataSink);
|
||||||
|
NuError Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL,
|
||||||
|
uchar* buffer, ulong 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);
|
||||||
|
ulong Nu_DataSinkGetOutCount(const NuDataSink* pDataSink);
|
||||||
|
const char* Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink);
|
||||||
|
char 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 uchar* buf, ulong len);
|
||||||
|
NuError Nu_DataSinkGetError(NuDataSink* pDataSink);
|
||||||
|
|
||||||
|
/* Thread.c */
|
||||||
|
#ifdef __Thread_c__
|
||||||
|
#define THREAD_INLINE /**/
|
||||||
|
#else
|
||||||
|
#define THREAD_INLINE extern inline
|
||||||
|
#endif
|
||||||
|
#if defined(inline) && !defined(__Thread_c__) /* somebody ovrd inline def? */
|
||||||
|
NuThread* Nu_GetThread(const NuRecord* pRecord, int idx);
|
||||||
|
#else
|
||||||
|
THREAD_INLINE NuThread*
|
||||||
|
Nu_GetThread(const NuRecord* pRecord, int idx)
|
||||||
|
{
|
||||||
|
if (idx >= (int)pRecord->recTotalThreads)
|
||||||
|
return nil;
|
||||||
|
else
|
||||||
|
return &pRecord->pThreads[idx];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
void Nu_StripHiIfAllSet(char* str);
|
||||||
|
Boolean Nu_IsPresizedThreadID(NuThreadID threadID);
|
||||||
|
Boolean Nu_IsCompressibleThreadID(NuThreadID threadID);
|
||||||
|
Boolean Nu_ThreadHasCRC(long 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,
|
||||||
|
ushort* pCrc);
|
||||||
|
NuError Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp,
|
||||||
|
ushort* 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, long* 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(long* pMajorVersion, long* pMinorVersion,
|
||||||
|
long* pBugVersion, const char** ppBuildDate, const char** ppBuildFlags);
|
||||||
|
|
||||||
|
#endif /*__NufxLibPriv__*/
|
|
@ -0,0 +1,92 @@
|
||||||
|
NufxLib README, updated 2000/05/18
|
||||||
|
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.
|
||||||
|
|
||||||
|
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". For pre-v1.0 sources,
|
||||||
|
setting -DNDEBUG is otherwise discouraged. The recommended configuration
|
||||||
|
is "-g -O2 -DDEBUG_MSGS", so that verbose debug output is available when
|
||||||
|
errors occur.
|
||||||
|
|
||||||
|
The flags are stuffed into Version.c, so the application program can
|
||||||
|
examine and display the flags that were used to build the library.
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
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++ 6.0. To build NufxLib,
|
||||||
|
start up a DOS shell and run vcvars32.bat to set your environment. Run:
|
||||||
|
nmake -f makefile.msc
|
||||||
|
to build with debugging info, or
|
||||||
|
nmake -f makefile.msc nodebug=1
|
||||||
|
to build optimized.
|
||||||
|
|
||||||
|
Once the library has been built, "cd samples" and run the same command there.
|
||||||
|
When it finishes, run "test-basic.exe".
|
||||||
|
|
||||||
|
|
||||||
|
Other Notes
|
||||||
|
===========
|
||||||
|
|
||||||
|
All of the source code was formatted with four-space hard tabs.
|
||||||
|
|
||||||
|
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.)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,866 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 != nil);
|
||||||
|
|
||||||
|
*ppDataSource = Nu_Malloc(nil, sizeof(**ppDataSource));
|
||||||
|
if (*ppDataSource == nil)
|
||||||
|
return kNuErrMalloc;
|
||||||
|
|
||||||
|
(*ppDataSource)->sourceType = kNuDataSourceUnknown;
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a copy of a DataSource.
|
||||||
|
*
|
||||||
|
* IMPORTANT: if the original had the "doClose" flag set, it will be cleared,
|
||||||
|
* but left set in the duplicate. The assumption is that the original will
|
||||||
|
* be thrown out before the duplicate. If this isn't the case, you will
|
||||||
|
* need to fix the flags on the copied data.
|
||||||
|
*
|
||||||
|
* Returns nil on error.
|
||||||
|
*/
|
||||||
|
NuDataSource*
|
||||||
|
Nu_DataSourceCopy(NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
NuDataSource* pNewDataSource;
|
||||||
|
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
|
||||||
|
if (Nu_DataSourceNew(&pNewDataSource) != kNuErrNone)
|
||||||
|
return nil;
|
||||||
|
Assert(pNewDataSource != nil);
|
||||||
|
|
||||||
|
/* 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 == nil); /* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free a data source structure, and any type-specific elements.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSourceFree(NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
if (pDataSource == nil)
|
||||||
|
return kNuErrNone;
|
||||||
|
|
||||||
|
switch (pDataSource->sourceType) {
|
||||||
|
case kNuDataSourceFromFile:
|
||||||
|
Nu_Free(nil, pDataSource->fromFile.pathname);
|
||||||
|
if (pDataSource->fromFile.fp != nil) {
|
||||||
|
fclose(pDataSource->fromFile.fp);
|
||||||
|
pDataSource->fromFile.fp = nil;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kNuDataSourceFromFP:
|
||||||
|
if (pDataSource->common.doClose) {
|
||||||
|
fclose(pDataSource->fromFile.fp);
|
||||||
|
pDataSource->fromFile.fp = nil;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kNuDataSourceFromBuffer:
|
||||||
|
if (pDataSource->common.doClose) {
|
||||||
|
Nu_Free(nil, (char*)pDataSource->fromBuffer.buffer);
|
||||||
|
pDataSource->fromBuffer.buffer = nil;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kNuDataSourceUnknown:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert(0);
|
||||||
|
return kNuErrInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nu_Free(nil, pDataSource);
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a data source for an unopened file.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSourceFile_New(NuThreadFormat threadFormat, Boolean doClose,
|
||||||
|
ulong otherLen, const char* pathname, Boolean isFromRsrcFork,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if (!(doClose == true || doClose == false) ||
|
||||||
|
pathname == nil ||
|
||||||
|
!(isFromRsrcFork == true || isFromRsrcFork == false) ||
|
||||||
|
ppDataSource == nil)
|
||||||
|
{
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Nu_DataSourceNew(ppDataSource);
|
||||||
|
BailErrorQuiet(err);
|
||||||
|
|
||||||
|
(*ppDataSource)->common.sourceType = kNuDataSourceFromFile;
|
||||||
|
(*ppDataSource)->common.threadFormat = threadFormat;
|
||||||
|
(*ppDataSource)->common.doClose = doClose;
|
||||||
|
(*ppDataSource)->common.otherLen = otherLen;
|
||||||
|
(*ppDataSource)->fromFile.pathname = strdup(pathname);
|
||||||
|
(*ppDataSource)->fromFile.fromRsrcFork = isFromRsrcFork;
|
||||||
|
|
||||||
|
(*ppDataSource)->common.dataLen = 0; /* to be filled in later */
|
||||||
|
(*ppDataSource)->fromFile.fp = nil; /* 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, Boolean doClose,
|
||||||
|
ulong otherLen, FILE* fp, long offset, long length,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if (!(doClose == true || doClose == false) ||
|
||||||
|
fp == nil || offset < 0 || length < 0 ||
|
||||||
|
ppDataSource == nil)
|
||||||
|
{
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherLen && otherLen < (ulong)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.doClose = doClose;
|
||||||
|
(*ppDataSource)->common.dataLen = length;
|
||||||
|
(*ppDataSource)->common.otherLen = otherLen;
|
||||||
|
(*ppDataSource)->fromFP.fp = fp;
|
||||||
|
(*ppDataSource)->fromFP.offset = offset;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a data source for a buffer.
|
||||||
|
*
|
||||||
|
* We allow "buffer" to be nil so long as "offset" and "length" are also
|
||||||
|
* nil. This is useful for creating empty pre-sized buffers, such as
|
||||||
|
* blank comment fields.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, Boolean doClose,
|
||||||
|
ulong otherLen, const uchar* buffer, long offset, long length,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if (!(doClose == true || doClose == false) ||
|
||||||
|
offset < 0 || length < 0 || ppDataSource == nil)
|
||||||
|
{
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
}
|
||||||
|
if (buffer == nil && (offset != 0 || length != 0))
|
||||||
|
{
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
}
|
||||||
|
if (buffer == nil)
|
||||||
|
doClose = false;
|
||||||
|
|
||||||
|
if (otherLen && otherLen < (ulong)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.doClose = doClose;
|
||||||
|
(*ppDataSource)->common.dataLen = length;
|
||||||
|
(*ppDataSource)->common.otherLen = otherLen;
|
||||||
|
(*ppDataSource)->fromBuffer.buffer = buffer;
|
||||||
|
(*ppDataSource)->fromBuffer.offset = offset;
|
||||||
|
|
||||||
|
(*ppDataSource)->fromBuffer.curOffset = offset;
|
||||||
|
(*ppDataSource)->fromBuffer.curDataLen = length;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the type of a NuDataSource.
|
||||||
|
*/
|
||||||
|
NuDataSourceType
|
||||||
|
Nu_DataSourceGetType(const NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
return pDataSource->sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the threadFormat for a data source.
|
||||||
|
*/
|
||||||
|
NuThreadFormat
|
||||||
|
Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
return pDataSource->common.threadFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get "dataLen" from a dataSource.
|
||||||
|
*/
|
||||||
|
ulong
|
||||||
|
Nu_DataSourceGetDataLen(const NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
|
||||||
|
if (pDataSource->sourceType == kNuDataSourceFromFile) {
|
||||||
|
/* dataLen can only be valid if file has been opened */
|
||||||
|
Assert(pDataSource->fromFile.fp != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pDataSource->common.dataLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get "otherLen" from a dataSource.
|
||||||
|
*/
|
||||||
|
ulong
|
||||||
|
Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
return pDataSource->common.otherLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the "otherLen" value.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil && otherLen > 0);
|
||||||
|
pDataSource->common.otherLen = otherLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the "raw CRC" value.
|
||||||
|
*/
|
||||||
|
ushort
|
||||||
|
Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
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, ushort crc)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
pDataSource->common.rawCrc = crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare a data source for action.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSourcePrepareInput(NuArchive* pArchive, NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
FILE* fileFp = nil;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.pathname, pDataSource->fromFile.fromRsrcFork,
|
||||||
|
&fileFp);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
Assert(fileFp != nil);
|
||||||
|
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 != nil) {
|
||||||
|
fclose(pDataSource->fromFile.fp);
|
||||||
|
pDataSource->fromFile.fp = nil;
|
||||||
|
pDataSource->common.dataLen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the pathname from a "from-file" dataSource.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource)
|
||||||
|
{
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
Assert(pDataSource->sourceType == kNuDataSourceFromFile);
|
||||||
|
Assert(pDataSource->fromFile.pathname != nil);
|
||||||
|
|
||||||
|
return pDataSource->fromFile.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a block of data from a dataSource.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSourceGetBlock(NuDataSource* pDataSource, uchar* buf, ulong len)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
Assert(pDataSource != nil);
|
||||||
|
Assert(buf != nil);
|
||||||
|
Assert(len > 0);
|
||||||
|
|
||||||
|
switch (pDataSource->sourceType) {
|
||||||
|
case kNuDataSourceFromFile:
|
||||||
|
Assert(pDataSource->fromFile.fp != nil);
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
switch (pDataSource->sourceType) {
|
||||||
|
case kNuDataSourceFromFile:
|
||||||
|
Assert(pDataSource->fromFile.fp != nil);
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
*ppDataSink = Nu_Malloc(nil, sizeof(**ppDataSink));
|
||||||
|
if (*ppDataSink == nil)
|
||||||
|
return kNuErrMalloc;
|
||||||
|
|
||||||
|
(*ppDataSink)->sinkType = kNuDataSinkUnknown;
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free a data sink structure, and any type-specific elements.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSinkFree(NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
if (pDataSink == nil)
|
||||||
|
return kNuErrNone;
|
||||||
|
|
||||||
|
switch (pDataSink->sinkType) {
|
||||||
|
case kNuDataSinkToFile:
|
||||||
|
Nu_DataSinkFile_Close(pDataSink);
|
||||||
|
Nu_Free(nil, pDataSink->toFile.pathname);
|
||||||
|
break;
|
||||||
|
case kNuDataSinkToFP:
|
||||||
|
break;
|
||||||
|
case kNuDataSinkToBuffer:
|
||||||
|
break;
|
||||||
|
case kNuDataSinkToVoid:
|
||||||
|
break;
|
||||||
|
case kNuDataSinkUnknown:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert(0);
|
||||||
|
return kNuErrInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nu_Free(nil, pDataSink);
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a data sink for an unopened file.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, const char* pathname,
|
||||||
|
char fssep, NuDataSink** ppDataSink)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((doExpand != true && doExpand != false) ||
|
||||||
|
(convertEOL != kNuConvertOff && convertEOL != kNuConvertOn &&
|
||||||
|
convertEOL != kNuConvertAuto) ||
|
||||||
|
pathname == nil ||
|
||||||
|
fssep == 0 ||
|
||||||
|
ppDataSink == nil)
|
||||||
|
{
|
||||||
|
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.pathname = strdup(pathname);
|
||||||
|
(*ppDataSink)->toFile.fssep = fssep;
|
||||||
|
|
||||||
|
(*ppDataSink)->toFile.fp = nil;
|
||||||
|
|
||||||
|
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 == nil ||
|
||||||
|
ppDataSink == nil)
|
||||||
|
{
|
||||||
|
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, uchar* buffer,
|
||||||
|
ulong bufLen, NuDataSink** ppDataSink)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
if ((doExpand != true && doExpand != false) ||
|
||||||
|
(convertEOL != kNuConvertOff && convertEOL != kNuConvertOn &&
|
||||||
|
convertEOL != kNuConvertAuto) ||
|
||||||
|
buffer == nil ||
|
||||||
|
bufLen == 0 ||
|
||||||
|
ppDataSink == nil)
|
||||||
|
{
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
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 != nil);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
ulong
|
||||||
|
Nu_DataSinkGetOutCount(const NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
return pDataSink->common.outCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get "pathname" from a to-file sink.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
Assert(pDataSink->sinkType == kNuDataSinkToFile);
|
||||||
|
|
||||||
|
return pDataSink->toFile.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get "fssep" from a to-file sink.
|
||||||
|
*/
|
||||||
|
char
|
||||||
|
Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
Assert(pDataSink->sinkType == kNuDataSinkToFile);
|
||||||
|
|
||||||
|
return pDataSink->toFile.fssep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the "fp" for a file sink.
|
||||||
|
*/
|
||||||
|
FILE*
|
||||||
|
Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
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 != nil);
|
||||||
|
Assert(pDataSink->sinkType == kNuDataSinkToFile);
|
||||||
|
|
||||||
|
pDataSink->toFile.fp = fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close a to-file sink.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Nu_DataSinkFile_Close(NuDataSink* pDataSink)
|
||||||
|
{
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
|
||||||
|
if (pDataSink->toFile.fp != nil) {
|
||||||
|
fclose(pDataSink->toFile.fp);
|
||||||
|
pDataSink->toFile.fp = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a block of data to a DataSink.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uchar* buf, ulong len)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
Assert(pDataSink != nil);
|
||||||
|
Assert(buf != nil);
|
||||||
|
Assert(len > 0);
|
||||||
|
|
||||||
|
switch (pDataSink->sinkType) {
|
||||||
|
case kNuDataSinkToFile:
|
||||||
|
err = Nu_FWrite(pDataSink->toFile.fp, buf, len);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
return err;
|
||||||
|
break;
|
||||||
|
case kNuDataSinkToFP:
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* This file was adapted from Devin Reade's "sunos4.h" in NuLib 3.2.5.
|
||||||
|
* It is provided for compilation under SunOS 4.x, when an ANSI compiler
|
||||||
|
* (such as gcc) is used. The system header files aren't quite sufficient
|
||||||
|
* to eliminate hordes of warnings.
|
||||||
|
*/
|
||||||
|
#ifndef __SunOS4__
|
||||||
|
#define __SunOS4__
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
extern int _flsbuf(int, FILE*);
|
||||||
|
extern int _filbuf(FILE*);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void bcopy(char*, char*, int);
|
||||||
|
extern int fclose(FILE*);
|
||||||
|
extern int fflush(FILE*);
|
||||||
|
extern int fprintf(FILE*, const char*, ...);
|
||||||
|
extern int fread(char*, int, int, FILE *);
|
||||||
|
extern int fseek(FILE*, long, int);
|
||||||
|
extern int ftruncate(int, off_t);
|
||||||
|
extern int fwrite(const char*, int, int, FILE*);
|
||||||
|
extern char* mktemp(char *template);
|
||||||
|
extern time_t mktime(struct tm*);
|
||||||
|
extern int perror(const char*);
|
||||||
|
extern int printf(const char*, ...);
|
||||||
|
extern int remove(const char*);
|
||||||
|
extern int rename(const char*, const char*);
|
||||||
|
extern int tolower(int);
|
||||||
|
extern int setvbuf(FILE*, char*, int, int);
|
||||||
|
extern int sscanf(char*, const char*, ...);
|
||||||
|
extern int strcasecmp(const char*, const char*);
|
||||||
|
extern int strncasecmp(const char*, const char*, size_t);
|
||||||
|
extern long strtol(const char *, char **, int);
|
||||||
|
extern int system(const char*);
|
||||||
|
extern time_t timelocal(struct tm*);
|
||||||
|
extern time_t time(time_t*);
|
||||||
|
extern int toupper(int);
|
||||||
|
extern int vfprintf(FILE*, const char *, va_list);
|
||||||
|
extern char* vsprintf(char *str, const char *format, va_list ap);
|
||||||
|
|
||||||
|
#endif /*__SunOS4__*/
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* External type definitions and function prototypes.
|
||||||
|
*/
|
||||||
|
#ifndef __SysDefs__
|
||||||
|
#define __SysDefs__
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VERBOSE
|
||||||
|
# define DEBUG_MSGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* these should exist everywhere */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* 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 uchar unsigned char
|
||||||
|
# define ushort unsigned short
|
||||||
|
# define uint unsigned int
|
||||||
|
# define ulong unsigned long
|
||||||
|
# define inline /*Visual C++6.0 can't inline ".c" files*/
|
||||||
|
# define mode_t int
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include <io.h>
|
||||||
|
# include <direct.h>
|
||||||
|
# define FOPEN_WANTS_B
|
||||||
|
# define HAVE_CHSIZE
|
||||||
|
# define snprintf _snprintf
|
||||||
|
# define vsnprintf _vsnprintf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_MALLOC_H
|
||||||
|
# include <malloc.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_STDLIB_H
|
||||||
|
# include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_STAT_H
|
||||||
|
# include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_TIME_H
|
||||||
|
# include <sys/time.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
# include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UTIME_H
|
||||||
|
# include <utime.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_UTIME_H
|
||||||
|
# include <sys/utime.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WINDOWS_LIKE)
|
||||||
|
# ifndef F_OK
|
||||||
|
# define F_OK 02
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__unix__) || defined(__unix) || defined(__BEOS__) || \
|
||||||
|
defined(__hpux) || defined(_AIX) || defined(__APPLE__)
|
||||||
|
# define UNIX_LIKE /* standardize */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(UNIX_LIKE)
|
||||||
|
# ifdef USE_REENTRANT_CALLS
|
||||||
|
# define _REENTRANT /* Solaris 2.x convention */
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MAC
|
||||||
|
# define HAS_RESOURCE_FORKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __ORCAC__
|
||||||
|
# define HAS_RESOURCE_FORKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* __FUNCTION__ was missing from BeOS __MWERKS__, and might be gcc-only */
|
||||||
|
#ifdef __GNUC__
|
||||||
|
# define HAS__FUNCTION__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__sun__) && !defined(__SVR4)
|
||||||
|
# include "SunOS4.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
# define HAS_MALLOC_CHECK_
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /*__SysDefs__*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* Get/set certain values and attributes.
|
||||||
|
*/
|
||||||
|
#include "NufxLibPriv.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a configurable parameter.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_GetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
|
||||||
|
if (pValue == nil)
|
||||||
|
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 kNuValueMimicSHK:
|
||||||
|
*pValue = pArchive->valMimicSHK;
|
||||||
|
break;
|
||||||
|
case kNuValueModifyOrig:
|
||||||
|
*pValue = pArchive->valModifyOrig;
|
||||||
|
break;
|
||||||
|
case kNuValueOnlyUpdateOlder:
|
||||||
|
*pValue = pArchive->valOnlyUpdateOlder;
|
||||||
|
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 %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valAllowDuplicates = value;
|
||||||
|
break;
|
||||||
|
case kNuValueConvertExtractedEOL:
|
||||||
|
if (value < kNuConvertOff || value > kNuConvertAuto) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueConvertExtractedEOL value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valConvertExtractedEOL = value;
|
||||||
|
break;
|
||||||
|
case kNuValueDataCompression:
|
||||||
|
if (value < kNuCompressNone || value > kNuCompressLZC16) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueDataCompression value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valDataCompression = value;
|
||||||
|
break;
|
||||||
|
case kNuValueDiscardWrapper:
|
||||||
|
if (value != true && value != false) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueDiscardWrapper value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valDiscardWrapper = value;
|
||||||
|
break;
|
||||||
|
case kNuValueEOL:
|
||||||
|
if (value < kNuEOLUnknown || value > kNuEOLCRLF) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueEOL value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valEOL = value;
|
||||||
|
break;
|
||||||
|
case kNuValueHandleExisting:
|
||||||
|
if (value < kNuMaybeOverwrite || value > kNuMustOverwrite) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueHandleExisting value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valHandleExisting = value;
|
||||||
|
break;
|
||||||
|
case kNuValueIgnoreCRC:
|
||||||
|
if (value != true && value != false) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueIgnoreCRC value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valIgnoreCRC = value;
|
||||||
|
break;
|
||||||
|
case kNuValueMimicSHK:
|
||||||
|
if (value != true && value != false) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueMimicSHK value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valMimicSHK = value;
|
||||||
|
break;
|
||||||
|
case kNuValueModifyOrig:
|
||||||
|
if (value != true && value != false) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueModifyOrig value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valModifyOrig = value;
|
||||||
|
break;
|
||||||
|
case kNuValueOnlyUpdateOlder:
|
||||||
|
if (value != true && value != false) {
|
||||||
|
Nu_ReportError(NU_BLOB, err,
|
||||||
|
"Invalid kNuValueOnlyUpdateOlder value %ld\n", value);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive->valOnlyUpdateOlder = 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 == nil)
|
||||||
|
return kNuErrInvalidArg;
|
||||||
|
|
||||||
|
switch (ident) {
|
||||||
|
case kNuAttrArchiveType:
|
||||||
|
*pAttr = pArchive->archiveType;
|
||||||
|
break;
|
||||||
|
case kNuAttrNumRecords:
|
||||||
|
*pAttr = pArchive->masterHeader.mhTotalRecords;
|
||||||
|
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;
|
||||||
|
case kNuCompressLZW1: threadFormat = kNuThreadFormatLZW1; break;
|
||||||
|
case kNuCompressLZW2: threadFormat = kNuThreadFormatLZW2; break;
|
||||||
|
case kNuCompressSQ: threadFormat = kNuThreadFormatHuffmanSQ; break;
|
||||||
|
case kNuCompressLZC12: threadFormat = kNuThreadFormatLZC12;
|
||||||
|
unsup = true; break;
|
||||||
|
case kNuCompressLZC16: threadFormat = kNuThreadFormatLZC16;
|
||||||
|
unsup = true; break;
|
||||||
|
default:
|
||||||
|
Assert(false);
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrInvalidArg,
|
||||||
|
"Unknown compress value %ld", compValue);
|
||||||
|
return kNuThreadFormatUncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsup) {
|
||||||
|
Nu_ReportError(NU_BLOB, kNuErrNone,
|
||||||
|
"Unsupported compression type 0x%04x requested (%ld)",
|
||||||
|
threadFormat, compValue);
|
||||||
|
return kNuThreadFormatUncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return threadFormat;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* !!! NOTE !!!
|
||||||
|
* The file Version.c is automatically generated from Version.c.in. Don't
|
||||||
|
* edit the .c file if you want your changes to be permanent.
|
||||||
|
*/
|
||||||
|
#include "NufxLibPriv.h"
|
||||||
|
|
||||||
|
/* version number; edit by hand */
|
||||||
|
static const long gNuMajorVersion = 1;
|
||||||
|
static const long gNuMinorVersion = 0;
|
||||||
|
static const long gNuBugVersion = 0;
|
||||||
|
|
||||||
|
/* executable was build on or after this date (inserted automatically) */
|
||||||
|
static const char gNuBuildDate[] = "BUILT"; /* approximate */
|
||||||
|
static const char gNuBuildFlags[] = "OPTFLAGS";
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the version number, date built, and build flags.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
Nu_GetVersion(long* pMajorVersion, long* pMinorVersion, long* pBugVersion,
|
||||||
|
const char** ppBuildDate, const char** ppBuildFlags)
|
||||||
|
{
|
||||||
|
if (pMajorVersion != nil)
|
||||||
|
*pMajorVersion = gNuMajorVersion;
|
||||||
|
if (pMinorVersion != nil)
|
||||||
|
*pMinorVersion = gNuMinorVersion;
|
||||||
|
if (pBugVersion != nil)
|
||||||
|
*pBugVersion = gNuBugVersion;
|
||||||
|
if (ppBuildDate != nil)
|
||||||
|
*ppBuildDate = gNuBuildDate;
|
||||||
|
if (ppBuildFlags != nil)
|
||||||
|
*ppBuildFlags = gNuBuildFlags;
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,125 @@
|
||||||
|
/* config.h.in. Generated automatically from configure.in by autoheader. */
|
||||||
|
|
||||||
|
/* Define to empty if the keyword does not work. */
|
||||||
|
#undef const
|
||||||
|
|
||||||
|
/* Define to empty if the keyword does not work. */
|
||||||
|
#undef inline
|
||||||
|
|
||||||
|
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef mode_t
|
||||||
|
|
||||||
|
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef off_t
|
||||||
|
|
||||||
|
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||||
|
#undef size_t
|
||||||
|
|
||||||
|
/* Define if you have the ANSI C header files. */
|
||||||
|
#undef STDC_HEADERS
|
||||||
|
|
||||||
|
/* Define if your <sys/time.h> declares struct tm. */
|
||||||
|
#undef TM_IN_SYS_TIME
|
||||||
|
|
||||||
|
/* Define to `unsigned char' if <sys/types.h> doesn't define. */
|
||||||
|
#undef uchar
|
||||||
|
|
||||||
|
/* Define to `unsigned short' if <sys/types.h> doesn't define. */
|
||||||
|
#undef ushort
|
||||||
|
|
||||||
|
/* Define to `unsigned int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef uint
|
||||||
|
|
||||||
|
/* Define to `unsigned long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef ulong
|
||||||
|
|
||||||
|
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef mode_t
|
||||||
|
|
||||||
|
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef off_t
|
||||||
|
|
||||||
|
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||||
|
#undef size_t
|
||||||
|
|
||||||
|
/* Define if you have the fdopen function. */
|
||||||
|
#undef HAVE_FDOPEN
|
||||||
|
|
||||||
|
/* Define if you have the ftruncate function. */
|
||||||
|
#undef HAVE_FTRUNCATE
|
||||||
|
|
||||||
|
/* Define if you have the localtime_r function. */
|
||||||
|
#undef HAVE_LOCALTIME_R
|
||||||
|
|
||||||
|
/* Define if you have the memmove function. */
|
||||||
|
#undef HAVE_MEMMOVE
|
||||||
|
|
||||||
|
/* Define if you have the mkdir function. */
|
||||||
|
#undef HAVE_MKDIR
|
||||||
|
|
||||||
|
/* Define if you have the mkstemp function. */
|
||||||
|
#undef HAVE_MKSTEMP
|
||||||
|
|
||||||
|
/* Define if you have the mktime function. */
|
||||||
|
#undef HAVE_MKTIME
|
||||||
|
|
||||||
|
/* Define if you have the snprintf function. */
|
||||||
|
#undef HAVE_SNPRINTF
|
||||||
|
|
||||||
|
/* Define if you have the strcasecmp function. */
|
||||||
|
#undef HAVE_STRCASECMP
|
||||||
|
|
||||||
|
/* Define if you have the strncasecmp function. */
|
||||||
|
#undef HAVE_STRNCASECMP
|
||||||
|
|
||||||
|
/* Define if you have the strerror function. */
|
||||||
|
#undef HAVE_STRERROR
|
||||||
|
|
||||||
|
/* Define if you have the strtoul function. */
|
||||||
|
#undef HAVE_STRTOUL
|
||||||
|
|
||||||
|
/* Define if you have the timelocal function. */
|
||||||
|
#undef HAVE_TIMELOCAL
|
||||||
|
|
||||||
|
/* Define if you have the vsnprintf function. */
|
||||||
|
#undef HAVE_VSNPRINTF
|
||||||
|
|
||||||
|
/* Define if you have the <fcntl.h> header file. */
|
||||||
|
#undef HAVE_FCNTL_H
|
||||||
|
|
||||||
|
/* Define if you have the <malloc.h> header file. */
|
||||||
|
#undef HAVE_MALLOC_H
|
||||||
|
|
||||||
|
/* Define if you have the <stdlib.h> header file. */
|
||||||
|
#undef HAVE_STDLIB_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/time.h> header file. */
|
||||||
|
#undef HAVE_SYS_STAT_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/time.h> header file. */
|
||||||
|
#undef HAVE_SYS_TIME_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/types.h> header file. */
|
||||||
|
#undef HAVE_SYS_TYPES_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/utime.h> header file. */
|
||||||
|
#undef HAVE_SYS_UTIME_H
|
||||||
|
|
||||||
|
/* Define if you have the <unistd.h> header file. */
|
||||||
|
#undef HAVE_UNISTD_H
|
||||||
|
|
||||||
|
/* Define if you have the <utime.h> header file. */
|
||||||
|
#undef HAVE_UTIME_H
|
||||||
|
|
||||||
|
/* Define if sprintf returns an int. */
|
||||||
|
#undef SPRINTF_RETURNS_INT
|
||||||
|
|
||||||
|
/* Define if SNPRINTF is declared in stdio.h. */
|
||||||
|
#undef SNPRINTF_DECLARED
|
||||||
|
|
||||||
|
/* Define if VSNPRINTF is declared in stdio.h. */
|
||||||
|
#undef VSNPRINTF_DECLARED
|
||||||
|
|
||||||
|
/* Define if we want to use the dmalloc library (--enable-dmalloc). */
|
||||||
|
#undef USE_DMALLOC
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,136 @@
|
||||||
|
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 libraries.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
AC_CHECK_TYPE(uchar, unsigned char)
|
||||||
|
AC_CHECK_TYPE(ushort, unsigned short)
|
||||||
|
AC_CHECK_TYPE(uint, unsigned int)
|
||||||
|
AC_CHECK_TYPE(ulong, unsigned long)
|
||||||
|
|
||||||
|
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 Figure out what the build and link flags should be
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
AC_SUBST(BUILD_FLAGS)
|
||||||
|
AC_SUBST(SHARE_FLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
dnl BeOS doesn't like /usr/local/include, and gets feisty about it. If libdir
|
||||||
|
dnl and includedir are set to defaults, replace them with BeOS values. This
|
||||||
|
dnl might be going a little too far...
|
||||||
|
if test "$host_os" = "beos"; then
|
||||||
|
if test "$prefix" = "NONE" -a \
|
||||||
|
"$includedir" = '${prefix}/include' -a \
|
||||||
|
"$libdir" = '${exec_prefix}/lib' -a \
|
||||||
|
"$bindir" = '${exec_prefix}/bin' -a \
|
||||||
|
"$mandir" = '${prefix}/man'
|
||||||
|
then
|
||||||
|
echo replacing install locations with BeOS values
|
||||||
|
prefix=/boot
|
||||||
|
includedir='${prefix}/develop/headers'
|
||||||
|
libdir='${exec_prefix}/home/config/lib'
|
||||||
|
bindir='${exec_prefix}/home/config/bin'
|
||||||
|
mandir='/tmp'
|
||||||
|
AC_SUBST(prefix)
|
||||||
|
AC_SUBST(includedir)
|
||||||
|
AC_SUBST(libdir)
|
||||||
|
AC_SUBST(bindir)
|
||||||
|
AC_SUBST(mandir)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
dnl Test to see if sprintf does something reasonable. I'm going to assume
|
||||||
|
dnl that vsprintf and (if available) vsnprintf return the same thing.
|
||||||
|
AC_MSG_CHECKING(if sprintf returns int)
|
||||||
|
AC_CACHE_VAL(nufxlib_cv_sprintf_returns_int, [
|
||||||
|
AC_TRY_RUN([
|
||||||
|
#include <stdio.h>
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
char buf[8];
|
||||||
|
count = sprintf(buf, "123"); /* should return three */
|
||||||
|
exit(count != 3);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[nufxlib_cv_sprintf_returns_int=yes], [nufxlib_cv_sprintf_returns_int=no],
|
||||||
|
[nufxlib_cv_sprintf_returns_int=no])
|
||||||
|
])
|
||||||
|
if test $nufxlib_cv_sprintf_returns_int = "yes"; then
|
||||||
|
AC_DEFINE(SPRINTF_RETURNS_INT)
|
||||||
|
fi
|
||||||
|
AC_MSG_RESULT($nufxlib_cv_sprintf_returns_int)
|
||||||
|
|
||||||
|
DMALLOC=
|
||||||
|
AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc: do dmalloc stuff], \
|
||||||
|
[ echo "--- enabling dmalloc"; \
|
||||||
|
DMALLOC="-L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ])
|
||||||
|
AC_SUBST(DMALLOC)
|
||||||
|
|
||||||
|
AC_OUTPUT(Makefile samples/Makefile)
|
|
@ -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
|
|
@ -0,0 +1,34 @@
|
||||||
|
#! /bin/sh
|
||||||
|
# mkinstalldirs --- make directory hierarchy
|
||||||
|
# Author: Noah Friedman <friedman@prep.ai.mit.edu>
|
||||||
|
# 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
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* Common functions for NuLib tests
|
||||||
|
*/
|
||||||
|
#ifndef __Common__
|
||||||
|
#define __Common__
|
||||||
|
|
||||||
|
#include "SysDefs.h" /* might as well draft off the autoconf */
|
||||||
|
#include "NufxLib.h"
|
||||||
|
|
||||||
|
#ifdef USE_DMALLOC
|
||||||
|
# include "dmalloc.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define nil NULL /* this is seriously habit-forming */
|
||||||
|
|
||||||
|
#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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 /*__Common__*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,624 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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"
|
||||||
|
|
||||||
|
/* we can get this out of NufxLib if the OS doesn't have it */
|
||||||
|
#ifndef HAVE_STRCASECMP
|
||||||
|
#define strcasecmp Nu_strcasecmp
|
||||||
|
int Nu_strcasecmp(const char *s1, const char *s2);
|
||||||
|
#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];
|
||||||
|
short headerLen;
|
||||||
|
short version;
|
||||||
|
long imageFormat;
|
||||||
|
unsigned long flags;
|
||||||
|
long numBlocks;
|
||||||
|
long dataOffset;
|
||||||
|
long dataLen;
|
||||||
|
long cmntOffset;
|
||||||
|
long cmntLen;
|
||||||
|
long creatorOffset;
|
||||||
|
long creatorLen;
|
||||||
|
long spare[4];
|
||||||
|
} ImgHeader;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a two-byte little-endian value.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ReadShortLE(FILE* fp, short* pBuf)
|
||||||
|
{
|
||||||
|
*pBuf = getc(fp);
|
||||||
|
*pBuf += (short) getc(fp) << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a two-byte little-endian value.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
WriteShortLE(FILE* fp, unsigned short val)
|
||||||
|
{
|
||||||
|
putc(val, fp);
|
||||||
|
putc(val >> 8, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a four-byte little-endian value.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ReadLongLE(FILE* fp, long* pBuf)
|
||||||
|
{
|
||||||
|
*pBuf = getc(fp);
|
||||||
|
*pBuf += (long) getc(fp) << 8;
|
||||||
|
*pBuf += (long) getc(fp) << 16;
|
||||||
|
*pBuf += (long) getc(fp) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a four-byte little-endian value.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
WriteLongLE(FILE* fp, unsigned long 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)
|
||||||
|
{
|
||||||
|
fread(pHeader->magic, 4, 1, fp);
|
||||||
|
fread(pHeader->creator, 4, 1, fp);
|
||||||
|
ReadShortLE(fp, &pHeader->headerLen);
|
||||||
|
ReadShortLE(fp, &pHeader->version);
|
||||||
|
ReadLongLE(fp, &pHeader->imageFormat);
|
||||||
|
ReadLongLE(fp, (long*)&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]);
|
||||||
|
|
||||||
|
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 = %ld\n", pHeader->imageFormat);
|
||||||
|
printf("\tflags = 0x%08lx\n", pHeader->flags);
|
||||||
|
printf("\tnumBlocks = %ld\n", pHeader->numBlocks);
|
||||||
|
printf("\tdataOffset = %ld\n", pHeader->dataOffset);
|
||||||
|
printf("\tdataLen = %ld\n", pHeader->dataLen);
|
||||||
|
printf("\tcmntOffset = %ld\n", pHeader->cmntOffset);
|
||||||
|
printf("\tcmntLen = %ld\n", pHeader->cmntLen);
|
||||||
|
printf("\tcreatorOffset = %ld\n", pHeader->creatorOffset);
|
||||||
|
printf("\tcreatorLen = %ld\n", pHeader->creatorLen);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Main functions
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum ArchiveKind { kKindUnknown, kKindShk, kKindImg } ArchiveKind;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
CreateProdosSource(const ImgHeader* pHeader, FILE* fp,
|
||||||
|
NuDataSource** ppDataSource)
|
||||||
|
{
|
||||||
|
return NuCreateDataSourceForFP(kNuThreadFormatUncompressed, false, 0, fp,
|
||||||
|
pHeader->dataOffset, pHeader->dataLen, 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 = nil;
|
||||||
|
long offset;
|
||||||
|
|
||||||
|
if (pHeader->dataLen % 4096) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ERROR: image size must be multiple of 4096 (%ld 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 == nil) {
|
||||||
|
fprintf(stderr, "ERROR: malloc(%ld) 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 < pHeader->dataLen; offset += 4096) {
|
||||||
|
fread(diskBuffer + offset + 0x0000, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0e00, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0d00, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0c00, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0b00, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0a00, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0900, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0800, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0700, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0600, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0500, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0400, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0300, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0200, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0100, 256, 1, fp);
|
||||||
|
fread(diskBuffer + offset + 0x0f00, 256, 1, fp);
|
||||||
|
}
|
||||||
|
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, true, 0,
|
||||||
|
(const unsigned char*) diskBuffer, 0, pHeader->dataLen,
|
||||||
|
ppDataSource);
|
||||||
|
if (err == kNuErrNone)
|
||||||
|
diskBuffer = nil;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (diskBuffer != nil)
|
||||||
|
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 = nil;
|
||||||
|
NuDataSource* pDataSource = nil;
|
||||||
|
NuRecordIdx recordIdx;
|
||||||
|
NuFileDetails fileDetails;
|
||||||
|
ImgHeader header;
|
||||||
|
FILE* fp = nil;
|
||||||
|
long flushStatus;
|
||||||
|
char* storageName = nil;
|
||||||
|
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 != nil)
|
||||||
|
*cp = '\0';
|
||||||
|
cp = strrchr(storageName, kLocalFssep);
|
||||||
|
if (cp != nil && *(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.storageName = 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);
|
||||||
|
break;
|
||||||
|
case kImageFormatProDOS:
|
||||||
|
err = CreateProdosSource(&header, fp, &pDataSource);
|
||||||
|
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,
|
||||||
|
nil);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: unable to create thread (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pDataSource = nil; /* 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%04lx)\n",
|
||||||
|
err, flushStatus);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
err = NuClose(pArchive);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: close failed (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pArchive = nil;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil) {
|
||||||
|
(void)NuAbort(pArchive);
|
||||||
|
(void)NuClose(pArchive);
|
||||||
|
}
|
||||||
|
NuFreeDataSource(pDataSource);
|
||||||
|
if (storageName != nil)
|
||||||
|
free(storageName);
|
||||||
|
if (fp != nil)
|
||||||
|
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 = nil;
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
NuRecordIdx recordIdx;
|
||||||
|
const NuRecord* pRecord;
|
||||||
|
const NuThread* pThread = nil;
|
||||||
|
ImgHeader header;
|
||||||
|
FILE* fp = nil;
|
||||||
|
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 != nil)
|
||||||
|
NuClose(pArchive);
|
||||||
|
NuFreeDataSink(pDataSink);
|
||||||
|
if (fp != nil)
|
||||||
|
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 == nil)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,587 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 have 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#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;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 = nil;
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
uchar* buffer = nil;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 == nil) {
|
||||||
|
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 %ld (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 nil 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, true,
|
||||||
|
pThread->thCompThreadEOF, buffer, 0,
|
||||||
|
pThread->actualThreadEOF, &pDataSource);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ERROR: unable to create pre-sized data source (err=%d)\n",err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true,
|
||||||
|
0, buffer, 0,
|
||||||
|
pThread->actualThreadEOF, &pDataSource);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ERROR: unable to create data source (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer = nil; /* 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, nil);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pDataSource = nil; /* library owns it now */
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pDataSource != nil)
|
||||||
|
NuFreeDataSource(pDataSource);
|
||||||
|
if (pDataSink != nil)
|
||||||
|
NuFreeDataSink(pDataSink);
|
||||||
|
if (buffer != nil)
|
||||||
|
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 = nil;
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
uchar* buffer = nil;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.)
|
||||||
|
*/
|
||||||
|
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 == nil) {
|
||||||
|
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 %ld (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, true,
|
||||||
|
pThread->thCompThreadEOF, buffer, 0,
|
||||||
|
pThread->actualThreadEOF, &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, true,
|
||||||
|
pThread->actualThreadEOF, buffer, 0,
|
||||||
|
pThread->thCompThreadEOF, &pDataSource);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ERROR: unable to create data source (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer = nil; /* 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, nil);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
pDataSource = nil; /* library owns it now */
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pDataSource != nil)
|
||||||
|
NuFreeDataSource(pDataSource);
|
||||||
|
if (pDataSink != nil)
|
||||||
|
NuFreeDataSink(pDataSink);
|
||||||
|
if (buffer != nil)
|
||||||
|
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 %ld\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=%ld was empty\n", recordIdx);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new record that looks just like the original.
|
||||||
|
*/
|
||||||
|
memset(&fileDetails, 0, sizeof(fileDetails));
|
||||||
|
fileDetails.storageName = pRecord->filename;
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
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 != nil);
|
||||||
|
|
||||||
|
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, long flags)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
NuArchive* pInArchive = nil;
|
||||||
|
NuArchive* pOutArchive = nil;
|
||||||
|
const NuMasterHeader* pMasterHeader;
|
||||||
|
NuRecordIdx recordIdx;
|
||||||
|
long 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 < (int)pMasterHeader->mhTotalRecords; idx++) {
|
||||||
|
err = NuGetRecordIdxByPosition(pInArchive, idx, &recordIdx);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: couldn't get record #%ld (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%04lx)\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%04lx)\n",
|
||||||
|
err, flushStatus);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pInArchive != nil)
|
||||||
|
NuClose(pInArchive);
|
||||||
|
if (pOutArchive != nil) {
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
NuAbort(pOutArchive);
|
||||||
|
NuClose(pOutArchive); /* flush pending changes and close */
|
||||||
|
}
|
||||||
|
return (err != kNuErrNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print usage info.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Usage(const char* argv0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [-crfat] infile.shk outfile.shk\n", argv0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grab the name of an archive to read.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
long major, minor, bug;
|
||||||
|
const char* pBuildDate;
|
||||||
|
long flags = 0;
|
||||||
|
char* cp = nil;
|
||||||
|
int cc;
|
||||||
|
|
||||||
|
(void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil);
|
||||||
|
printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n",
|
||||||
|
major, minor, bug, pBuildDate);
|
||||||
|
|
||||||
|
if (argc < 3 || argc > 4) {
|
||||||
|
Usage(argv[0]);
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 4) {
|
||||||
|
cp = argv[1];
|
||||||
|
if (*cp++ != '-') {
|
||||||
|
Usage(argv[0]);
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*cp != '\0') {
|
||||||
|
switch (*cp) {
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
Usage(argv[0]);
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
|
||||||
|
cc = LaunderArchive(argv[1], argv[2], flags);
|
||||||
|
|
||||||
|
if (cc == 0)
|
||||||
|
printf("Success!\n");
|
||||||
|
else
|
||||||
|
printf("Failed.\n");
|
||||||
|
exit(cc != 0);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
#
|
||||||
|
# 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 Launder.c ImgConv.c TestBasic.c TestExtract.c \
|
||||||
|
TestSimple.c
|
||||||
|
|
||||||
|
NUFXLIB = -L.. -lnufx @DMALLOC@
|
||||||
|
|
||||||
|
PRODUCTS = exerciser imgconv launder test-basic test-extract test-simple
|
||||||
|
|
||||||
|
#ifdef PURIFY_BUILD
|
||||||
|
# PURIFY = purify
|
||||||
|
# CFLAGS += -DPURIFY
|
||||||
|
#endif
|
||||||
|
#ifdef QUANTIFY_BUILD
|
||||||
|
# QUANTIFY = quantify
|
||||||
|
# CFLAGS += -DQUANTIFY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
all: $(PRODUCTS)
|
||||||
|
@true
|
||||||
|
|
||||||
|
#quantify:
|
||||||
|
# -rm -f $(PRODUCT)
|
||||||
|
# @$(MAKE) QUANTIFY_BUILD=1
|
||||||
|
#
|
||||||
|
#purify:
|
||||||
|
# -rm -f $(PRODUCT)
|
||||||
|
# @$(MAKE) PURIFY_BUILD=1
|
||||||
|
|
||||||
|
exerciser: Exerciser.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ Exerciser.o $(NUFXLIB)
|
||||||
|
|
||||||
|
imgconv: ImgConv.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ ImgConv.o $(NUFXLIB)
|
||||||
|
|
||||||
|
launder: Launder.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ Launder.o $(NUFXLIB)
|
||||||
|
|
||||||
|
test-basic: TestBasic.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ TestBasic.o $(NUFXLIB)
|
||||||
|
|
||||||
|
test-simple: TestSimple.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ TestSimple.o $(NUFXLIB)
|
||||||
|
|
||||||
|
test-extract: TestExtract.o $(LIB_PRODUCT)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ TestExtract.o $(NUFXLIB)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
depend:
|
||||||
|
makedepend -- $(CFLAGS) -I/usr/local/include -- $(ALL_SRCS)
|
||||||
|
|
||||||
|
# DO NOT DELETE THIS LINE -- make depend depends on it.
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
#
|
||||||
|
# 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".
|
||||||
|
#
|
||||||
|
|
||||||
|
# Windows magic
|
||||||
|
TARGETOS = BOTH
|
||||||
|
!include <ntwin32.mak>
|
||||||
|
|
||||||
|
NUFXSRCDIR = ..
|
||||||
|
LIB_PRODUCT = $(NUFXSRCDIR)\nufxlib.lib
|
||||||
|
|
||||||
|
!ifdef NODEBUG
|
||||||
|
#OPT = /D NDEBUG /ML /Ogityb2
|
||||||
|
OPT = /D DEBUG_MSGS /ML /Ogityb2
|
||||||
|
LIB_FLAGS = /nodefaultlib:libcd.lib /nologo libc.lib setargv.obj
|
||||||
|
!else
|
||||||
|
OPT = /D DEBUG_MSGS /MLd /Od
|
||||||
|
LIB_FLAGS = /nodefaultlib:libc.lib /nologo libcd.lib setargv.obj
|
||||||
|
!endif
|
||||||
|
|
||||||
|
BUILD_FLAGS = /W3 /GX /D "WIN32" /D "_CONSOLE" /I "$(NUFXSRCDIR)"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
all: $(PRODUCTS)
|
||||||
|
|
||||||
|
exerciser.exe: Exerciser.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) Exerciser.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
imgconv.exe: ImgConv.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) ImgConv.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
launder.exe: Launder.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) Launder.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
test-basic.exe: TestBasic.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) TestBasic.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
test-simple.exe: TestSimple.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) TestSimple.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
test-extract.exe: TestExtract.obj $(LIB_PRODUCT)
|
||||||
|
$(link) $(ldebug) TestExtract.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
del *.obj
|
||||||
|
del exerciser.exe
|
||||||
|
del imgconv.exe
|
||||||
|
del launder.exe
|
||||||
|
del test-basic.exe
|
||||||
|
del test-simple.exe
|
||||||
|
del test-extract.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
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
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] infile.shk outfile.shk
|
||||||
|
|
||||||
|
The flags are:
|
||||||
|
|
||||||
|
-c Just copy 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
|
||||||
|
|
||||||
|
If you use the "-c" flag with an archive created by P8 ShrinkIt or NuLib,
|
||||||
|
the laundered archive may have CRC failures. 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.
|
||||||
|
|
||||||
|
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-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.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,494 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "NufxLib.h"
|
||||||
|
#include "Common.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define false 0
|
||||||
|
#define true (!false)
|
||||||
|
|
||||||
|
#define kHappySize 2408
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* ArchiveRecord
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Track an archive record.
|
||||||
|
*/
|
||||||
|
typedef struct ArchiveRecord {
|
||||||
|
char* filename;
|
||||||
|
NuRecordIdx recordIdx;
|
||||||
|
|
||||||
|
long numThreads;
|
||||||
|
NuThread* pThreads;
|
||||||
|
|
||||||
|
struct ArchiveRecord* pNext;
|
||||||
|
} ArchiveRecord;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alloc a new ArchiveRecord.
|
||||||
|
*/
|
||||||
|
ArchiveRecord*
|
||||||
|
ArchiveRecord_New(const NuRecord* pRecord)
|
||||||
|
{
|
||||||
|
ArchiveRecord* pArcRec = nil;
|
||||||
|
|
||||||
|
pArcRec = malloc(sizeof(*pArcRec));
|
||||||
|
if (pArcRec == nil)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
if (pRecord->filename == nil)
|
||||||
|
pArcRec->filename = strdup("<unknown>");
|
||||||
|
else
|
||||||
|
pArcRec->filename = strdup((char*)pRecord->filename);
|
||||||
|
|
||||||
|
pArcRec->recordIdx = pRecord->recordIdx;
|
||||||
|
pArcRec->numThreads = NuRecordGetNumThreads(pRecord);
|
||||||
|
(void) NuRecordCopyThreads(pRecord, &pArcRec->pThreads);
|
||||||
|
|
||||||
|
pArcRec->pNext = nil;
|
||||||
|
|
||||||
|
return pArcRec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free up an ArchiveRecord.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ArchiveRecord_Free(ArchiveRecord* pArcRec)
|
||||||
|
{
|
||||||
|
if (pArcRec == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pArcRec->filename != nil)
|
||||||
|
free(pArcRec->filename);
|
||||||
|
if (pArcRec->pThreads != nil)
|
||||||
|
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 nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char*
|
||||||
|
ArchiveRecord_GetFilename(const ArchiveRecord* pArcRec)
|
||||||
|
{
|
||||||
|
return pArcRec->filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 nil;
|
||||||
|
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 == nil)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
pArcData->numRecords = 0;
|
||||||
|
pArcData->pRecordHead = pArcData->pRecordTail = nil;
|
||||||
|
|
||||||
|
return pArcData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArchiveData_Free(ArchiveData* pArcData)
|
||||||
|
{
|
||||||
|
ArchiveRecord* pNext;
|
||||||
|
|
||||||
|
if (pArcData == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
printf("*** Deleting %ld records!\n", pArcData->numRecords);
|
||||||
|
while (pArcData->pRecordHead != nil) {
|
||||||
|
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 != nil);
|
||||||
|
assert((pArcData->pRecordHead == nil && pArcData->pRecordTail == nil) ||
|
||||||
|
(pArcData->pRecordHead != nil && pArcData->pRecordTail != nil));
|
||||||
|
|
||||||
|
if (pArcData->pRecordHead == nil) {
|
||||||
|
/* 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 != nil) {
|
||||||
|
const NuThread* pThread;
|
||||||
|
int i, count;
|
||||||
|
|
||||||
|
printf("%5ld '%s'\n",
|
||||||
|
ArchiveRecord_GetRecordIdx(pArcRec),
|
||||||
|
ArchiveRecord_GetFilename(pArcRec));
|
||||||
|
|
||||||
|
count = ArchiveRecord_GetNumThreads(pArcRec);
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
pThread = ArchiveRecord_GetThread(pArcRec, i);
|
||||||
|
printf(" %5ld 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 = nil;
|
||||||
|
ArchiveRecord* pArchiveRecord = ArchiveRecord_New(pRecord);
|
||||||
|
|
||||||
|
NuGetExtraData(pArchive, (void**)&pArchiveData);
|
||||||
|
assert(pArchiveData != nil);
|
||||||
|
|
||||||
|
printf("*** Filename = '%s'\n",
|
||||||
|
pRecord->filename == nil ? "<unknown>":(const char*)pRecord->filename);
|
||||||
|
|
||||||
|
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 != nil) {
|
||||||
|
pThread = ArchiveRecord_FindThreadByID(pArchiveRecord,
|
||||||
|
kNuThreadIDFilename);
|
||||||
|
if (pThread != nil) {
|
||||||
|
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 = nil;
|
||||||
|
|
||||||
|
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 = nil;
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
|
||||||
|
if ((fp = fopen("out.fp", kNuFileOpenWriteTrunc)) == nil)
|
||||||
|
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 != nil)
|
||||||
|
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;
|
||||||
|
unsigned char buffer[kHappySize];
|
||||||
|
NuDataSink* pDataSink = nil;
|
||||||
|
unsigned long 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)) != nil) {
|
||||||
|
|
||||||
|
printf("*** Writing %ld 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 char* filename)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
NuMasterHeader* pMasterHeader = nil;
|
||||||
|
ArchiveData* pArchiveData = ArchiveData_New();
|
||||||
|
|
||||||
|
err = NuOpenRO(filename, &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 != nil) {
|
||||||
|
NuError err2 = NuClose(pArchive);
|
||||||
|
if (err == kNuErrNone && err2 != kNuErrNone)
|
||||||
|
err = err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pMasterHeader != nil)
|
||||||
|
free(pMasterHeader);
|
||||||
|
|
||||||
|
if (pArchiveData != nil)
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
long major, minor, bug;
|
||||||
|
const char* pBuildDate;
|
||||||
|
FILE* infp;
|
||||||
|
int cc;
|
||||||
|
|
||||||
|
(void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil);
|
||||||
|
printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n",
|
||||||
|
major, minor, bug, pBuildDate);
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
infp = fopen(argv[1], kNuFileOpenReadOnly);
|
||||||
|
if (infp == nil) {
|
||||||
|
perror("fopen failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "ERROR: you have to specify a filename\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
cc = DoFileStuff(argv[1]);
|
||||||
|
|
||||||
|
exit(cc != 0);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 <stdio.h>
|
||||||
|
#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;
|
||||||
|
|
||||||
|
printf("*** Filename = '%s'\n", pRecord->filename);
|
||||||
|
|
||||||
|
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 = nil;
|
||||||
|
|
||||||
|
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 != nil) {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
long major, minor, bug;
|
||||||
|
const char* pBuildDate;
|
||||||
|
FILE* infp = nil;
|
||||||
|
int cc;
|
||||||
|
|
||||||
|
(void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil);
|
||||||
|
printf("Using NuFX lib %ld.%ld.%ld 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 == nil) {
|
||||||
|
fprintf(stderr, "ERROR: unable to open '%s'\n", argv[1]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cc = DoStreamStuff(infp);
|
||||||
|
exit(cc != 0);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Add files to or update files in the archive.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
static NuError AddToArchive(NulibState* pState, NuArchive* pArchive);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the specified files to a new or existing archive.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoAdd(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
long flushStatus;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadWrite(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
NState_SetMatchCount(pState, 0);
|
||||||
|
|
||||||
|
/* tell them about the list of files */
|
||||||
|
err = AddToArchive(pState, pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
/*(void)NuDebugDumpArchive(pArchive);*/
|
||||||
|
|
||||||
|
if (!NState_GetMatchCount(pState))
|
||||||
|
printf("%s: no records matched\n", gProgName);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil) {
|
||||||
|
NuError err2;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
printf("Attempting to flush changes in spite of errors...\n");
|
||||||
|
err = kNuErrNone;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (err == kNuErrNone) {
|
||||||
|
err = NuFlush(pArchive, &flushStatus);
|
||||||
|
if (err != kNuErrNone && err == kNuErrNone) {
|
||||||
|
ReportError(err,
|
||||||
|
"Unable to flush archive changes (status=0x%04lx)",
|
||||||
|
flushStatus);
|
||||||
|
NuAbort(pArchive);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NuAbort(pArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
err2 = NuClose(pArchive);
|
||||||
|
assert(err2 == kNuErrNone);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the requested files to the specified archive.
|
||||||
|
*
|
||||||
|
* This just results in NuAddFile calls; the deferred write operation
|
||||||
|
* isn't initiated.
|
||||||
|
*/
|
||||||
|
static NuError
|
||||||
|
AddToArchive(NulibState* pState, NuArchive* pArchive)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
char* const* pSpec;
|
||||||
|
ulong fileCount;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
if (!NState_GetFilespecCount(pState)) {
|
||||||
|
err = kNuErrSyntax;
|
||||||
|
ReportError(err, "no files were specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCount = 0;
|
||||||
|
|
||||||
|
pSpec = NState_GetFilespecPointer(pState);
|
||||||
|
for (i = NState_GetFilespecCount(pState); i > 0; i--, pSpec++) {
|
||||||
|
err = AddFile(pState, pArchive, *pSpec);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,935 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Common archive-related utility functions.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Output pathnames
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General-purpose output pathname filter, invoked by nufxlib via a
|
||||||
|
* callback. Normalizes the pathname to match the current OS requirements.
|
||||||
|
*
|
||||||
|
* If we're extracting to stdout, this fills in the "newFp" field instead.
|
||||||
|
*
|
||||||
|
* The buffer we return to the archive library will be overwritten the
|
||||||
|
* next time this function gets called. This is expected.
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
OutputPathnameFilter(NuArchive* pArchive, void* vproposal)
|
||||||
|
{
|
||||||
|
NuPathnameProposal* pathProposal = vproposal;
|
||||||
|
NulibState* pState;
|
||||||
|
const char* newPathname;
|
||||||
|
NuRecordIdx renameFromIdx;
|
||||||
|
char* renameToStr;
|
||||||
|
char* resultBuf;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
/* handle extract-to-pipe */
|
||||||
|
if (NState_GetCommand(pState) == kCommandExtractToPipe) {
|
||||||
|
pathProposal->newDataSink = NState_GetPipeSink(pState);
|
||||||
|
NState_SetSuppressOutput(pState, true);
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If they're trying to rename this file, do so now. We don't run
|
||||||
|
* the output through the normalizer; if the user typed it, assume
|
||||||
|
* that it's okay (and let the OS tell them if it isn't).
|
||||||
|
*/
|
||||||
|
renameToStr = NState_GetRenameToStr(pState);
|
||||||
|
if (renameToStr != nil) {
|
||||||
|
renameFromIdx = NState_GetRenameFromIdx(pState);
|
||||||
|
|
||||||
|
if (renameFromIdx == pathProposal->pRecord->recordIdx) {
|
||||||
|
/* right source file, proceed with the rename */
|
||||||
|
NState_SetTempPathnameLen(pState, strlen(renameToStr) +1);
|
||||||
|
resultBuf = NState_GetTempPathnameBuf(pState);
|
||||||
|
assert(resultBuf != nil);
|
||||||
|
strcpy(resultBuf, renameToStr);
|
||||||
|
|
||||||
|
pathProposal->newPathname = resultBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* free up renameToStr */
|
||||||
|
NState_SetRenameToStr(pState, nil);
|
||||||
|
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the pathname into something suitable for the current OS.
|
||||||
|
*/
|
||||||
|
newPathname = NormalizePath(pState, pathProposal);
|
||||||
|
if (newPathname == nil) {
|
||||||
|
ReportError(kNuErrNone, "unable to convert pathname");
|
||||||
|
return kNuAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathProposal->newPathname = newPathname;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* User input
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a single character from the input.
|
||||||
|
*
|
||||||
|
* For portability, I'm just getting a line of input and keeping the
|
||||||
|
* first character. A fancier version would play with line disciplines
|
||||||
|
* so you wouldn't have to hit "return".
|
||||||
|
*/
|
||||||
|
static char
|
||||||
|
GetReplyChar(char defaultReply)
|
||||||
|
{
|
||||||
|
char tmpBuf[32];
|
||||||
|
|
||||||
|
if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == nil)
|
||||||
|
return defaultReply;
|
||||||
|
if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r')
|
||||||
|
return defaultReply;
|
||||||
|
|
||||||
|
return tmpBuf[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define kMaxInputLen 128
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a string back from the user.
|
||||||
|
*
|
||||||
|
* String returned should be freed by the caller.
|
||||||
|
*/
|
||||||
|
static char*
|
||||||
|
GetReplyString(const char* prompt)
|
||||||
|
{
|
||||||
|
char buf[kMaxInputLen];
|
||||||
|
char* result;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
printf("%s", prompt);
|
||||||
|
fflush(stdout);
|
||||||
|
result = fgets(buf, sizeof(buf), stdin);
|
||||||
|
if (result == nil || feof(stdin) || ferror(stdin) || buf[0] == '\0' ||
|
||||||
|
buf[0] == '\n')
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nuke the terminating '\n', which is lots of fun in filenames */
|
||||||
|
len = strlen(buf);
|
||||||
|
if (buf[len-1] == '\n')
|
||||||
|
buf[len-1] = '\0';
|
||||||
|
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a one-line comment from the user, of at most "maxLen" bytes.
|
||||||
|
*
|
||||||
|
* If the user enters a blank line, return "nil".
|
||||||
|
*
|
||||||
|
* A pointer to a newly-allocated buffer is returned.
|
||||||
|
*/
|
||||||
|
char*
|
||||||
|
GetSimpleComment(NulibState* pState, const char* pathname, int maxLen)
|
||||||
|
{
|
||||||
|
char* buf = nil;
|
||||||
|
char* result;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
buf = Malloc(maxLen);
|
||||||
|
if (buf == nil)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
printf("Enter one-line comment for '%s'\n: ", pathname);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
result = fgets(buf, maxLen, stdin);
|
||||||
|
if (result == nil || feof(stdin) || ferror(stdin) || buf[0] == '\0' ||
|
||||||
|
buf[0] == '\n')
|
||||||
|
{
|
||||||
|
Free(buf);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nuke the terminating '\n', which we don't need */
|
||||||
|
len = strlen(buf);
|
||||||
|
if (buf[len-1] == '\n')
|
||||||
|
buf[len-1] = '\0';
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Callbacks (progress updates, error handling, record selection)
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define kMaxDisplayLen 60
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns "true" if the filespec in "spec" matches what's in "pRecord".
|
||||||
|
*
|
||||||
|
* (Someday "spec" might be a regexp.)
|
||||||
|
*/
|
||||||
|
static Boolean
|
||||||
|
SpecMatchesRecord(NulibState* pState, const char* spec, const NuRecord* pRecord)
|
||||||
|
{
|
||||||
|
if (NState_GetModRecurse(pState))
|
||||||
|
return (strncmp(spec, pRecord->filename, strlen(spec)) == 0);
|
||||||
|
else
|
||||||
|
return (strcmp(spec, pRecord->filename) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine whether the current record we're examining is described by
|
||||||
|
* the file specification given on the command line.
|
||||||
|
*
|
||||||
|
* If no filespec was provided, then all records are "specified".
|
||||||
|
*/
|
||||||
|
Boolean
|
||||||
|
IsSpecified(NulibState* pState, const NuRecord* pRecord)
|
||||||
|
{
|
||||||
|
char* const* pSpec;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!NState_GetFilespecCount(pState))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
pSpec = NState_GetFilespecPointer(pState);
|
||||||
|
for (i = NState_GetFilespecCount(pState); i > 0; i--, pSpec++) {
|
||||||
|
if (SpecMatchesRecord(pState, *pSpec, pRecord))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General-purpose selection filter, invoked as a callback. Compares the
|
||||||
|
* selection proposal with the filenames in "filespec".
|
||||||
|
*/
|
||||||
|
NuResult
|
||||||
|
SelectionFilter(NuArchive* pArchive, void* vproposal)
|
||||||
|
{
|
||||||
|
const NuSelectionProposal* selProposal = vproposal;
|
||||||
|
NulibState* pState;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
if (IsSpecified(pState, selProposal->pRecord)) {
|
||||||
|
NState_IncMatchCount(pState);
|
||||||
|
|
||||||
|
/* we don't get progress notifications for delete, so do it here */
|
||||||
|
if (NState_GetCommand(pState) == kCommandDelete)
|
||||||
|
printf("Deleting %s\n", selProposal->pRecord->filename);
|
||||||
|
|
||||||
|
return kNuOK;
|
||||||
|
} else
|
||||||
|
return kNuSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a three-digit progress percentage; range is 0% to 100%.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
PrintPercentage(ulong total, ulong progress)
|
||||||
|
{
|
||||||
|
ulong perc;
|
||||||
|
|
||||||
|
if (!total) {
|
||||||
|
/*printf(" %%");*/
|
||||||
|
printf(" ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total < 21474836) {
|
||||||
|
perc = (progress * 100 + 50) / total;
|
||||||
|
if (perc > 100)
|
||||||
|
perc = 100;
|
||||||
|
} else {
|
||||||
|
perc = progress / (total / 100);
|
||||||
|
if (perc > 100)
|
||||||
|
perc = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%3ld%%", perc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Show our progress, unless we're expanding to a pipe. Invoked as a
|
||||||
|
* callback by nufxlib.
|
||||||
|
*/
|
||||||
|
NuResult
|
||||||
|
ProgressUpdater(NuArchive* pArchive, void* vProgress)
|
||||||
|
{
|
||||||
|
const NuProgressData* pProgress = vProgress;
|
||||||
|
NulibState* pState;
|
||||||
|
const char* percStr;
|
||||||
|
const char* actionStr;
|
||||||
|
char nameBuf[kMaxDisplayLen+1];
|
||||||
|
Boolean showName, eolConv;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
if (NState_GetSuppressOutput(pState))
|
||||||
|
return kNuOK;
|
||||||
|
|
||||||
|
percStr = nil;
|
||||||
|
showName = false;
|
||||||
|
eolConv = false;
|
||||||
|
|
||||||
|
switch (pProgress->operation) {
|
||||||
|
case kNuOpAdd:
|
||||||
|
switch (pProgress->state) {
|
||||||
|
case kNuProgressPreparing:
|
||||||
|
case kNuProgressOpening:
|
||||||
|
actionStr = "adding ";
|
||||||
|
showName = true;
|
||||||
|
break;
|
||||||
|
case kNuProgressCompressing:
|
||||||
|
actionStr = "compressing";
|
||||||
|
break;
|
||||||
|
case kNuProgressStoring:
|
||||||
|
actionStr = "storing ";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
actionStr = "?????? ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kNuOpExtract:
|
||||||
|
switch (pProgress->state) {
|
||||||
|
case kNuProgressPreparing:
|
||||||
|
case kNuProgressOpening:
|
||||||
|
actionStr = "extracting";
|
||||||
|
showName = true;
|
||||||
|
break;
|
||||||
|
case kNuProgressExpanding:
|
||||||
|
actionStr = "expanding ";
|
||||||
|
break;
|
||||||
|
case kNuProgressCopying:
|
||||||
|
actionStr = "extracting";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
actionStr = "?????? ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pProgress->expand.convertEOL == kNuConvertOn)
|
||||||
|
eolConv = true;
|
||||||
|
break;
|
||||||
|
case kNuOpTest:
|
||||||
|
switch (pProgress->state) {
|
||||||
|
case kNuProgressPreparing:
|
||||||
|
case kNuProgressOpening:
|
||||||
|
showName = true;
|
||||||
|
/* no break */
|
||||||
|
case kNuProgressExpanding:
|
||||||
|
case kNuProgressCopying:
|
||||||
|
actionStr = "verifying";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
actionStr = "?????? ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
actionStr = "????";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pProgress->state) {
|
||||||
|
case kNuProgressDone:
|
||||||
|
actionStr = nil;
|
||||||
|
percStr = "DONE\n";
|
||||||
|
break;
|
||||||
|
case kNuProgressSkipped:
|
||||||
|
actionStr = nil;
|
||||||
|
percStr = "SKIP\n";
|
||||||
|
break;
|
||||||
|
case kNuProgressFailed:
|
||||||
|
actionStr = nil;
|
||||||
|
percStr = "FAIL\n";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showName) {
|
||||||
|
/*
|
||||||
|
* Use "pathname" (whole thing) rather than "filename" (file part).
|
||||||
|
* Could also use "origPathname", but I like to show what they're
|
||||||
|
* getting instead of what they're giving.
|
||||||
|
*/
|
||||||
|
int len = strlen(pProgress->pathname);
|
||||||
|
if (len < sizeof(nameBuf)) {
|
||||||
|
strcpy(nameBuf, pProgress->pathname);
|
||||||
|
} else {
|
||||||
|
nameBuf[0] = nameBuf[1] = '.';
|
||||||
|
strncpy(nameBuf+2, pProgress->pathname + len - (sizeof(nameBuf)-3),
|
||||||
|
sizeof(nameBuf)-3);
|
||||||
|
nameBuf[sizeof(nameBuf)-1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionStr == nil && percStr != nil) {
|
||||||
|
printf("\r%s", percStr);
|
||||||
|
} else if (actionStr != nil && percStr == nil) {
|
||||||
|
if (percStr == nil) {
|
||||||
|
putc('\r', stdout);
|
||||||
|
PrintPercentage(pProgress->uncompressedLength,
|
||||||
|
pProgress->uncompressedProgress);
|
||||||
|
if (showName)
|
||||||
|
printf(" %s%s %s", actionStr, eolConv ? "+" : " ", nameBuf);
|
||||||
|
else
|
||||||
|
printf(" %s%s", actionStr, eolConv ? "+" : " ");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(0);
|
||||||
|
printf("????\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
/*printf(" %ld \n", pProgress->uncompressedProgress);*/
|
||||||
|
/*usleep(250000);*/
|
||||||
|
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decide whether or not to replace an existing file (during extract)
|
||||||
|
* or record (during add).
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
HandleReplaceExisting(NulibState* pState, NuArchive* pArchive,
|
||||||
|
const NuErrorStatus* pErrorStatus)
|
||||||
|
{
|
||||||
|
NuResult result = kNuOK;
|
||||||
|
char* renameName;
|
||||||
|
char reply;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pErrorStatus != nil);
|
||||||
|
assert(pErrorStatus->pathname != nil);
|
||||||
|
|
||||||
|
assert(pErrorStatus->canOverwrite);
|
||||||
|
assert(pErrorStatus->canSkip);
|
||||||
|
assert(pErrorStatus->canAbort);
|
||||||
|
|
||||||
|
if (NState_GetInputUnavailable(pState)) {
|
||||||
|
putc('\n', stdout);
|
||||||
|
ReportError(pErrorStatus->err, "Giving up");
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
printf("\n Replace %s? [y]es, [n]o, [A]ll, [N]one",
|
||||||
|
pErrorStatus->pathname);
|
||||||
|
if (pErrorStatus->canRename) /* renaming records not allowed */
|
||||||
|
printf(", [r]ename: ");
|
||||||
|
else
|
||||||
|
printf(": ");
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
reply = GetReplyChar('n');
|
||||||
|
|
||||||
|
switch (reply) {
|
||||||
|
case 'y':
|
||||||
|
result = kNuOverwrite;
|
||||||
|
goto bail;
|
||||||
|
case 'n':
|
||||||
|
result = kNuSkip;
|
||||||
|
goto bail;
|
||||||
|
case 'A':
|
||||||
|
(void) NuSetValue(pArchive, kNuValueHandleExisting,
|
||||||
|
kNuAlwaysOverwrite);
|
||||||
|
result = kNuOverwrite;
|
||||||
|
goto bail;
|
||||||
|
case 'N':
|
||||||
|
(void) NuSetValue(pArchive, kNuValueHandleExisting,
|
||||||
|
kNuNeverOverwrite);
|
||||||
|
result = kNuSkip;
|
||||||
|
goto bail;
|
||||||
|
case 'r':
|
||||||
|
if (!pErrorStatus->canRename) {
|
||||||
|
printf("Response not acceptable\n");
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
}
|
||||||
|
renameName = GetReplyString("New name: ");
|
||||||
|
if (renameName == nil)
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
if (pErrorStatus->pRecord == nil) {
|
||||||
|
ReportError(kNuErrNone, "Unexpected nil record");
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
}
|
||||||
|
NState_SetRenameFromIdx(pState,
|
||||||
|
pErrorStatus->pRecord->recordIdx);
|
||||||
|
NState_SetRenameToStr(pState, renameName);
|
||||||
|
result = kNuRename;
|
||||||
|
goto bail;
|
||||||
|
case 'q': /* stealth option to quit */
|
||||||
|
case 'Q':
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
default:
|
||||||
|
printf("Response not understood -- please use y/n/A/N/r\n");
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Found a bad CRC... should we press onward?
|
||||||
|
*
|
||||||
|
* Note pErrorStatus->pathname may be nil if the error was found in the
|
||||||
|
* master header or in the record header.
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
HandleBadCRC(NulibState* pState, NuArchive* pArchive,
|
||||||
|
const NuErrorStatus* pErrorStatus)
|
||||||
|
{
|
||||||
|
NuResult result = kNuOK;
|
||||||
|
char reply;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pErrorStatus != nil);
|
||||||
|
|
||||||
|
if (NState_GetInputUnavailable(pState)) {
|
||||||
|
putc('\n', stderr);
|
||||||
|
ReportError(pErrorStatus->err, "Giving up");
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (pErrorStatus->pathname != nil)
|
||||||
|
fprintf(stderr, "\n Found a bad CRC in %s\n",
|
||||||
|
pErrorStatus->pathname);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "\n Found a bad CRC in the archive\n");
|
||||||
|
|
||||||
|
fprintf(stderr,
|
||||||
|
" Archive may be damaged, continue anyway? [y]es, [n]o: ");
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
reply = GetReplyChar('n');
|
||||||
|
|
||||||
|
switch (reply) {
|
||||||
|
case 'y':
|
||||||
|
result = kNuIgnore;
|
||||||
|
goto bail;
|
||||||
|
case 'n':
|
||||||
|
case 'N':
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Response not understood -- please use y/n\n");
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/*
|
||||||
|
* Tried to add a nonexistent file; continue?
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
HandleAddNotFound(NulibState* pState, NuArchive* pArchive,
|
||||||
|
const NuErrorStatus* pErrorStatus)
|
||||||
|
{
|
||||||
|
NuResult result = kNuOK;
|
||||||
|
char reply;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pErrorStatus != nil);
|
||||||
|
assert(pErrorStatus->pathname != nil);
|
||||||
|
|
||||||
|
if (NState_GetInputUnavailable(pState)) {
|
||||||
|
putc('\n', stdout);
|
||||||
|
ReportError(pErrorStatus->err, "Giving up");
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
fprintf("\n Couldn't find %s, continue? [y]es, [n]o: ",
|
||||||
|
pErrorStatus->pathname);
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
reply = GetReplyChar('n');
|
||||||
|
|
||||||
|
switch (reply) {
|
||||||
|
case 'y':
|
||||||
|
result = kNuSkip;
|
||||||
|
goto bail;
|
||||||
|
case 'n':
|
||||||
|
case 'N':
|
||||||
|
result = kNuAbort;
|
||||||
|
goto bail;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Response not understood -- please use y/n\n");
|
||||||
|
break; /* continue in "while" loop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Something failed, and the user may want to choose how to handle it.
|
||||||
|
* Invoked as a callback.
|
||||||
|
*/
|
||||||
|
NuResult
|
||||||
|
ErrorHandler(NuArchive* pArchive, void* vErrorStatus)
|
||||||
|
{
|
||||||
|
const NuErrorStatus* pErrorStatus = vErrorStatus;
|
||||||
|
NulibState* pState;
|
||||||
|
NuResult result;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
/* default action is to abort the current operation */
|
||||||
|
result = kNuAbort;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When extracting, the error handler callback gets invoked for several
|
||||||
|
* different problems because we might want to rename the file. Also,
|
||||||
|
* because extractions are done with "bulk" calls, returning an
|
||||||
|
* individual error message would be meaningless.
|
||||||
|
*
|
||||||
|
* When adding files, the NuAddFile and NuAddRecord calls can return
|
||||||
|
* immediate, specific results for a single add. The only reasons for
|
||||||
|
* calling here are to decide if an existing record should be replaced
|
||||||
|
* or not (without even an option to rename), or to decide what to do
|
||||||
|
* when the NuFlush call runs into a problem while adding a file.
|
||||||
|
*/
|
||||||
|
if (pErrorStatus->operation == kNuOpExtract) {
|
||||||
|
if (pErrorStatus->err == kNuErrFileExists) {
|
||||||
|
result = HandleReplaceExisting(pState, pArchive, pErrorStatus);
|
||||||
|
} else if (pErrorStatus->err == kNuErrNotNewer) {
|
||||||
|
/* if we were expecting this, it's okay */
|
||||||
|
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
|
||||||
|
printf("\rSKIP\n");
|
||||||
|
result = kNuSkip;
|
||||||
|
} else {
|
||||||
|
DBUG(("WEIRD one\n"));
|
||||||
|
}
|
||||||
|
} else if (pErrorStatus->err == kNuErrDuplicateNotFound) {
|
||||||
|
/* if we were expecting this, it's okay */
|
||||||
|
if (NState_GetModFreshen(pState)) {
|
||||||
|
printf("\rSKIP\n");
|
||||||
|
result = kNuSkip;
|
||||||
|
} else {
|
||||||
|
DBUG(("WEIRD two\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (pErrorStatus->operation == kNuOpAdd) {
|
||||||
|
if (pErrorStatus->err == kNuErrRecordExists) {
|
||||||
|
/* if they want to update or freshen, don't hassle them */
|
||||||
|
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState))
|
||||||
|
result = kNuOverwrite;
|
||||||
|
else
|
||||||
|
result = HandleReplaceExisting(pState, pArchive, pErrorStatus);
|
||||||
|
} else if (pErrorStatus->err == kNuErrFileNotFound) {
|
||||||
|
/*
|
||||||
|
* This should never happen, because NuLib2 verifies the
|
||||||
|
* presence of the files. (If you want to test this out,
|
||||||
|
* you have to "sabotage" AddFile, or remove a file from disk
|
||||||
|
* while NuFlush is running.)
|
||||||
|
*/
|
||||||
|
assert(0);
|
||||||
|
/*result = HandleAddNotFound(pState, pArchive, pErrorStatus);*/
|
||||||
|
}
|
||||||
|
} else if (pErrorStatus->operation == kNuOpTest) {
|
||||||
|
if (pErrorStatus->err == kNuErrBadMHCRC ||
|
||||||
|
pErrorStatus->err == kNuErrBadRHCRC ||
|
||||||
|
pErrorStatus->err == kNuErrBadThreadCRC ||
|
||||||
|
pErrorStatus->err == kNuErrBadDataCRC)
|
||||||
|
{
|
||||||
|
result = HandleBadCRC(pState, pArchive, pErrorStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/*
|
||||||
|
* Display an error message.
|
||||||
|
*
|
||||||
|
* (This was just a test to see if it worked... NufxLib's default behavior
|
||||||
|
* is fine for NuLib2.)
|
||||||
|
*/
|
||||||
|
NuResult
|
||||||
|
ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage)
|
||||||
|
{
|
||||||
|
const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage;
|
||||||
|
|
||||||
|
fprintf(stderr, "%s%d %3d %s:%d %s %s\n",
|
||||||
|
pArchive == nil ? "(GLOBAL)" : "",
|
||||||
|
pErrorMessage->isDebug, pErrorMessage->err, pErrorMessage->file,
|
||||||
|
pErrorMessage->line, pErrorMessage->function, pErrorMessage->message);
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Open an archive
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* an archive name of "-" indicates we want to use stdin */
|
||||||
|
static const char* kStdinArchive = "-";
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine whether the access bits on the record make it a read-only
|
||||||
|
* file or not.
|
||||||
|
*
|
||||||
|
* Uses a simplified view of the access flags.
|
||||||
|
*/
|
||||||
|
Boolean
|
||||||
|
IsRecordReadOnly(const NuRecord* pRecord)
|
||||||
|
{
|
||||||
|
if (pRecord->recAccess == 0x21L || pRecord->recAccess == 0x01L)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns "true" if "archiveName" is the name we use to represent stdin.
|
||||||
|
*/
|
||||||
|
Boolean
|
||||||
|
IsFilenameStdin(const char* archiveName)
|
||||||
|
{
|
||||||
|
assert(archiveName != nil);
|
||||||
|
return (strcmp(archiveName, kStdinArchive) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define BailError(err) { if (err != kNuErrNone) goto bail; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the archive in read-only mode. We use "file mode" for a file, or
|
||||||
|
* "streaming mode" for stdin.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
OpenArchiveReadOnly(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
if (IsFilenameStdin(NState_GetArchiveFilename(pState))) {
|
||||||
|
err = NuStreamOpenRO(stdin, &pArchive);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
ReportError(err, "unable to open create stdin archive");
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Since the archive is on stdin, we can't ask the user questions.
|
||||||
|
* On a UNIX system we could open /dev/tty, but that's not portable,
|
||||||
|
* and I don't think archives on stdin are going to be popular
|
||||||
|
* enough to make this worth doing.
|
||||||
|
*/
|
||||||
|
NState_SetInputUnavailable(pState, true);
|
||||||
|
} else {
|
||||||
|
err = NuOpenRO(NState_GetArchiveFilename(pState), &pArchive);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
ReportError(err, "unable to open '%s'",
|
||||||
|
NState_GetArchiveFilename(pState));
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* introduce them */
|
||||||
|
NState_SetNuArchive(pState, pArchive);
|
||||||
|
err = NuSetExtraData(pArchive, pState);
|
||||||
|
|
||||||
|
err = NuSetSelectionFilter(pArchive, SelectionFilter);
|
||||||
|
err = NuSetOutputPathnameFilter(pArchive, OutputPathnameFilter);
|
||||||
|
err = NuSetProgressUpdater(pArchive, ProgressUpdater);
|
||||||
|
err = NuSetErrorHandler(pArchive, ErrorHandler);
|
||||||
|
/*err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/
|
||||||
|
|
||||||
|
/* set the EOL conversion */
|
||||||
|
if (NState_GetModConvertAll(pState))
|
||||||
|
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOn);
|
||||||
|
else if (NState_GetModConvertText(pState))
|
||||||
|
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertAuto);
|
||||||
|
else
|
||||||
|
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOff);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
/* handle "-s" flag */
|
||||||
|
if (NState_GetModOverwriteExisting(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuAlwaysOverwrite);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle "-f" and "-u" flags (this overrides "-s" during extraction) */
|
||||||
|
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
if (NState_GetModFreshen(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG(("--- enabling ShrinkIt compatibility mode\n"));
|
||||||
|
err = NuSetValue(pArchive, kNuValueMimicSHK, true);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR);
|
||||||
|
else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF);
|
||||||
|
else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF);
|
||||||
|
else {
|
||||||
|
assert(0);
|
||||||
|
err = kNuErrInternal;
|
||||||
|
ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the archive in read-write mode, for purposes of adding, deleting,
|
||||||
|
* or updating files. We don't plan on extracting anything with this.
|
||||||
|
*
|
||||||
|
* "Streaming mode" isn't allowed.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
OpenArchiveReadWrite(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
NuArchive* pArchive;
|
||||||
|
char* tempName = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(IsFilenameStdin(NState_GetArchiveFilename(pState)) == false);
|
||||||
|
|
||||||
|
tempName = MakeTempArchiveName(pState);
|
||||||
|
if (tempName == nil)
|
||||||
|
goto bail;
|
||||||
|
DBUG(("TEMP NAME = '%s'\n", tempName));
|
||||||
|
|
||||||
|
err = NuOpenRW(NState_GetArchiveFilename(pState), tempName,
|
||||||
|
kNuOpenCreat, &pArchive);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
ReportError(err, "unable to open '%s'",
|
||||||
|
NState_GetArchiveFilename(pState));
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* introduce them */
|
||||||
|
NState_SetNuArchive(pState, pArchive);
|
||||||
|
err = NuSetExtraData(pArchive, pState);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
err = NuSetSelectionFilter(pArchive, SelectionFilter);
|
||||||
|
BailError(err)
|
||||||
|
err = NuSetProgressUpdater(pArchive, ProgressUpdater);
|
||||||
|
BailError(err)
|
||||||
|
err = NuSetErrorHandler(pArchive, ErrorHandler);
|
||||||
|
BailError(err)
|
||||||
|
/*err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/
|
||||||
|
|
||||||
|
/* handle "-0" flag */
|
||||||
|
if (NState_GetModNoCompression(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueDataCompression, kNuCompressNone);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle "-f" and "-u" flags */
|
||||||
|
/* (BUG: if "-f" is set, creating a new archive is impossible) */
|
||||||
|
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
if (NState_GetModFreshen(pState)) {
|
||||||
|
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite);
|
||||||
|
BailError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG(("--- enabling ShrinkIt compatibility mode\n"));
|
||||||
|
err = NuSetValue(pArchive, kNuValueMimicSHK, true);
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
/* this probably isn't needed here, but set it anyway */
|
||||||
|
if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR);
|
||||||
|
else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF);
|
||||||
|
else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0)
|
||||||
|
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF);
|
||||||
|
else {
|
||||||
|
assert(0);
|
||||||
|
err = kNuErrInternal;
|
||||||
|
ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
BailError(err);
|
||||||
|
|
||||||
|
/*(void) NuSetValue(pArchive, kNuValueAllowDuplicates, true);*/
|
||||||
|
|
||||||
|
bail:
|
||||||
|
Free(tempName);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,340 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) 19yy <name of author>
|
||||||
|
|
||||||
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
|
@ -0,0 +1,123 @@
|
||||||
|
2000/05/18 ***** v1.0.0 shipped *****
|
||||||
|
|
||||||
|
2000/05/18 fadden
|
||||||
|
- added nulib2 to set of things stripped by "distbin"
|
||||||
|
- 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
|
||||||
|
- don't call mktemp(), just pass template into NuOpenRW
|
||||||
|
- 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
|
||||||
|
- added "distbin" target
|
||||||
|
|
||||||
|
2000/02/09 ***** v0.5.0 (alpha) shipped *****
|
||||||
|
|
||||||
|
2000/02/09 fadden
|
||||||
|
- changed the comparison used when extracting/deleting a list of files
|
||||||
|
from strcasecmp to strcmp, since NufxLib does case-sensitive compares.
|
||||||
|
- fixed the percentage for files and archives larger than 21MB
|
||||||
|
|
||||||
|
2000/02/08 fadden
|
||||||
|
- tweaked the BeOS/PPC config around a little
|
||||||
|
- deleted some commas to make "gcc -pedantic" happy
|
||||||
|
- changed version to x.y.z format here too
|
||||||
|
- generalized the "aux" handling to include all MS-DOS device names
|
||||||
|
|
||||||
|
2000/02/06 fadden
|
||||||
|
- include @CFLAGS@ in case somebody wants to override them
|
||||||
|
|
||||||
|
2000/02/06 ***** v0.4b shipped *****
|
||||||
|
|
||||||
|
2000/02/06 fadden
|
||||||
|
- added "install-shared" make target
|
||||||
|
- portability fixes for HP/UX
|
||||||
|
|
||||||
|
2000/02/06 ***** v0.4a shipped *****
|
||||||
|
|
||||||
|
2000/02/06 fadden
|
||||||
|
- massaged configure.in for BeOS, and added some type casts for mwerks
|
||||||
|
|
||||||
|
2000/02/06 ***** v0.4 shipped *****
|
||||||
|
|
||||||
|
2000/02/05 fadden
|
||||||
|
- added "mkinstalldirs" to install target
|
||||||
|
- added Win32 makefile
|
||||||
|
- made a few implicit typecasts explicit for Visual C++'s benefit
|
||||||
|
- change "aux" to "_aux", because FAT filesystems choke on it
|
||||||
|
|
||||||
|
2000/02/04 fadden
|
||||||
|
- added Win32 recursive directory descent
|
||||||
|
|
||||||
|
2000/02/02 fadden
|
||||||
|
- minor changes to get it working under Win32 (Visual C++ 6.0)
|
||||||
|
- added --enable-dmalloc to configuration
|
||||||
|
|
||||||
|
2000/02/01 fadden
|
||||||
|
- screen out leading "./", and junk the path if ".." shows up in path
|
||||||
|
- don't try to add comments to records we're skipping
|
||||||
|
- set kNuValueEOL appropriately for the current system
|
||||||
|
|
||||||
|
2000/01/29 ***** v0.3 shipped *****
|
||||||
|
|
||||||
|
2000/01/29 fadden
|
||||||
|
- added "make install" target, with the standard autoconf defines
|
||||||
|
- added some examples to the man page
|
||||||
|
|
||||||
|
2000/01/28 fadden
|
||||||
|
- merged "Kind" and "Type" columns in "v" output
|
||||||
|
- display a '+' when doing EOL conversions on an extracted file
|
||||||
|
|
||||||
|
2000/01/26 fadden
|
||||||
|
- added UI for allowing the user to ignore bad CRCs
|
||||||
|
- implemented "-j" (junk paths) for add and extract
|
||||||
|
- implemented "-c" (comments) for add and extract
|
||||||
|
- added totals to bottom of "v" output
|
||||||
|
|
||||||
|
2000/01/25 fadden
|
||||||
|
- when extracting without type preservation, append "_rsrc_" to
|
||||||
|
resource forks
|
||||||
|
|
||||||
|
2000/01/24 fadden
|
||||||
|
- added support for "-k" (add as disk image) flag
|
||||||
|
|
||||||
|
2000/01/24 ***** v0.2 shipped *****
|
||||||
|
|
||||||
|
2000/01/22 fadden
|
||||||
|
- added support for "-u" (update) and "-f" (freshen) flags
|
||||||
|
- set file dates in AddFile call
|
||||||
|
|
||||||
|
2000/01/20 fadden
|
||||||
|
- restructed the progress updater
|
||||||
|
|
||||||
|
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
|
||||||
|
- started recording locked/unlocked status
|
||||||
|
- some BeOS/Metrowerks "it's not gcc" changes from Eric Shepherd
|
||||||
|
- implemented "-s" (stomp existing) and "-0" (no compression) modifiers
|
||||||
|
|
||||||
|
2000/01/17 ***** v0.1 shipped *****
|
||||||
|
|
||||||
|
(much time passes)
|
||||||
|
|
||||||
|
mid-1998 fadden
|
||||||
|
- work begins
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Delete files from the archive.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete the specified files.
|
||||||
|
*
|
||||||
|
* This uses the "bulk" delete call, allowing the SelectionFilter callback
|
||||||
|
* to do the matching against specified filenames.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoDelete(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadWrite(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
NState_SetMatchCount(pState, 0);
|
||||||
|
|
||||||
|
err = NuDelete(pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
if (!NState_GetMatchCount(pState))
|
||||||
|
printf("%s: no records matched\n", gProgName);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Extract files and test archives.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract all of the records from the archive, pulling out and displaying
|
||||||
|
* comment threads.
|
||||||
|
*
|
||||||
|
* The "bulk extract" call doesn't deal with comments. Since we want to
|
||||||
|
* show them while we're extracting the files, we have to manually find
|
||||||
|
* and extract them.
|
||||||
|
*/
|
||||||
|
static NuError
|
||||||
|
ExtractAllRecords(NulibState* pState, NuArchive* pArchive)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
const NuRecord* pRecord;
|
||||||
|
const NuThread* pThread;
|
||||||
|
NuRecordIdx recordIdx;
|
||||||
|
NuAttr numRecords;
|
||||||
|
int idx, threadIdx;
|
||||||
|
|
||||||
|
DBUG(("--- doing manual extract\n"));
|
||||||
|
assert(NState_GetCommand(pState) == kCommandExtract); /* no "-p" here */
|
||||||
|
|
||||||
|
err = NuGetAttr(pArchive, kNuAttrNumRecords, &numRecords);
|
||||||
|
for (idx = 0; idx < (int) numRecords; idx++) {
|
||||||
|
err = NuGetRecordIdxByPosition(pArchive, idx, &recordIdx);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: couldn't get record #%d (err=%d)\n",
|
||||||
|
idx, err);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = NuGetRecord(pArchive, recordIdx, &pRecord);
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: unable to get recordIdx %ld\n", recordIdx);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do we want to extract this record? */
|
||||||
|
if (!IsSpecified(pState, pRecord))
|
||||||
|
continue;
|
||||||
|
NState_IncMatchCount(pState);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look for a comment thread.
|
||||||
|
*/
|
||||||
|
for (threadIdx = 0; (ulong)threadIdx < pRecord->recTotalThreads;
|
||||||
|
threadIdx++)
|
||||||
|
{
|
||||||
|
pThread = NuGetThread(pRecord, threadIdx);
|
||||||
|
assert(pThread != nil);
|
||||||
|
|
||||||
|
if (NuGetThreadID(pThread) == kNuThreadIDComment &&
|
||||||
|
pThread->actualThreadEOF > 0)
|
||||||
|
{
|
||||||
|
printf("----- '%s':\n", pRecord->filename);
|
||||||
|
err = NuExtractThread(pArchive, pThread->threadIdx,
|
||||||
|
NState_GetCommentSink(pState));
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
printf("[comment extraction failed, continuing\n");
|
||||||
|
} else {
|
||||||
|
printf("\n-----\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extract the record, using the usual mechanisms */
|
||||||
|
err = NuExtractRecord(pArchive, recordIdx);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract the specified files.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoExtract(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadOnly(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
NState_SetMatchCount(pState, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're not interested in comments, just use the "bulk" extract
|
||||||
|
* call. If we want comments, we need to do this one at a time.
|
||||||
|
*/
|
||||||
|
if (!NState_GetModComments(pState)) {
|
||||||
|
err = NuExtract(pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
} else {
|
||||||
|
err = ExtractAllRecords(pState, pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NState_GetMatchCount(pState))
|
||||||
|
printf("%s: no records match\n", gProgName);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract the specified files to stdout.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoExtractToPipe(NulibState* pState)
|
||||||
|
{
|
||||||
|
/* we handle the "to pipe" part farther down */
|
||||||
|
return DoExtract(pState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do an integrity check on one or more records in the archive.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoTest(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadOnly(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
NState_SetMatchCount(pState, 0);
|
||||||
|
|
||||||
|
err = NuTest(pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
if (!NState_GetMatchCount(pState))
|
||||||
|
printf("%s: no records match\n", gProgName);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,660 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Filename manipulation, including file type preservation.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Common definitions
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define kPreserveIndic '#' /* use # rather than $ for hex indication */
|
||||||
|
#define kFilenameExtDelim '.' /* separates extension from filename */
|
||||||
|
#define kResourceFlag 'r'
|
||||||
|
#define kDiskImageFlag 'i'
|
||||||
|
#define kMaxExtLen 4 /* ".1234" */
|
||||||
|
#define kResourceStr "_rsrc_"
|
||||||
|
|
||||||
|
/* must be longer then strlen(kResourceStr)... no problem there */
|
||||||
|
#define kMaxPathGrowth (sizeof("#XXXXXXXXYYYYYYYYZ")-1 + kMaxExtLen+1)
|
||||||
|
|
||||||
|
|
||||||
|
/* ProDOS file type names; must be entirely in upper case */
|
||||||
|
static const char gFileTypeNames[256][4] = {
|
||||||
|
"NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT",
|
||||||
|
"FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR",
|
||||||
|
"RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17",
|
||||||
|
"$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F",
|
||||||
|
"TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27",
|
||||||
|
"$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F",
|
||||||
|
"$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37",
|
||||||
|
"$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F",
|
||||||
|
"DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47",
|
||||||
|
"$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F",
|
||||||
|
"GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN",
|
||||||
|
"HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN",
|
||||||
|
"$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67",
|
||||||
|
"$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV",
|
||||||
|
"$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77",
|
||||||
|
"$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F",
|
||||||
|
"$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87",
|
||||||
|
"$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F",
|
||||||
|
"$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97",
|
||||||
|
"$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F",
|
||||||
|
"WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7",
|
||||||
|
"$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF",
|
||||||
|
"SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF",
|
||||||
|
"NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC",
|
||||||
|
"PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV",
|
||||||
|
"FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF",
|
||||||
|
"$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI",
|
||||||
|
"SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF",
|
||||||
|
"LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7",
|
||||||
|
"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS",
|
||||||
|
"CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7",
|
||||||
|
"$F8", "OS ", "INT", "IVR", "BAS", "VAR", "REL", "SYS"
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some file extensions we recognize. When adding files with "extended"
|
||||||
|
* preservation mode, we try to assign types to files that weren't
|
||||||
|
* explicitly preserved, but nevertheless have a recognizeable type.
|
||||||
|
*
|
||||||
|
* geoff@gwlink.net points out that this really ought to be in an external
|
||||||
|
* file rather than a hard-coded table. Ought to fix that someday.
|
||||||
|
*/
|
||||||
|
static const struct {
|
||||||
|
const char* label;
|
||||||
|
ushort fileType;
|
||||||
|
ulong auxType;
|
||||||
|
uchar flags;
|
||||||
|
} gRecognizedExtensions[] = {
|
||||||
|
{ "ASM", 0xb0, 0x0003, 0 }, /* APW assembly source */
|
||||||
|
{ "C", 0xb0, 0x000a, 0 }, /* APW C source */
|
||||||
|
{ "H", 0xb0, 0x000a, 0 }, /* APW C header */
|
||||||
|
{ "BNY", 0xe0, 0x8000, 0 }, /* Binary II lib */
|
||||||
|
{ "BQY", 0xe0, 0x8000, 0 }, /* Binary II lib, w/ compress */
|
||||||
|
{ "BXY", 0xe0, 0x8000, 0 }, /* Binary II wrap around SHK */
|
||||||
|
{ "BSE", 0xe0, 0x8000, 0 }, /* Binary II wrap around SEA */
|
||||||
|
{ "SEA", 0xb3, 0xdb07, 0 }, /* GSHK SEA */
|
||||||
|
{ "GIF", 0xc0, 0x8006, 0 }, /* GIF image */
|
||||||
|
{ "JPG", 0x06, 0x0000, 0 }, /* JPEG (nicer than 'NON') */
|
||||||
|
{ "JPEG", 0x06, 0x0000, 0 }, /* JPEG (nicer than 'NON') */
|
||||||
|
{ "SHK", 0xe0, 0x8002, 0 }, /* ShrinkIt archive */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the three-letter representation of the file type name.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
GetFileTypeString(ulong fileType)
|
||||||
|
{
|
||||||
|
if (fileType < NELEM(gFileTypeNames))
|
||||||
|
return gFileTypeNames[fileType];
|
||||||
|
else
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* File type preservation
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a preservation string.
|
||||||
|
*
|
||||||
|
* "pathBuf" is assumed to have enough space to hold the current path
|
||||||
|
* plus kMaxPathGrowth more. It will be modified in place.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AddPreservationString(NulibState* pState,
|
||||||
|
const NuPathnameProposal* pPathProposal, char* pathBuf)
|
||||||
|
{
|
||||||
|
char extBuf[kMaxPathGrowth +1];
|
||||||
|
const NuRecord* pRecord;
|
||||||
|
const NuThread* pThread;
|
||||||
|
NuThreadID threadID;
|
||||||
|
char* cp;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pPathProposal != nil);
|
||||||
|
assert(pathBuf != nil);
|
||||||
|
assert(NState_GetModPreserveType(pState));
|
||||||
|
|
||||||
|
pRecord = pPathProposal->pRecord;
|
||||||
|
pThread = pPathProposal->pThread;
|
||||||
|
assert(pRecord != nil);
|
||||||
|
assert(pThread != nil);
|
||||||
|
|
||||||
|
cp = extBuf;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cons up a preservation string. On some platforms "sprintf" doesn't
|
||||||
|
* return the #of characters written, so we add it up manually.
|
||||||
|
*/
|
||||||
|
if (pRecord->recFileType < 0x100 && pRecord->recExtraType < 0x10000) {
|
||||||
|
sprintf(cp, "%c%02lx%04lx", kPreserveIndic, pRecord->recFileType,
|
||||||
|
pRecord->recExtraType);
|
||||||
|
cp += 7;
|
||||||
|
} else {
|
||||||
|
sprintf(cp, "%c%08lx%08lx", kPreserveIndic, pRecord->recFileType,
|
||||||
|
pRecord->recExtraType);
|
||||||
|
cp += 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind);
|
||||||
|
if (threadID == kNuThreadIDRsrcFork)
|
||||||
|
*cp++ = kResourceFlag;
|
||||||
|
else if (threadID == kNuThreadIDDiskImage)
|
||||||
|
*cp++ = kDiskImageFlag;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If they've asked for "extended" type preservation, then we need
|
||||||
|
* to retain either the existing extension or append an extension
|
||||||
|
* based on the ProDOS file type.
|
||||||
|
*/
|
||||||
|
if (NState_GetModPreserveTypeExtended(pState)) {
|
||||||
|
const char* pExt;
|
||||||
|
char* end;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find extension. Note FindExtension guarantees there's at least
|
||||||
|
* one char after '.'.
|
||||||
|
*
|
||||||
|
* It's hard to know when this is right and when it isn't. It's
|
||||||
|
* fairly likely that a text file really ought to end in ".txt",
|
||||||
|
* and it's fairly unlikely that a BIN file should be ".bin", but
|
||||||
|
* where do the rest fall in? We might want to force TXT files
|
||||||
|
* to be ".txt", and perhaps do something clever for some others.
|
||||||
|
*/
|
||||||
|
if (pRecord->recFileType == 0x04)
|
||||||
|
pExt = nil;
|
||||||
|
else
|
||||||
|
pExt = FindExtension(pState, pathBuf);
|
||||||
|
if (pExt != nil && strlen(pExt+1) <= kMaxExtLen) {
|
||||||
|
pExt++; /* skip past the '.' */
|
||||||
|
|
||||||
|
/* if it's strictly decimal-numeric, don't use it (.1, .2, etc) */
|
||||||
|
(void) strtoul(pExt, &end, 10);
|
||||||
|
if (*end == '\0') {
|
||||||
|
pExt = nil;
|
||||||
|
} else {
|
||||||
|
/* if '#' appears in it, don't use it -- it'll confuse us */
|
||||||
|
const char* ccp = pExt;
|
||||||
|
while (*ccp != '\0') {
|
||||||
|
if (*ccp == '#') {
|
||||||
|
pExt = nil;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ccp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* There's no extension on the filename. Use the standard
|
||||||
|
* ProDOS type, if one exists for this entry. We don't use
|
||||||
|
* the table if it's "NON" or a hex value.
|
||||||
|
*/
|
||||||
|
if (pRecord->recFileType) {
|
||||||
|
pExt = GetFileTypeString(pRecord->recFileType);
|
||||||
|
if (pExt[0] == '?' || pExt[0] == '$')
|
||||||
|
pExt = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pExt != nil) {
|
||||||
|
*cp++ = kFilenameExtDelim;
|
||||||
|
strcpy(cp, pExt);
|
||||||
|
cp += strlen(pExt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure it's terminated */
|
||||||
|
*cp = '\0';
|
||||||
|
|
||||||
|
assert(cp - extBuf <= kMaxPathGrowth);
|
||||||
|
strcat(pathBuf, extBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalize a path for the conventions on the output filesystem. This
|
||||||
|
* adds optional file type preservation.
|
||||||
|
*
|
||||||
|
* The path from the archive is in "pPathProposal". Thew new pathname
|
||||||
|
* will be placed in the "new pathname" section of "pPathProposal".
|
||||||
|
*
|
||||||
|
* The new pathname may be shorter (because characters were removed) or
|
||||||
|
* longer (if we add a "#XXYYYYZ" extension or replace chars with '%' codes).
|
||||||
|
*
|
||||||
|
* This returns the new pathname, which is held in NState's temporary
|
||||||
|
* pathname buffer.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
NormalizePath(NulibState* pState, NuPathnameProposal* pPathProposal)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
char* pathBuf;
|
||||||
|
const char* startp;
|
||||||
|
const char* endp;
|
||||||
|
char* dstp;
|
||||||
|
char localFssep;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pPathProposal != nil);
|
||||||
|
assert(pPathProposal->pathname != nil);
|
||||||
|
|
||||||
|
localFssep = NState_GetSystemPathSeparator(pState);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up temporary buffer space. The maximum possible expansion
|
||||||
|
* requires converting all chars to '%' codes and adding the longest
|
||||||
|
* possible preservation string.
|
||||||
|
*/
|
||||||
|
NState_SetTempPathnameLen(pState,
|
||||||
|
strlen(pPathProposal->pathname)*3 + kMaxPathGrowth +1);
|
||||||
|
pathBuf = NState_GetTempPathnameBuf(pState);
|
||||||
|
assert(pathBuf != nil);
|
||||||
|
if (pathBuf == nil)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
startp = pPathProposal->pathname;
|
||||||
|
dstp = pathBuf;
|
||||||
|
while (*startp == pPathProposal->filenameSeparator) {
|
||||||
|
/* ignore leading path sep; always extract to current dir */
|
||||||
|
startp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* normalize all directory components and the filename component */
|
||||||
|
while (startp != nil) {
|
||||||
|
endp = strchr(startp, pPathProposal->filenameSeparator);
|
||||||
|
if (endp != nil) {
|
||||||
|
/* normalize directory component */
|
||||||
|
err = NormalizeDirectoryName(pState, startp, endp - startp,
|
||||||
|
pPathProposal->filenameSeparator, &dstp,
|
||||||
|
NState_GetTempPathnameLen(pState));
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
*dstp++ = localFssep;
|
||||||
|
|
||||||
|
startp = endp +1;
|
||||||
|
} else {
|
||||||
|
/* normalize filename */
|
||||||
|
err = NormalizeFileName(pState, startp, strlen(startp),
|
||||||
|
pPathProposal->filenameSeparator, &dstp,
|
||||||
|
NState_GetTempPathnameLen(pState));
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
/* add/replace extension if necessary */
|
||||||
|
*dstp++ = '\0';
|
||||||
|
if (NState_GetModPreserveType(pState)) {
|
||||||
|
AddPreservationString(pState, pPathProposal, pathBuf);
|
||||||
|
} else if (NuGetThreadID(pPathProposal->pThread) == kNuThreadIDRsrcFork)
|
||||||
|
{
|
||||||
|
/* add this in lieu of the preservation extension */
|
||||||
|
strcat(pathBuf, kResourceStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
startp = nil; /* we're done */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pPathProposal->newPathname = pathBuf;
|
||||||
|
pPathProposal->newFilenameSeparator = localFssep;
|
||||||
|
|
||||||
|
/* check for overflow */
|
||||||
|
assert(dstp - pathBuf <=
|
||||||
|
(int)(strlen(pPathProposal->pathname) + kMaxPathGrowth));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If "junk paths" is set, drop everything but the last component.
|
||||||
|
*/
|
||||||
|
if (NState_GetModJunkPaths(pState)) {
|
||||||
|
char* lastFssep;
|
||||||
|
lastFssep = strrchr(pathBuf, localFssep);
|
||||||
|
if (lastFssep != nil) {
|
||||||
|
assert(*(lastFssep+1) != '\0'); /* should already have been caught*/
|
||||||
|
memmove(pathBuf, lastFssep+1, strlen(lastFssep+1)+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
return nil;
|
||||||
|
return pathBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* File type restoration
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to figure out what file type is associated with a filename extension.
|
||||||
|
*
|
||||||
|
* This checks the standard list of ProDOS types (which should catch things
|
||||||
|
* like "TXT" and "BIN") and the separate list of recognized extensions.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
LookupExtension(NulibState* pState, const char* ext, ulong* pFileType,
|
||||||
|
ulong* pAuxType)
|
||||||
|
{
|
||||||
|
char uext3[4];
|
||||||
|
int i, extLen;
|
||||||
|
|
||||||
|
extLen = strlen(ext);
|
||||||
|
assert(extLen > 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First step is to try to find it in the recognized types list.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < NELEM(gRecognizedExtensions); i++) {
|
||||||
|
if (strcasecmp(ext, gRecognizedExtensions[i].label) == 0) {
|
||||||
|
*pFileType = gRecognizedExtensions[i].fileType;
|
||||||
|
*pAuxType = gRecognizedExtensions[i].auxType;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Second step is to try to find it in the ProDOS types list.
|
||||||
|
*
|
||||||
|
* The extension is converted to upper case and padded with spaces.
|
||||||
|
*
|
||||||
|
* [do we want to obstruct matching on things like '$f7' here?]
|
||||||
|
*/
|
||||||
|
if (extLen <= 3) {
|
||||||
|
for (i = 2; i >= extLen; i--)
|
||||||
|
uext3[i] = ' ';
|
||||||
|
for ( ; i >= 0; i--)
|
||||||
|
uext3[i] = toupper(ext[i]);
|
||||||
|
uext3[3] = '\0';
|
||||||
|
|
||||||
|
/*printf("### converted '%s' to '%s'\n", ext, uext3);*/
|
||||||
|
|
||||||
|
for (i = 0; i < NELEM(gFileTypeNames); i++) {
|
||||||
|
if (strcmp(uext3, gFileTypeNames[i]) == 0) {
|
||||||
|
*pFileType = i;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to associate some meaning with the file extension.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
InterpretExtension(NulibState* pState, const char* pathName, ulong* pFileType,
|
||||||
|
ulong* pAuxType)
|
||||||
|
{
|
||||||
|
const char* pExt;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pathName != nil);
|
||||||
|
assert(pFileType != nil);
|
||||||
|
assert(pAuxType != nil);
|
||||||
|
|
||||||
|
pExt = FindExtension(pState, pathName);
|
||||||
|
if (pExt != nil)
|
||||||
|
LookupExtension(pState, pExt+1, pFileType, pAuxType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check to see if there's a preservation string on the filename. If so,
|
||||||
|
* set the filetype and auxtype information, and trim the preservation
|
||||||
|
* string off.
|
||||||
|
*
|
||||||
|
* We have to be careful not to trip on false-positive occurrences of '#'
|
||||||
|
* in the filename.
|
||||||
|
*/
|
||||||
|
Boolean
|
||||||
|
ExtractPreservationString(NulibState* pState, char* pathname, ulong* pFileType,
|
||||||
|
ulong* pAuxType, NuThreadID* pThreadID)
|
||||||
|
{
|
||||||
|
char numBuf[9];
|
||||||
|
ulong fileType, auxType;
|
||||||
|
NuThreadID threadID;
|
||||||
|
char* pPreserve;
|
||||||
|
char* cp;
|
||||||
|
int digitCount;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pathname != nil);
|
||||||
|
assert(pFileType != nil);
|
||||||
|
assert(pAuxType != nil);
|
||||||
|
assert(pThreadID != nil);
|
||||||
|
|
||||||
|
pPreserve = strrchr(pathname, kPreserveIndic);
|
||||||
|
if (pPreserve == nil)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* count up the #of hex digits */
|
||||||
|
digitCount = 0;
|
||||||
|
for (cp = pPreserve+1; *cp != '\0' && isxdigit((int)*cp); cp++)
|
||||||
|
digitCount++;
|
||||||
|
|
||||||
|
/* extract the file and aux type */
|
||||||
|
switch (digitCount) {
|
||||||
|
case 6:
|
||||||
|
/* ProDOS 1-byte type and 2-byte aux */
|
||||||
|
memcpy(numBuf, pPreserve+1, 2);
|
||||||
|
numBuf[2] = 0;
|
||||||
|
fileType = strtoul(numBuf, &cp, 16);
|
||||||
|
assert(cp == numBuf + 2);
|
||||||
|
|
||||||
|
auxType = strtoul(pPreserve+3, &cp, 16);
|
||||||
|
assert(cp == pPreserve + 7);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
/* HFS 4-byte type and 4-byte creator */
|
||||||
|
memcpy(numBuf, pPreserve+1, 8);
|
||||||
|
numBuf[8] = 0;
|
||||||
|
fileType = strtoul(numBuf, &cp, 16);
|
||||||
|
assert(cp == numBuf + 8);
|
||||||
|
|
||||||
|
auxType = strtoul(pPreserve+9, &cp, 16);
|
||||||
|
assert(cp == pPreserve + 17);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* not valid */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for a threadID specifier */
|
||||||
|
threadID = kNuThreadIDDataFork;
|
||||||
|
switch (*cp) {
|
||||||
|
case kResourceFlag:
|
||||||
|
threadID = kNuThreadIDRsrcFork;
|
||||||
|
cp++;
|
||||||
|
break;
|
||||||
|
case kDiskImageFlag:
|
||||||
|
threadID = kNuThreadIDDiskImage;
|
||||||
|
cp++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* do nothing... yet */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure we were the very last component */
|
||||||
|
switch (*cp) {
|
||||||
|
case kFilenameExtDelim: /* redundant "-ee" extension */
|
||||||
|
case '\0': /* end of string! */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* truncate the original string, and return what we got */
|
||||||
|
*pPreserve = '\0';
|
||||||
|
*pFileType = fileType;
|
||||||
|
*pAuxType = auxType;
|
||||||
|
*pThreadID = threadID;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove NuLib2's normalization magic (e.g. "%2f" for '/').
|
||||||
|
*
|
||||||
|
* This always results in the filename staying the same length or getting
|
||||||
|
* smaller, so we can do it in place in the buffer.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
DenormalizePath(NulibState* pState, char* pathBuf)
|
||||||
|
{
|
||||||
|
const char* srcp;
|
||||||
|
char* dstp;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
srcp = pathBuf;
|
||||||
|
dstp = pathBuf;
|
||||||
|
|
||||||
|
while (*srcp != '\0') {
|
||||||
|
if (*srcp == kForeignIndic) {
|
||||||
|
srcp++;
|
||||||
|
if (*srcp == kForeignIndic) {
|
||||||
|
*dstp++ = kForeignIndic;
|
||||||
|
srcp++;
|
||||||
|
} else if (isxdigit((int)*srcp)) {
|
||||||
|
ch = HexDigit(*srcp) << 4;
|
||||||
|
srcp++;
|
||||||
|
if (isxdigit((int)*srcp)) {
|
||||||
|
/* valid, output char */
|
||||||
|
ch += HexDigit(*srcp);
|
||||||
|
*dstp++ = ch;
|
||||||
|
srcp++;
|
||||||
|
} else {
|
||||||
|
/* bogus '%' with trailing hex digit found! */
|
||||||
|
*dstp++ = kForeignIndic;
|
||||||
|
*dstp++ = *(srcp-1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* bogus lone '%s' found! */
|
||||||
|
*dstp++ = kForeignIndic;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
*dstp++ = *srcp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dstp = '\0';
|
||||||
|
assert(dstp <= srcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Misc utils
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the filename component of a local pathname. Uses the fssep defined
|
||||||
|
* in pState.
|
||||||
|
*
|
||||||
|
* Always returns a pointer to a string; never returns nil.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
FilenameOnly(NulibState* pState, const char* pathname)
|
||||||
|
{
|
||||||
|
const char* retstr;
|
||||||
|
const char* pSlash;
|
||||||
|
char* tmpStr = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
assert(pathname != nil);
|
||||||
|
|
||||||
|
pSlash = strrchr(pathname, NState_GetSystemPathSeparator(pState));
|
||||||
|
if (pSlash == nil) {
|
||||||
|
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, NState_GetSystemPathSeparator(pState));
|
||||||
|
|
||||||
|
if (pSlash == nil) {
|
||||||
|
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 nil if no
|
||||||
|
* extension was found.
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
FindExtension(NulibState* pState, const char* pathname)
|
||||||
|
{
|
||||||
|
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(pState, pathname);
|
||||||
|
assert(pFilename != nil);
|
||||||
|
pExt = strrchr(pFilename, kFilenameExtDelim);
|
||||||
|
|
||||||
|
/* also check for "/blah/foo.", which doesn't count */
|
||||||
|
if (pExt != nil && *(pExt+1) != '\0')
|
||||||
|
return pExt;
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -0,0 +1,420 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* List files in the archive.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* kinds of records */
|
||||||
|
enum RecordKind {
|
||||||
|
kRecordKindUnknown = 0,
|
||||||
|
kRecordKindDisk,
|
||||||
|
kRecordKindFile,
|
||||||
|
kRecordKindForkedFile
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* gShortFormatNames[] = {
|
||||||
|
"unc", "squ", "lz1", "lz2", "u12", "u16"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* days of the week */
|
||||||
|
static const char* gDayNames[] = {
|
||||||
|
"[ null ]",
|
||||||
|
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
/* months of the year */
|
||||||
|
static const char* gMonths[] = {
|
||||||
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||||
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||||
|
};
|
||||||
|
|
||||||
|
#define kNuDateOutputLen 64
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute a percentage.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ComputePercent(ulong totalSize, ulong size)
|
||||||
|
{
|
||||||
|
int perc;
|
||||||
|
|
||||||
|
if (!totalSize && !size)
|
||||||
|
return 100; /* file is zero bytes long */
|
||||||
|
|
||||||
|
if (totalSize < 21474836)
|
||||||
|
perc = (totalSize * 100) / size;
|
||||||
|
else
|
||||||
|
perc = totalSize / (size/100);
|
||||||
|
|
||||||
|
/* don't say "0%" if it's not actually zero... it looks dumb */
|
||||||
|
if (!perc && size)
|
||||||
|
perc = 1;
|
||||||
|
|
||||||
|
return perc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a NuDateTime structure into something printable. This uses an
|
||||||
|
* abbreviated format, with date and time but not weekday or seconds.
|
||||||
|
*
|
||||||
|
* The buffer passed in must hold at least kNuDateOutputLen bytes.
|
||||||
|
*
|
||||||
|
* Returns "buffer" for the benefit of printf() calls.
|
||||||
|
*/
|
||||||
|
static char*
|
||||||
|
FormatDateShort(const NuDateTime* pDateTime, char* buffer)
|
||||||
|
{
|
||||||
|
/* is it valid? */
|
||||||
|
if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 ||
|
||||||
|
pDateTime->minute > 59)
|
||||||
|
{
|
||||||
|
strcpy(buffer, " <invalid> ");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buffer, "%02d-%s-%02d %02d:%02d",
|
||||||
|
pDateTime->day+1, gMonths[pDateTime->month], pDateTime->year % 100,
|
||||||
|
pDateTime->hour, pDateTime->minute);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NuStream callback function. Displays the filename.
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
ShowContentsShort(NuArchive* pArchive, void* vpRecord)
|
||||||
|
{
|
||||||
|
const NuRecord* pRecord = (NuRecord*) vpRecord;
|
||||||
|
NulibState* pState;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
if (!IsSpecified(pState, pRecord))
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
printf("%s\n",
|
||||||
|
pRecord->filename == nil ? "<unknown>":(const char*)pRecord->filename);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Analyze the contents of a record to determine if it's a disk, file,
|
||||||
|
* or "other". Compute the total compressed and uncompressed lengths
|
||||||
|
* of all data threads. Return the "best" format.
|
||||||
|
*
|
||||||
|
* The "best format" and "record type" stuff assume that the entire
|
||||||
|
* record contains only a disk thread or a file thread, and that any
|
||||||
|
* format is interesting so long as it isn't "no compression". In
|
||||||
|
* general these will be true, because ShrinkIt and NuLib create files
|
||||||
|
* this way.
|
||||||
|
*
|
||||||
|
* You could, of course, create a single record with a data thread and
|
||||||
|
* a disk image thread, but it's a fair bet ShrinkIt would ignore one
|
||||||
|
* or the other.
|
||||||
|
*/
|
||||||
|
static NuError
|
||||||
|
AnalyzeRecord(const NuRecord* pRecord, enum RecordKind* pRecordKind,
|
||||||
|
ushort* pFormat, ulong* pTotalLen, ulong* pTotalCompLen)
|
||||||
|
{
|
||||||
|
const NuThread* pThread;
|
||||||
|
NuThreadID threadID;
|
||||||
|
ulong idx;
|
||||||
|
|
||||||
|
*pRecordKind = kRecordKindUnknown;
|
||||||
|
*pTotalLen = *pTotalCompLen = 0;
|
||||||
|
*pFormat = kNuThreadFormatUncompressed;
|
||||||
|
|
||||||
|
for (idx = 0; idx < pRecord->recTotalThreads; idx++) {
|
||||||
|
pThread = NuGetThread(pRecord, idx);
|
||||||
|
assert(pThread != nil);
|
||||||
|
|
||||||
|
if (pThread->thThreadClass == kNuThreadClassData) {
|
||||||
|
/* replace what's there if this might be more interesting */
|
||||||
|
if (*pFormat == kNuThreadFormatUncompressed)
|
||||||
|
*pFormat = pThread->thThreadFormat;
|
||||||
|
|
||||||
|
threadID = NuMakeThreadID(pThread->thThreadClass,
|
||||||
|
pThread->thThreadKind);
|
||||||
|
if (threadID == kNuThreadIDRsrcFork)
|
||||||
|
*pRecordKind = kRecordKindForkedFile;
|
||||||
|
else if (threadID == kNuThreadIDDiskImage)
|
||||||
|
*pRecordKind = kRecordKindDisk;
|
||||||
|
else if (threadID == kNuThreadIDDataFork &&
|
||||||
|
*pRecordKind == kRecordKindUnknown)
|
||||||
|
*pRecordKind = kRecordKindFile;
|
||||||
|
|
||||||
|
/* sum up, so we get both forks of forked files */
|
||||||
|
*pTotalLen += pThread->actualThreadEOF;
|
||||||
|
*pTotalCompLen += pThread->thCompThreadEOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NuStream callback function. Displays the filename and several attributes.
|
||||||
|
*
|
||||||
|
* This is intended to mimic the output of some old version of ProDOS 8
|
||||||
|
* ShrinkIt.
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
ShowContentsVerbose(NuArchive* pArchive, void* vpRecord)
|
||||||
|
{
|
||||||
|
NuError err = kNuErrNone;
|
||||||
|
const NuRecord* pRecord = (NuRecord*) vpRecord;
|
||||||
|
enum RecordKind recordKind;
|
||||||
|
ulong totalLen, totalCompLen;
|
||||||
|
ushort format;
|
||||||
|
NulibState* pState;
|
||||||
|
char date1[kNuDateOutputLen];
|
||||||
|
char tmpbuf[16];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
assert(pArchive != nil);
|
||||||
|
(void) NuGetExtraData(pArchive, (void**) &pState);
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
if (!IsSpecified(pState, pRecord))
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
err = AnalyzeRecord(pRecord, &recordKind, &format, &totalLen,
|
||||||
|
&totalCompLen);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
len = strlen(pRecord->filename);
|
||||||
|
if (len <= 27) {
|
||||||
|
printf("%c%-27.27s ", IsRecordReadOnly(pRecord) ? '+' : ' ',
|
||||||
|
pRecord->filename);
|
||||||
|
} else {
|
||||||
|
printf("%c..%-25.25s ", IsRecordReadOnly(pRecord) ? '+' : ' ',
|
||||||
|
pRecord->filename + len - 25);
|
||||||
|
}
|
||||||
|
switch (recordKind) {
|
||||||
|
case kRecordKindUnknown:
|
||||||
|
printf("??? $%04lX ",
|
||||||
|
/*GetFileTypeString(pRecord->recFileType),*/
|
||||||
|
pRecord->recExtraType);
|
||||||
|
break;
|
||||||
|
case kRecordKindDisk:
|
||||||
|
sprintf(tmpbuf, "%ldk", totalLen / 1024);
|
||||||
|
printf("Disk %-6s ", tmpbuf);
|
||||||
|
break;
|
||||||
|
case kRecordKindFile:
|
||||||
|
case kRecordKindForkedFile:
|
||||||
|
printf("%s%c $%04lX ",
|
||||||
|
GetFileTypeString(pRecord->recFileType),
|
||||||
|
recordKind == kRecordKindForkedFile ? '+' : ' ',
|
||||||
|
pRecord->recExtraType);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
printf("ERROR ");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s ", FormatDateShort(&pRecord->recArchiveWhen, date1));
|
||||||
|
if (format >= NELEM(gShortFormatNames))
|
||||||
|
printf("??? ");
|
||||||
|
else
|
||||||
|
printf("%s ", gShortFormatNames[format]);
|
||||||
|
|
||||||
|
/* compute the percent size */
|
||||||
|
if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen))
|
||||||
|
printf("--- "); /* weird */
|
||||||
|
else if (totalLen < totalCompLen)
|
||||||
|
printf(">100%% "); /* compression failed? */
|
||||||
|
else {
|
||||||
|
sprintf(tmpbuf, "%02d%%", ComputePercent(totalCompLen, totalLen));
|
||||||
|
printf("%4s ", tmpbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totalLen && totalCompLen)
|
||||||
|
printf(" ????"); /* weird */
|
||||||
|
else
|
||||||
|
printf("%8ld", totalLen);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
NState_AddToTotals(pState, totalLen, totalCompLen);
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (err != kNuErrNone) {
|
||||||
|
printf("(ERROR on '%s')\n", pRecord->filename == nil ?
|
||||||
|
"<unknown>" : (const char*)pRecord->filename);
|
||||||
|
}
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a short listing of the contents of an archive.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoListShort(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadOnly(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
err = NuContents(pArchive, ShowContentsShort);
|
||||||
|
/* fall through with err */
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a more verbose listing of the contents of an archive.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoListVerbose(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
const NuMasterHeader* pHeader;
|
||||||
|
char date1[kNuDateOutputLen];
|
||||||
|
char date2[kNuDateOutputLen];
|
||||||
|
long totalLen, totalCompLen;
|
||||||
|
const char* cp;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadOnly(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to get just the filename.
|
||||||
|
*/
|
||||||
|
if (IsFilenameStdin(NState_GetArchiveFilename(pState)))
|
||||||
|
cp = "<stdin>";
|
||||||
|
else
|
||||||
|
cp = FilenameOnly(pState, NState_GetArchiveFilename(pState));
|
||||||
|
|
||||||
|
/* grab the master header block */
|
||||||
|
err = NuGetMasterHeader(pArchive, &pHeader);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
printf(" %-15.15s Created:%s Mod:%s Recs:%5lu\n\n",
|
||||||
|
cp,
|
||||||
|
FormatDateShort(&pHeader->mhArchiveCreateWhen, date1),
|
||||||
|
FormatDateShort(&pHeader->mhArchiveModWhen, date2),
|
||||||
|
pHeader->mhTotalRecords);
|
||||||
|
printf(" Name Type Auxtyp Archived"
|
||||||
|
" Fmat Size Un-Length\n");
|
||||||
|
printf("-------------------------------------------------"
|
||||||
|
"----------------------------\n");
|
||||||
|
|
||||||
|
err = NuContents(pArchive, ShowContentsVerbose);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Show the totals. NuFX overhead can be as much as 25% for archives
|
||||||
|
* with lots of small files.
|
||||||
|
*/
|
||||||
|
NState_GetTotals(pState, &totalLen, &totalCompLen);
|
||||||
|
printf("-------------------------------------------------"
|
||||||
|
"----------------------------\n");
|
||||||
|
printf(" Uncomp: %ld Comp: %ld %%of orig: %d%%\n",
|
||||||
|
totalLen, totalCompLen,
|
||||||
|
totalLen == 0 ? 0 : ComputePercent(totalCompLen, totalLen));
|
||||||
|
#ifdef DEBUG_VERBOSE
|
||||||
|
printf(" Overhead: %ld (%d%%)\n",
|
||||||
|
pHeader->mhMasterEOF - totalCompLen,
|
||||||
|
ComputePercent(pHeader->mhMasterEOF - totalCompLen,
|
||||||
|
pHeader->mhMasterEOF));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*(void) NuDebugDumpArchive(pArchive);*/
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Null callback, for those times when you don't really want to do anything.
|
||||||
|
*/
|
||||||
|
static NuResult
|
||||||
|
NullCallback(NuArchive* pArchive, void* vpRecord)
|
||||||
|
{
|
||||||
|
return kNuOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print very detailed output, suitable for debugging (requires that
|
||||||
|
* debugging be enabled in nufxlib).
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
DoListDebug(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuArchive* pArchive = nil;
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
err = OpenArchiveReadOnly(pState);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
pArchive = NState_GetNuArchive(pState);
|
||||||
|
assert(pArchive != nil);
|
||||||
|
|
||||||
|
/* have to do something to force the library to scan the archive */
|
||||||
|
err = NuContents(pArchive, NullCallback);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
err = NuDebugDumpArchive(pArchive);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
fprintf(stderr, "ERROR: debugging not enabled in nufxlib\n");
|
||||||
|
/* fall through with err */
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (pArchive != nil)
|
||||||
|
(void) NuClose(pArchive);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,409 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Main entry point and shell command argument processing.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Globals and constants.
|
||||||
|
*/
|
||||||
|
const char* gProgName = "Nulib2";
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Which modifiers are valid with which commands?
|
||||||
|
*/
|
||||||
|
typedef struct ValidCombo {
|
||||||
|
Command cmd;
|
||||||
|
Boolean okayForPipe;
|
||||||
|
Boolean filespecRequired;
|
||||||
|
const char* modifiers;
|
||||||
|
} ValidCombo;
|
||||||
|
|
||||||
|
static const ValidCombo gValidCombos[] = {
|
||||||
|
{ kCommandAdd, false, true, "ufrj0cke" },
|
||||||
|
{ kCommandDelete, false, true, "r" },
|
||||||
|
{ kCommandExtract, true, false, "ufrjclse" },
|
||||||
|
{ kCommandExtractToPipe, true, false, "rl" },
|
||||||
|
{ kCommandListShort, true, false, "" },
|
||||||
|
{ kCommandListVerbose, true, false, "" },
|
||||||
|
{ kCommandListDebug, true, false, "" },
|
||||||
|
{ kCommandTest, true, false, "r" },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find an entry in the gValidCombos table matching the specified command.
|
||||||
|
*
|
||||||
|
* Returns nil if not found.
|
||||||
|
*/
|
||||||
|
static const ValidCombo*
|
||||||
|
FindValidComboEntry(Command cmd)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < NELEM(gValidCombos); i++) {
|
||||||
|
if (gValidCombos[i].cmd == cmd)
|
||||||
|
return &gValidCombos[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine whether the specified modifier is valid when used with the
|
||||||
|
* current command.
|
||||||
|
*/
|
||||||
|
static Boolean
|
||||||
|
IsValidModifier(Command cmd, char modifier)
|
||||||
|
{
|
||||||
|
const ValidCombo* pvc;
|
||||||
|
|
||||||
|
pvc = FindValidComboEntry(cmd);
|
||||||
|
if (pvc != nil) {
|
||||||
|
if (strchr(pvc->modifiers, modifier) == nil)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine whether the specified command can be used with stdin as input.
|
||||||
|
*/
|
||||||
|
static Boolean
|
||||||
|
IsValidOnPipe(Command cmd)
|
||||||
|
{
|
||||||
|
const ValidCombo* pvc;
|
||||||
|
|
||||||
|
pvc = FindValidComboEntry(cmd);
|
||||||
|
if (pvc != nil) {
|
||||||
|
return pvc->okayForPipe;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine whether the specified command can be used with stdin as input.
|
||||||
|
*/
|
||||||
|
static Boolean
|
||||||
|
IsFilespecRequired(Command cmd)
|
||||||
|
{
|
||||||
|
const ValidCombo* pvc;
|
||||||
|
|
||||||
|
pvc = FindValidComboEntry(cmd);
|
||||||
|
if (pvc != nil) {
|
||||||
|
return pvc->filespecRequired;
|
||||||
|
} else {
|
||||||
|
/* command not found? warn about it here... */
|
||||||
|
fprintf(stderr, "%s: Command %d not found in gValidCombos table\n",
|
||||||
|
gProgName, cmd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Separate the program name out of argv[0].
|
||||||
|
*/
|
||||||
|
static const char*
|
||||||
|
GetProgName(const NulibState* pState, const char* argv0)
|
||||||
|
{
|
||||||
|
const char* result;
|
||||||
|
char sep;
|
||||||
|
|
||||||
|
/* use the appropriate system pathname separator */
|
||||||
|
sep = NState_GetSystemPathSeparator(pState);
|
||||||
|
|
||||||
|
result = strrchr(argv0, sep);
|
||||||
|
if (result == nil)
|
||||||
|
result = argv0;
|
||||||
|
else
|
||||||
|
result++; /* advance past the separator */
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print program usage.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
Usage(const NulibState* pState)
|
||||||
|
{
|
||||||
|
long majorVersion, minorVersion, bugVersion;
|
||||||
|
const char* nufxLibDate;
|
||||||
|
const char* nufxLibFlags;
|
||||||
|
|
||||||
|
(void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion,
|
||||||
|
&nufxLibDate, &nufxLibFlags);
|
||||||
|
|
||||||
|
printf("\nNulib2 v%s, linked with NufxLib v%ld.%ld.%ld [%s]\n",
|
||||||
|
NState_GetProgramVersion(pState),
|
||||||
|
majorVersion, minorVersion, bugVersion, nufxLibFlags);
|
||||||
|
printf("This software is distributed under terms of the GNU General Public License.\n");
|
||||||
|
printf("Written by Andy McFadden, http://www.nulib.com/.\n\n");
|
||||||
|
printf("Usage: %s -command[modifiers] archive [filename-list]\n\n",
|
||||||
|
gProgName);
|
||||||
|
printf(
|
||||||
|
" -a add files, create arc if needed -x extract files\n"
|
||||||
|
" -t list files (short) -v list files (verbose)\n"
|
||||||
|
" -p extract files to pipe, no msgs -i test archive integrity\n"
|
||||||
|
" -d delete files from archive\n"
|
||||||
|
"\n"
|
||||||
|
" modifiers:\n"
|
||||||
|
" -u update files (add + keep newest) -f freshen (update, no add)\n"
|
||||||
|
" -r recurse into subdirs -j junk (don't record) directory names\n"
|
||||||
|
" -0 don't use compression -c add one-line comments\n"
|
||||||
|
" -l auto-convert text files -ll auto-convert ALL files\n"
|
||||||
|
" -s stomp existing files w/o asking -k store files as disk images\n"
|
||||||
|
" -e preserve ProDOS file types -ee extend preserved names\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process the command-line options. The results are placed into "pState".
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ProcessOptions(NulibState* pState, int argc, char* const* argv)
|
||||||
|
{
|
||||||
|
const char* cp;
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Must have at least a command letter and an archive filename.
|
||||||
|
*/
|
||||||
|
if (argc < 3) {
|
||||||
|
Usage(pState);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Argv[1] and any subsequent entries that have a leading hyphen
|
||||||
|
* are options. Anything after that is a filename. Parse until we
|
||||||
|
* think we've hit the filename.
|
||||||
|
*
|
||||||
|
* By UNIX convention, however, stdin is specified as a file called "-".
|
||||||
|
*/
|
||||||
|
for (idx = 1; idx < argc; idx++) {
|
||||||
|
cp = argv[idx];
|
||||||
|
|
||||||
|
if (idx > 1 && *cp != '-')
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (*cp == '-')
|
||||||
|
cp++;
|
||||||
|
if (*cp == '\0') {
|
||||||
|
if (idx == 1) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: You must specify a command after the '-'\n",
|
||||||
|
gProgName);
|
||||||
|
goto fail;
|
||||||
|
} else {
|
||||||
|
/* they're using '-' for the filename */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx == 1) {
|
||||||
|
switch (tolower(*cp)) {
|
||||||
|
case 'a': NState_SetCommand(pState, kCommandAdd); break;
|
||||||
|
case 'x': NState_SetCommand(pState, kCommandExtract); break;
|
||||||
|
case 'p': NState_SetCommand(pState, kCommandExtractToPipe); break;
|
||||||
|
case 't': NState_SetCommand(pState, kCommandListShort); break;
|
||||||
|
case 'v': NState_SetCommand(pState, kCommandListVerbose); break;
|
||||||
|
case 'z': NState_SetCommand(pState, kCommandListDebug); break;
|
||||||
|
case 'i': NState_SetCommand(pState, kCommandTest); break;
|
||||||
|
case 'd': NState_SetCommand(pState, kCommandDelete); break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: Unknown command '%c'\n", gProgName, *cp);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*cp != '\0') {
|
||||||
|
switch (tolower(*cp)) {
|
||||||
|
case 'u': NState_SetModUpdate(pState, true); break;
|
||||||
|
case 'f': NState_SetModFreshen(pState, true); break;
|
||||||
|
case 'r': NState_SetModRecurse(pState, true); break;
|
||||||
|
case 'j': NState_SetModJunkPaths(pState, true); break;
|
||||||
|
case '0': NState_SetModNoCompression(pState, true); break;
|
||||||
|
case 'c': NState_SetModComments(pState, true); break;
|
||||||
|
case 's': NState_SetModOverwriteExisting(pState, true); break;
|
||||||
|
case 'k': NState_SetModAddAsDisk(pState, true); break;
|
||||||
|
case 'e':
|
||||||
|
if (*(cp-1) == 'e') /* should never point at invalid */
|
||||||
|
NState_SetModPreserveTypeExtended(pState, true);
|
||||||
|
else
|
||||||
|
NState_SetModPreserveType(pState, true);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
if (*(cp-1) == 'l') /* should never point at invalid */
|
||||||
|
NState_SetModConvertAll(pState, true);
|
||||||
|
else
|
||||||
|
NState_SetModConvertText(pState, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: Unknown modifier '%c'\n", gProgName, *cp);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValidModifier(NState_GetCommand(pState), (char)tolower(*cp)))
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: The '%c' modifier doesn't make sense here\n",
|
||||||
|
gProgName, tolower(*cp));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See if we have an archive name. If it's "-", see if we allow that.
|
||||||
|
*/
|
||||||
|
assert(idx < argc);
|
||||||
|
NState_SetArchiveFilename(pState, argv[idx]);
|
||||||
|
if (IsFilenameStdin(argv[idx])) {
|
||||||
|
if (!IsValidOnPipe(NState_GetCommand(pState))) {
|
||||||
|
fprintf(stderr, "%s: You can't do that with a pipe\n",
|
||||||
|
gProgName);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See if we have a file specification. Some of the commands require
|
||||||
|
* a filespec; others just perform the requested operation on all of
|
||||||
|
* the records in the archive if none is provided.
|
||||||
|
*/
|
||||||
|
if (idx < argc) {
|
||||||
|
/* got one or more */
|
||||||
|
NState_SetFilespecPointer(pState, &argv[idx]);
|
||||||
|
NState_SetFilespecCount(pState, argc - idx);
|
||||||
|
} else {
|
||||||
|
assert(idx == argc);
|
||||||
|
if (IsFilespecRequired(NState_GetCommand(pState))) {
|
||||||
|
fprintf(stderr, "%s: This command requires a list of files\n",
|
||||||
|
gProgName);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
NState_SetFilespecPointer(pState, nil);
|
||||||
|
NState_SetFilespecCount(pState, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_VERBOSE
|
||||||
|
NState_DebugDump(pState);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: (invoke without arguments to see usage information)\n",
|
||||||
|
gProgName);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have all of the parsed command line options in "pState". Now we just
|
||||||
|
* have to do something useful with it.
|
||||||
|
*
|
||||||
|
* Returns 0 on success, 1 on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
DoWork(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
|
||||||
|
switch (NState_GetCommand(pState)) {
|
||||||
|
case kCommandAdd:
|
||||||
|
err = DoAdd(pState);
|
||||||
|
break;
|
||||||
|
case kCommandExtract:
|
||||||
|
err = DoExtract(pState);
|
||||||
|
break;
|
||||||
|
case kCommandExtractToPipe:
|
||||||
|
err = DoExtractToPipe(pState);
|
||||||
|
break;
|
||||||
|
case kCommandTest:
|
||||||
|
err = DoTest(pState);
|
||||||
|
break;
|
||||||
|
case kCommandListShort:
|
||||||
|
err = DoListShort(pState);
|
||||||
|
break;
|
||||||
|
case kCommandListVerbose:
|
||||||
|
err = DoListVerbose(pState);
|
||||||
|
break;
|
||||||
|
case kCommandListDebug:
|
||||||
|
err = DoListDebug(pState);
|
||||||
|
break;
|
||||||
|
case kCommandDelete:
|
||||||
|
err = DoDelete(pState);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "ERROR: unexpected command %d\n",
|
||||||
|
NState_GetCommand(pState));
|
||||||
|
err = kNuErrInternal;
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (err != kNuErrNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Entry point.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
NulibState* pState = nil;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
extern NuResult ErrorMessageHandler(NuArchive* pArchive,
|
||||||
|
void* vErrorMessage);
|
||||||
|
NuSetGlobalErrorMessageHandler(ErrorMessageHandler);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (NState_Init(&pState) != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: unable to initialize globals\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gProgName = GetProgName(pState, argv[0]);
|
||||||
|
|
||||||
|
if (ProcessOptions(pState, argc, argv) < 0) {
|
||||||
|
result = 2;
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NState_ExtraInit(pState) != kNuErrNone) {
|
||||||
|
fprintf(stderr, "ERROR: additional initialization failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = DoWork(pState);
|
||||||
|
if (result)
|
||||||
|
printf("Failed.\n");
|
||||||
|
|
||||||
|
bail:
|
||||||
|
NState_Free(pState);
|
||||||
|
exit(result);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
#
|
||||||
|
# Makefile for nulib2 stuff (should work with non-GNU make).
|
||||||
|
#
|
||||||
|
# You can use:
|
||||||
|
# make (builds nulib2 and checks for libnufx.a)
|
||||||
|
# make shared (builds nulib2 and checks for libnufx.so)
|
||||||
|
#
|
||||||
|
# This will try to link against the library in $(NUFXSRCDIR) first, then
|
||||||
|
# look for a copy in the standard system install location (usually
|
||||||
|
# /usr/local/lib).
|
||||||
|
#
|
||||||
|
# Note that this really wants to find $(NUFXLIB) for dependency checking.
|
||||||
|
# If you're building against a copy in /usr/local/lib, just put a '#' in
|
||||||
|
# front of the "NUFXLIB" line below.
|
||||||
|
#
|
||||||
|
|
||||||
|
# set this to where the NuFX library and ".h" file live
|
||||||
|
VERSION = 100
|
||||||
|
NUFXSRCDIR = ../nufxlib-$(VERSION)
|
||||||
|
NUFXLIB = $(NUFXSRCDIR)/$(LIB_PRODUCT)
|
||||||
|
|
||||||
|
# NuLib2 install location. The man page will go into $(mandir)/man1.
|
||||||
|
prefix = @prefix@
|
||||||
|
exec_prefix = @exec_prefix@
|
||||||
|
includedir = @includedir@
|
||||||
|
libdir = @libdir@
|
||||||
|
bindir = @bindir@
|
||||||
|
mandir = @mandir@
|
||||||
|
srcdir = @srcdir@
|
||||||
|
|
||||||
|
SHELL = @SHELL@
|
||||||
|
INSTALL = @INSTALL@
|
||||||
|
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||||
|
INSTALL_DATA = @INSTALL_DATA@
|
||||||
|
CC = @CC@
|
||||||
|
#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$(NUFXSRCDIR) -I$(includedir) @DEFS@
|
||||||
|
|
||||||
|
#ifdef PURIFY_BUILD
|
||||||
|
# PURIFY = purify
|
||||||
|
# CFLAGS += -DPURIFY
|
||||||
|
#endif
|
||||||
|
#ifdef QUANTIFY_BUILD
|
||||||
|
# QUANTIFY = quantify
|
||||||
|
# CFLAGS += -DQUANTIFY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SRCS = Add.c ArcUtils.c Delete.c Extract.c Filename.c List.c Main.c \
|
||||||
|
MiscStuff.c MiscUtils.c State.c SysUtils.c
|
||||||
|
OBJS = Add.o ArcUtils.o Delete.o Extract.o Filename.o List.o Main.o \
|
||||||
|
MiscStuff.o MiscUtils.o State.o SysUtils.o
|
||||||
|
|
||||||
|
PRODUCT = nulib2
|
||||||
|
|
||||||
|
# this is used for dependency checking
|
||||||
|
LIB_PRODUCT = libnufx.a
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build stuff
|
||||||
|
#
|
||||||
|
|
||||||
|
all: $(PRODUCT)
|
||||||
|
@true
|
||||||
|
|
||||||
|
install: $(PRODUCT)
|
||||||
|
$(srcdir)/mkinstalldirs $(bindir)
|
||||||
|
$(INSTALL_PROGRAM) $(PRODUCT) $(bindir)
|
||||||
|
$(srcdir)/mkinstalldirs $(mandir)/man1
|
||||||
|
$(INSTALL_DATA) nulib2.1 $(mandir)/man1/
|
||||||
|
|
||||||
|
install-shared:
|
||||||
|
LIB_PRODUCT="libnufx.so" $(MAKE) -e install
|
||||||
|
|
||||||
|
# Link against the shared version of libnufx. This is only needed so
|
||||||
|
# the dependency checking does the right thing.
|
||||||
|
shared::
|
||||||
|
LIB_PRODUCT="libnufx.so" $(MAKE) -e
|
||||||
|
|
||||||
|
quantify:
|
||||||
|
-rm -f $(PRODUCT)
|
||||||
|
@$(MAKE) QUANTIFY_BUILD=1
|
||||||
|
|
||||||
|
purify:
|
||||||
|
-rm -f $(PRODUCT)
|
||||||
|
@$(MAKE) PURIFY_BUILD=1
|
||||||
|
|
||||||
|
$(PRODUCT): $(OBJS) $(NUFXLIB)
|
||||||
|
$(PURIFY) $(QUANTIFY) $(CC) -o $@ $(OBJS) -L$(NUFXSRCDIR) -L$(libdir) -lnufx @DMALLOC@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -f *.o core
|
||||||
|
-rm -f $(PRODUCT)
|
||||||
|
|
||||||
|
tags::
|
||||||
|
ctags -R --totals * $(NUFXSRCDIR)/*
|
||||||
|
@#ctags *.[ch] $(NUFXSRCDIR)/*.[ch]
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
-rm -f Makefile Makefile.bak
|
||||||
|
-rm -f config.log config.cache config.status config.h
|
||||||
|
-rm -f tags
|
||||||
|
-rm -f nulib2-$(VERSION)-@host_alias@ nulib2-$(VERSION)-@host_alias@.tar.Z
|
||||||
|
|
||||||
|
# Copy all of the binaries into a directory and tar them up for distribution.
|
||||||
|
# All binaries except "nulib2" are stripped to reduce their size.
|
||||||
|
distbin: $(PRODUCT)
|
||||||
|
@ \
|
||||||
|
builddir="nulib2-$(VERSION)-@host_alias@"; \
|
||||||
|
samples=$(NUFXSRCDIR)/samples; \
|
||||||
|
echo "building $$builddir.tar.Z"; \
|
||||||
|
rm -rf $$builddir; \
|
||||||
|
mkdir -p $$builddir; \
|
||||||
|
cp -p $(PRODUCT) nulib2.1 README.txt COPYING $$samples/README-S.txt \
|
||||||
|
$$samples/exerciser $$samples/imgconv $$samples/launder \
|
||||||
|
$$samples/test-basic $$samples/test-extract \
|
||||||
|
$$samples/test-simple \
|
||||||
|
$$builddir; \
|
||||||
|
strip $$builddir/$(PRODUCT) \
|
||||||
|
$$builddir/exerciser $$builddir/imgconv $$builddir/launder \
|
||||||
|
$$builddir/test-basic $$builddir/test-extract \
|
||||||
|
$$builddir/test-simple; \
|
||||||
|
tar -cf - $$builddir | compress > $$builddir.tar.Z; \
|
||||||
|
rm -rf $$builddir
|
||||||
|
|
||||||
|
baktar:
|
||||||
|
@tar cvf nulib2.tar *.txt COPYING INSTALL nulib2.1 configure *.in Makefile \
|
||||||
|
Makefile.msc install-sh config.guess config.sub mkinstalldirs *.[ch]
|
||||||
|
@gzip -9 nulib2.tar
|
||||||
|
@mv -i nulib2.tar.gz /home/fadden/BAK/
|
||||||
|
|
||||||
|
depend:
|
||||||
|
makedepend -- $(CFLAGS) -- $(SRCS)
|
||||||
|
|
||||||
|
# DO NOT DELETE THIS LINE -- make depend depends on it.
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
#
|
||||||
|
# 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 "..\nufxlib-$(VERSION)".
|
||||||
|
#
|
||||||
|
# To build without debugging info, use "nmake nodebug=1".
|
||||||
|
#
|
||||||
|
|
||||||
|
# Windows magic
|
||||||
|
TARGETOS = BOTH
|
||||||
|
!include <ntwin32.mak>
|
||||||
|
|
||||||
|
VERSION=100
|
||||||
|
NUFXSRCDIR = ..\nufxlib-$(VERSION)
|
||||||
|
|
||||||
|
# object files
|
||||||
|
OBJS1 = Add.obj ArcUtils.obj Delete.obj Extract.obj Filename.obj List.obj
|
||||||
|
OBJS2 = Main.obj MiscStuff.obj MiscUtils.obj State.obj SysUtils.obj
|
||||||
|
OBJS = $(OBJS1) $(OBJS2)
|
||||||
|
|
||||||
|
!ifdef NODEBUG
|
||||||
|
#OPT = $(cdebug) /D NDEBUG /ML
|
||||||
|
OPT = $(cdebug) /ML
|
||||||
|
LIB_FLAGS = /nodefaultlib:libcd.lib /nologo libc.lib setargv.obj
|
||||||
|
!else
|
||||||
|
OPT = $(cdebug) /MLd
|
||||||
|
LIB_FLAGS = /nodefaultlib:libc.lib /nologo libcd.lib setargv.obj
|
||||||
|
!endif
|
||||||
|
|
||||||
|
BUILD_FLAGS = /W3 /GX /D "WIN32" /D "_CONSOLE" /I "$(NUFXSRCDIR)"
|
||||||
|
|
||||||
|
# how to compile sources
|
||||||
|
.c.obj:
|
||||||
|
@$(cc) $(cdebug) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $<
|
||||||
|
|
||||||
|
|
||||||
|
PRODUCT = nulib2.exe
|
||||||
|
|
||||||
|
all: $(PRODUCT)
|
||||||
|
|
||||||
|
nulib2.exe: $(OBJS) $(NUFXSRCDIR)\nufxlib.lib
|
||||||
|
$(link) $(ldebug) $** -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
del *.obj
|
||||||
|
del $(PRODUCT)
|
||||||
|
|
||||||
|
Add.obj: Add.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
ArcUtils.obj: ArcUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
Delete.obj: Delete.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
Extract.obj: Extract.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
Filename.obj: Filename.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
List.obj: List.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
Main.obj: Main.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
MiscStuff.obj: MiscStuff.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
MiscUtils.obj: MiscUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
State.obj: State.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
SysUtils.obj: SysUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 <ctype.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public 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 __MiscStuff__
|
||||||
|
#define __MiscStuff__
|
||||||
|
|
||||||
|
#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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#define nil NULL /* I can't seem to stop typing 'nil' now */
|
||||||
|
|
||||||
|
typedef uchar 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) ( ((uint)(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)
|
||||||
|
#define DebugFill(addr, len) memset(addr, 0xa3, len)
|
||||||
|
#else
|
||||||
|
#define DebugFill(addr, len) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DebugAbort() abort()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define kInvalidFill (0xa3)
|
||||||
|
#define kInvalidPtr ((void*)0xa3a3a3a3)
|
||||||
|
|
||||||
|
#endif /*__MiscStuff__*/
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Misc support functions.
|
||||||
|
*/
|
||||||
|
#define __MiscUtils_c__
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to perror(), but takes the error as an argument, and knows
|
||||||
|
* about NufxLib errors as well as system errors.
|
||||||
|
*
|
||||||
|
* If "format" is nil, just the error message itself is printed.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ReportError(NuError err, const char* format, ...)
|
||||||
|
{
|
||||||
|
const char* msg;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
assert(format != nil);
|
||||||
|
|
||||||
|
va_start(args, format);
|
||||||
|
|
||||||
|
/* print the message, if any */
|
||||||
|
if (format != nil) {
|
||||||
|
fprintf(stderr, "%s: ERROR: ", gProgName);
|
||||||
|
vfprintf(stderr, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* print the error code data, if any */
|
||||||
|
if (err == kNuErrNone)
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
else {
|
||||||
|
if (format != nil)
|
||||||
|
fprintf(stderr, ": ");
|
||||||
|
|
||||||
|
msg = nil;
|
||||||
|
if (err >= 0)
|
||||||
|
msg = strerror(err);
|
||||||
|
if (msg == nil)
|
||||||
|
msg = NuStrError(err);
|
||||||
|
|
||||||
|
if (msg == nil)
|
||||||
|
fprintf(stderr, "(unknown err=%d)\n", err);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Memory allocation wrappers.
|
||||||
|
*
|
||||||
|
* Under gcc these would be macros, but not all compilers can handle that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USE_DMALLOC
|
||||||
|
void*
|
||||||
|
Malloc(size_t size)
|
||||||
|
{
|
||||||
|
void* _result;
|
||||||
|
|
||||||
|
Assert(size > 0);
|
||||||
|
_result = malloc(size);
|
||||||
|
if (_result == nil) {
|
||||||
|
ReportError(kNuErrMalloc, "malloc(%u) failed", (uint) size);
|
||||||
|
DebugAbort(); /* leave a core dump if we're built for it */
|
||||||
|
}
|
||||||
|
DebugFill(_result, size);
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
Calloc(size_t size)
|
||||||
|
{
|
||||||
|
void* _cresult = Malloc(size);
|
||||||
|
memset(_cresult, 0, size);
|
||||||
|
return _cresult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
Realloc(void* ptr, size_t size)
|
||||||
|
{
|
||||||
|
void* _result;
|
||||||
|
|
||||||
|
Assert(ptr != nil); /* disallow this usage */
|
||||||
|
Assert(size > 0); /* disallow this usage */
|
||||||
|
_result = realloc(ptr, size);
|
||||||
|
if (_result == nil) {
|
||||||
|
ReportError(kNuErrMalloc, "realloc(%u) failed", (uint) size);
|
||||||
|
DebugAbort(); /* leave a core dump if we're built for it */
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Free(void* ptr)
|
||||||
|
{
|
||||||
|
if (ptr != nil)
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*/
|
||||||
|
#ifndef __Nulib2__
|
||||||
|
#define __Nulib2__
|
||||||
|
|
||||||
|
#include "SysDefs.h" /* system-dependent defs; must come first */
|
||||||
|
#include <NufxLib.h>
|
||||||
|
#include "State.h"
|
||||||
|
#include "MiscStuff.h"
|
||||||
|
|
||||||
|
#ifdef USE_DMALLOC
|
||||||
|
/* enable with something like "dmalloc -l logfile -i 100 medium" */
|
||||||
|
# include "dmalloc.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* replace unsupported chars with '%xx' */
|
||||||
|
#define kForeignIndic '%'
|
||||||
|
|
||||||
|
/* make our one-line comments this big */
|
||||||
|
#define kDefaultCommentLen 200
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Add.c */
|
||||||
|
NuError DoAdd(NulibState* pState);
|
||||||
|
|
||||||
|
/* ArcUtils.c */
|
||||||
|
char* GetSimpleComment(NulibState* pState, const char* pathname, int maxLen);
|
||||||
|
Boolean IsFilenameStdin(const char* archiveName);
|
||||||
|
Boolean IsSpecified(NulibState* pState, const NuRecord* pRecord);
|
||||||
|
NuError OpenArchiveReadOnly(NulibState* pState);
|
||||||
|
NuError OpenArchiveReadWrite(NulibState* pState);
|
||||||
|
const NuThread* GetThread(const NuRecord* pRecord, ulong idx);
|
||||||
|
Boolean IsRecordReadOnly(const NuRecord* pRecord);
|
||||||
|
|
||||||
|
/* Delete.c */
|
||||||
|
NuError DoDelete(NulibState* pState);
|
||||||
|
|
||||||
|
/* Extract.c */
|
||||||
|
NuError DoExtract(NulibState* pState);
|
||||||
|
NuError DoExtractToPipe(NulibState* pState);
|
||||||
|
NuError DoTest(NulibState* pState);
|
||||||
|
|
||||||
|
/* Filename.c */
|
||||||
|
const char* GetFileTypeString(ulong fileType);
|
||||||
|
const char* NormalizePath(NulibState* pState, NuPathnameProposal* pathProposal);
|
||||||
|
void InterpretExtension(NulibState* pState, const char* pathName,
|
||||||
|
ulong* pFileType, ulong* pAuxType);
|
||||||
|
Boolean ExtractPreservationString(NulibState* pState, char* pathname,
|
||||||
|
ulong* pFileType, ulong* pAuxType, NuThreadID* pThreadID);
|
||||||
|
void DenormalizePath(NulibState* pState, char* pathBuf);
|
||||||
|
const char* FilenameOnly(NulibState* pState, const char* pathname);
|
||||||
|
const char* FindExtension(NulibState* pState, const char* pathname);
|
||||||
|
|
||||||
|
/* List.c */
|
||||||
|
NuError DoListShort(NulibState* pState);
|
||||||
|
NuError DoListVerbose(NulibState* pState);
|
||||||
|
NuError DoListDebug(NulibState* pState);
|
||||||
|
|
||||||
|
/* Main.c */
|
||||||
|
extern const char* gProgName;
|
||||||
|
|
||||||
|
/* MiscUtils.c */
|
||||||
|
void ReportError(NuError err, const char* format, ...)
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
__attribute__ ((format(printf, 2, 3)))
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
#ifdef USE_DMALLOC /* want file and line numbers for calls */
|
||||||
|
# define Malloc(size) malloc(size)
|
||||||
|
# define Calloc(size) calloc(1, size)
|
||||||
|
# define Realloc(ptr, size) realloc(ptr, size)
|
||||||
|
# define Free(ptr) (ptr != nil ? free(ptr) : (void)0)
|
||||||
|
#else
|
||||||
|
void* Malloc(size_t size);
|
||||||
|
void* Calloc(size_t size);
|
||||||
|
void* Realloc(void* ptr, size_t size);
|
||||||
|
void Free(void* ptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* SysUtils.c */
|
||||||
|
NuError NormalizeFileName(NulibState* pState, const char* srcp, long srcLen,
|
||||||
|
char fssep, char** pDstp, long dstLen);
|
||||||
|
NuError NormalizeDirectoryName(NulibState* pState, const char* srcp,
|
||||||
|
long srcLen, char fssep, char** pDstp, long dstLen);
|
||||||
|
char* MakeTempArchiveName(NulibState* pState);
|
||||||
|
NuError AddFile(NulibState* pState, NuArchive* pArchive,
|
||||||
|
const char* pathname);
|
||||||
|
|
||||||
|
#endif /*__Nulib2__*/
|
|
@ -0,0 +1,122 @@
|
||||||
|
NuLib2 README, updated 2000/05/18
|
||||||
|
http://www.nulib.com/
|
||||||
|
|
||||||
|
See "COPYING" for distribution restrictions.
|
||||||
|
|
||||||
|
To build NuLib2, you will also need a copy of NufxLib. This may have come
|
||||||
|
in the same .tar.gz file. Build the library first.
|
||||||
|
|
||||||
|
|
||||||
|
UNIX
|
||||||
|
====
|
||||||
|
|
||||||
|
Make sure that the "NUFXSRCDIR" define in Makefile.in points to the correct
|
||||||
|
directory, or that the library has been installed in a standard location
|
||||||
|
such as /usr/local/lib/. If you received NuLib2 and NufxLib in a single
|
||||||
|
".tar.gz" file, the variable is already set correctly. The makefile will
|
||||||
|
look in $(NUFXSRCDIR) first, /usr/local/lib second.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Run "make depend" if you have makedepend, and then type "make".
|
||||||
|
This should leave you with an executable called "nulib2". If you like,
|
||||||
|
"make install" will put things into your install directory, usually
|
||||||
|
/usr/local/bin/ and /usr/local/man/.
|
||||||
|
|
||||||
|
You may want to fiddle with the "OPT" setting in Makefile to enable or
|
||||||
|
disable optimizations and assertions. Because almost all of the hard
|
||||||
|
work is done by NufxLib, turning compiler optimizations on in NuLib2 has
|
||||||
|
little impact on performance.
|
||||||
|
|
||||||
|
|
||||||
|
A man page for nulib2 is in "nulib2.1", which you can format for viewing
|
||||||
|
with "nroff -man nulib2.1". A full manual for NuLib2 is available from
|
||||||
|
the www.nulib.com web site.
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
Win32
|
||||||
|
=====
|
||||||
|
|
||||||
|
If you're using an environment that supports "configure" scripts, such as
|
||||||
|
DJGPP, follow the UNIX instructions.
|
||||||
|
|
||||||
|
NuLib2 has been tested with Microsoft Visual C++ 6.0. To build NuLib2,
|
||||||
|
start up a DOS shell and run vcvars32.bat to set your environment. Run:
|
||||||
|
nmake -f makefile.msc
|
||||||
|
to build with debugging info, or
|
||||||
|
nmake -f makefile.msc nodebug=1
|
||||||
|
to build optimized.
|
||||||
|
|
||||||
|
|
||||||
|
Other Notes
|
||||||
|
===========
|
||||||
|
|
||||||
|
All of the source code was formatted with four-space hard tabs.
|
||||||
|
|
||||||
|
|
||||||
|
Fun benchmark of the day:
|
||||||
|
|
||||||
|
Time to compress 1525 files, totaling 19942152 bytes, on an Apple IIgs
|
||||||
|
with an 8MHz ZipGS accelerator and Apple HS SCSI card, running System
|
||||||
|
6.0.1, from a 20MB ProDOS partition to a 13.9MB archive on an HFS volume,
|
||||||
|
with GS/ShrinkIt 1.1: about 40 minutes.
|
||||||
|
|
||||||
|
Time to compress the same files, on a 128MB 500MHz Pentium-III running
|
||||||
|
Red Hat Linux 6.0, with NuLib2 v0.3: about six seconds.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Here's a nifty way to evaluate GSHK vs NuLib2 (as well as Linux NuLib2
|
||||||
|
vs Win32 NuLib2):
|
||||||
|
|
||||||
|
- Archive a whole bunch of files from a ProDOS volume with GS/ShrinkIt.
|
||||||
|
I used a 20MB partition, which resulted in a 14MB archive. Transfer
|
||||||
|
the archive to a machine running NuLib2 (perhaps a Linux system).
|
||||||
|
- Create a new subdirectory, cd into it, and extract the entire archive
|
||||||
|
with "nulib2 xe ../foo.shk".
|
||||||
|
- Now create a new archive with all of the files, using
|
||||||
|
"nulib2 aer ../new.shk *".
|
||||||
|
- Change back to the directory above, and use "nulib2 v" to see what's
|
||||||
|
in them, e.g. "nulib2 v foo.shk > out.orig" and
|
||||||
|
"nulib2 v new.shk > out.new".
|
||||||
|
- Edit both of the "out" files with vi. Do a global search-and-replace
|
||||||
|
to change '/' to ':' in out.new (:%s/\//:/g) so the filename separator
|
||||||
|
doesn't mess up the comparison.
|
||||||
|
- Do a global search-and-replace in both files to set the file dates
|
||||||
|
to be the same. I used ":%s/..-...-.. ..:../01-Jan-00 00:00/". This
|
||||||
|
is necessary because, like ShrinkIt, NuLib displays the date on which
|
||||||
|
the files were archived, not when they were last modified.
|
||||||
|
- Sort both files, with ":%!sort". This is necessary because you
|
||||||
|
added the files with '*' up above, so the NuLib2-created archive
|
||||||
|
has the top-level files alphabetized.
|
||||||
|
- Quit out of vi. Diff the two files.
|
||||||
|
|
||||||
|
I did this for a 20MB hard drive partition with 1500 files on it.
|
||||||
|
The only discrepancies (accounting for a total difference of 116 bytes)
|
||||||
|
were a zero-byte "Kangaroo.data" file that GSHK stored improperly and some
|
||||||
|
semi-random GSHK behavior that I can't mimic. When the "Mimic ShrinkIt"
|
||||||
|
flag is disabled, the resulting archive is 13K smaller.
|
||||||
|
|
||||||
|
|
||||||
|
The largest archive I've tried had 4700 files for a total of 76MB
|
||||||
|
(compressed down to 47MB), and contained the entire contents of four 20MB
|
||||||
|
ProDOS hard drive partitions. NuLib2 under Linux handled it without
|
||||||
|
breaking a sweat.
|
||||||
|
|
|
@ -0,0 +1,499 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* Global-ish state object.
|
||||||
|
*/
|
||||||
|
#include "Nulib2.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char* gProgramVersion = "1.0.0";
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate and initialize the semi-global Nulib2 state object.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
NState_Init(NulibState** ppState)
|
||||||
|
{
|
||||||
|
assert(ppState != nil);
|
||||||
|
|
||||||
|
*ppState = Calloc(sizeof(**ppState));
|
||||||
|
if (*ppState == nil)
|
||||||
|
return kNuErrMalloc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the contents to default values.
|
||||||
|
*/
|
||||||
|
(*ppState)->systemPathSeparator = PATH_SEP;
|
||||||
|
(*ppState)->programVersion = gProgramVersion;
|
||||||
|
|
||||||
|
return kNuErrNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A little extra initialization, performed after arguments are parsed.
|
||||||
|
*/
|
||||||
|
NuError
|
||||||
|
NState_ExtraInit(NulibState* pState)
|
||||||
|
{
|
||||||
|
NuError err;
|
||||||
|
NuValue convertEOL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a data sink for "stdout", in case we use the "-p" command.
|
||||||
|
* Set the EOL conversion according to the "-l" modifier.
|
||||||
|
*/
|
||||||
|
convertEOL = kNuConvertOff;
|
||||||
|
if (pState->modConvertText)
|
||||||
|
convertEOL = kNuConvertAuto;
|
||||||
|
if (pState->modConvertAll)
|
||||||
|
convertEOL = kNuConvertOn;
|
||||||
|
|
||||||
|
err = NuCreateDataSinkForFP(true, convertEOL, stdout, &pState->pPipeSink);
|
||||||
|
if (err != kNuErrNone)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a data sink for "stdout", in case we use the "-c" modifier.
|
||||||
|
* The EOL conversion is always on.
|
||||||
|
*/
|
||||||
|
err = NuCreateDataSinkForFP(true, kNuConvertOn, stdout,
|
||||||
|
&pState->pCommentSink);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free up the state structure and its contents.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
NState_Free(NulibState* pState)
|
||||||
|
{
|
||||||
|
if (pState == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Free(pState->renameToStr); /* ?? */
|
||||||
|
Free(pState->tempPathnameBuf);
|
||||||
|
if (pState->pPipeSink != nil)
|
||||||
|
NuFreeDataSink(pState->pPipeSink);
|
||||||
|
if (pState->pCommentSink != nil)
|
||||||
|
NuFreeDataSink(pState->pCommentSink);
|
||||||
|
Free(pState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_MSGS
|
||||||
|
void
|
||||||
|
NState_DebugDump(const NulibState* pState)
|
||||||
|
{
|
||||||
|
/* this will break when the code changes, but it's just for debugging */
|
||||||
|
static const char* kCommandNames[] = {
|
||||||
|
"<unknown>",
|
||||||
|
"add",
|
||||||
|
"delete",
|
||||||
|
"extract",
|
||||||
|
"extractToPipe",
|
||||||
|
"listShort",
|
||||||
|
"listVerbose",
|
||||||
|
"listDebug",
|
||||||
|
"test",
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(pState != nil);
|
||||||
|
|
||||||
|
printf("NState:\n");
|
||||||
|
printf(" programVersion: '%s'\n", pState->programVersion);
|
||||||
|
printf(" systemPathSeparator: '%c'\n", pState->systemPathSeparator);
|
||||||
|
printf(" archiveFilename: '%s'\n", pState->archiveFilename);
|
||||||
|
printf(" filespec: %ld (%s ...)\n", pState->filespecCount,
|
||||||
|
!pState->filespecCount ? "<none>" : *pState->filespecPointer);
|
||||||
|
|
||||||
|
printf(" command: %d (%s); modifiers:\n", pState->command,
|
||||||
|
kCommandNames[pState->command]);
|
||||||
|
if (pState->modUpdate)
|
||||||
|
printf(" update\n");
|
||||||
|
if (pState->modFreshen)
|
||||||
|
printf(" freshen\n");
|
||||||
|
if (pState->modRecurse)
|
||||||
|
printf(" recurse\n");
|
||||||
|
if (pState->modJunkPaths)
|
||||||
|
printf(" junkPaths\n");
|
||||||
|
if (pState->modNoCompression)
|
||||||
|
printf(" noCompression\n");
|
||||||
|
if (pState->modComments)
|
||||||
|
printf(" comments\n");
|
||||||
|
if (pState->modConvertText)
|
||||||
|
printf(" convertText\n");
|
||||||
|
if (pState->modConvertAll)
|
||||||
|
printf(" convertAll\n");
|
||||||
|
if (pState->modOverwriteExisting)
|
||||||
|
printf(" overwriteExisting\n");
|
||||||
|
if (pState->modPreserveType)
|
||||||
|
printf(" preserveType\n");
|
||||||
|
if (pState->modPreserveTypeExtended)
|
||||||
|
printf(" preserveTypeExtended\n");
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================================
|
||||||
|
* Simple set/get functions
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
char
|
||||||
|
NState_GetSystemPathSeparator(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->systemPathSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
NState_GetProgramVersion(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->programVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuArchive*
|
||||||
|
NState_GetNuArchive(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->pArchive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetNuArchive(NulibState* pState, NuArchive* pArchive)
|
||||||
|
{
|
||||||
|
pState->pArchive = pArchive;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetSuppressOutput(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->suppressOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetSuppressOutput(NulibState* pState, Boolean doSuppress)
|
||||||
|
{
|
||||||
|
pState->suppressOutput = doSuppress;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetInputUnavailable(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->inputUnavailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetInputUnavailable(NulibState* pState, Boolean isUnavailable)
|
||||||
|
{
|
||||||
|
pState->inputUnavailable = isUnavailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuRecordIdx
|
||||||
|
NState_GetRenameFromIdx(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->renameFromIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetRenameFromIdx(NulibState* pState, NuRecordIdx recordIdx)
|
||||||
|
{
|
||||||
|
pState->renameFromIdx = recordIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
char*
|
||||||
|
NState_GetRenameToStr(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->renameToStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetRenameToStr(NulibState* pState, char* str)
|
||||||
|
{
|
||||||
|
Free(pState->renameToStr);
|
||||||
|
pState->renameToStr = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NuDataSink*
|
||||||
|
NState_GetPipeSink(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->pPipeSink;
|
||||||
|
}
|
||||||
|
|
||||||
|
NuDataSink*
|
||||||
|
NState_GetCommentSink(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->pCommentSink;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
NState_GetMatchCount(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->matchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetMatchCount(NulibState* pState, long count)
|
||||||
|
{
|
||||||
|
pState->matchCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_IncMatchCount(NulibState* pState)
|
||||||
|
{
|
||||||
|
pState->matchCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_AddToTotals(NulibState* pState, long len, long compLen)
|
||||||
|
{
|
||||||
|
pState->totalLen += len;
|
||||||
|
pState->totalCompLen += compLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_GetTotals(NulibState* pState, long* pTotalLen, long* pTotalCompLen)
|
||||||
|
{
|
||||||
|
*pTotalLen = pState->totalLen;
|
||||||
|
*pTotalCompLen = pState->totalCompLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
NState_GetTempPathnameLen(NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->tempPathnameAlloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetTempPathnameLen(NulibState* pState, long len)
|
||||||
|
{
|
||||||
|
char* newBuf;
|
||||||
|
|
||||||
|
len++; /* add one for the '\0' */
|
||||||
|
|
||||||
|
if (pState->tempPathnameAlloc < len) {
|
||||||
|
if (pState->tempPathnameBuf == nil)
|
||||||
|
newBuf = Malloc(len);
|
||||||
|
else
|
||||||
|
newBuf = Realloc(pState->tempPathnameBuf, len);
|
||||||
|
assert(newBuf != nil);
|
||||||
|
if (newBuf == nil) {
|
||||||
|
Free(pState->tempPathnameBuf);
|
||||||
|
pState->tempPathnameBuf = nil;
|
||||||
|
pState->tempPathnameAlloc = 0;
|
||||||
|
ReportError(kNuErrMalloc, "buf realloc failed (%ld)", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pState->tempPathnameBuf = newBuf;
|
||||||
|
pState->tempPathnameAlloc = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char*
|
||||||
|
NState_GetTempPathnameBuf(NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->tempPathnameBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Command
|
||||||
|
NState_GetCommand(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->command;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetCommand(NulibState* pState, Command cmd)
|
||||||
|
{
|
||||||
|
pState->command = cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
NState_GetArchiveFilename(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->archiveFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetArchiveFilename(NulibState* pState, const char* archiveFilename)
|
||||||
|
{
|
||||||
|
pState->archiveFilename = archiveFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* const*
|
||||||
|
NState_GetFilespecPointer(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->filespecPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetFilespecPointer(NulibState* pState, char* const* filespecPointer)
|
||||||
|
{
|
||||||
|
pState->filespecPointer = filespecPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
NState_GetFilespecCount(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->filespecCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetFilespecCount(NulibState* pState, long filespecCount)
|
||||||
|
{
|
||||||
|
pState->filespecCount = filespecCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModUpdate(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModUpdate(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modUpdate = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModFreshen(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modFreshen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModFreshen(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modFreshen = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModRecurse(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modRecurse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModRecurse(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modRecurse = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModJunkPaths(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modJunkPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModJunkPaths(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modJunkPaths = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModNoCompression(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modNoCompression;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModNoCompression(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modNoCompression = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModComments(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModComments(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modComments = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModConvertText(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modConvertText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModConvertText(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modConvertText = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModConvertAll(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modConvertAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModConvertAll(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modConvertAll = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModOverwriteExisting(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modOverwriteExisting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModOverwriteExisting(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modOverwriteExisting = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModAddAsDisk(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modAddAsDisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModAddAsDisk(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modAddAsDisk = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModPreserveType(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modPreserveType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModPreserveType(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modPreserveType = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean
|
||||||
|
NState_GetModPreserveTypeExtended(const NulibState* pState)
|
||||||
|
{
|
||||||
|
return pState->modPreserveTypeExtended;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NState_SetModPreserveTypeExtended(NulibState* pState, Boolean val)
|
||||||
|
{
|
||||||
|
pState->modPreserveTypeExtended = val;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*/
|
||||||
|
#ifndef __State__
|
||||||
|
#define __State__
|
||||||
|
|
||||||
|
#include "MiscStuff.h"
|
||||||
|
#include "NufxLib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Things you can tell Nulib2 to do.
|
||||||
|
*
|
||||||
|
* (Some debug code in NState_DebugDump() is sensitive to the order here.)
|
||||||
|
*/
|
||||||
|
typedef enum Command {
|
||||||
|
kCommandUnknown = 0,
|
||||||
|
kCommandAdd,
|
||||||
|
kCommandDelete,
|
||||||
|
kCommandExtract,
|
||||||
|
kCommandExtractToPipe,
|
||||||
|
kCommandListShort,
|
||||||
|
kCommandListVerbose,
|
||||||
|
kCommandListDebug,
|
||||||
|
kCommandTest
|
||||||
|
} Command;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Program-wide state.
|
||||||
|
*/
|
||||||
|
typedef struct NulibState {
|
||||||
|
/* global goodness */
|
||||||
|
const char* programVersion;
|
||||||
|
|
||||||
|
/* system-specific values */
|
||||||
|
char systemPathSeparator;
|
||||||
|
|
||||||
|
/* pointer to archive we're working with */
|
||||||
|
NuArchive* pArchive;
|
||||||
|
|
||||||
|
/* misc state */
|
||||||
|
Boolean suppressOutput;
|
||||||
|
Boolean inputUnavailable;
|
||||||
|
NuRecordIdx renameFromIdx;
|
||||||
|
char* renameToStr;
|
||||||
|
NuDataSink* pPipeSink;
|
||||||
|
NuDataSink* pCommentSink;
|
||||||
|
long matchCount;
|
||||||
|
long totalLen;
|
||||||
|
long totalCompLen;
|
||||||
|
|
||||||
|
/* temp storage */
|
||||||
|
long tempPathnameAlloc;
|
||||||
|
char* tempPathnameBuf;
|
||||||
|
|
||||||
|
/* command-line options */
|
||||||
|
Command command;
|
||||||
|
Boolean modUpdate;
|
||||||
|
Boolean modFreshen;
|
||||||
|
Boolean modRecurse;
|
||||||
|
Boolean modJunkPaths;
|
||||||
|
Boolean modNoCompression;
|
||||||
|
Boolean modComments;
|
||||||
|
Boolean modConvertText;
|
||||||
|
Boolean modConvertAll;
|
||||||
|
Boolean modOverwriteExisting;
|
||||||
|
Boolean modAddAsDisk;
|
||||||
|
Boolean modPreserveType;
|
||||||
|
Boolean modPreserveTypeExtended;
|
||||||
|
|
||||||
|
const char* archiveFilename;
|
||||||
|
char* const* filespecPointer;
|
||||||
|
long filespecCount;
|
||||||
|
} NulibState;
|
||||||
|
|
||||||
|
NuError NState_Init(NulibState** ppState);
|
||||||
|
NuError NState_ExtraInit(NulibState* pState);
|
||||||
|
void NState_Free(NulibState* pState);
|
||||||
|
#ifdef DEBUG_MSGS
|
||||||
|
void NState_DebugDump(const NulibState* pState);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char NState_GetSystemPathSeparator(const NulibState* pState);
|
||||||
|
const char* NState_GetProgramVersion(const NulibState* pState);
|
||||||
|
NuArchive* NState_GetNuArchive(const NulibState* pState);
|
||||||
|
void NState_SetNuArchive(NulibState* pState, NuArchive* pArchive);
|
||||||
|
|
||||||
|
Boolean NState_GetSuppressOutput(const NulibState* pState);
|
||||||
|
void NState_SetSuppressOutput(NulibState* pState, Boolean doSuppress);
|
||||||
|
Boolean NState_GetInputUnavailable(const NulibState* pState);
|
||||||
|
void NState_SetInputUnavailable(NulibState* pState, Boolean isUnavailable);
|
||||||
|
NuRecordIdx NState_GetRenameFromIdx(const NulibState* pState);
|
||||||
|
void NState_SetRenameFromIdx(NulibState* pState, NuRecordIdx recordIdx);
|
||||||
|
char* NState_GetRenameToStr(const NulibState* pState);
|
||||||
|
void NState_SetRenameToStr(NulibState* pState, char* str);
|
||||||
|
NuDataSink* NState_GetPipeSink(const NulibState* pState);
|
||||||
|
NuDataSink* NState_GetCommentSink(const NulibState* pState);
|
||||||
|
long NState_GetMatchCount(const NulibState* pState);
|
||||||
|
void NState_SetMatchCount(NulibState* pState, long count);
|
||||||
|
void NState_IncMatchCount(NulibState* pState);
|
||||||
|
void NState_AddToTotals(NulibState* pState, long len, long compLen);
|
||||||
|
void NState_GetTotals(NulibState* pState, long* pTotalLen, long* pTotalCompLen);
|
||||||
|
|
||||||
|
long NState_GetTempPathnameLen(NulibState* pState);
|
||||||
|
void NState_SetTempPathnameLen(NulibState* pState, long len);
|
||||||
|
char* NState_GetTempPathnameBuf(NulibState* pState);
|
||||||
|
|
||||||
|
Command NState_GetCommand(const NulibState* pState);
|
||||||
|
void NState_SetCommand(NulibState* pState, Command cmd);
|
||||||
|
const char* NState_GetArchiveFilename(const NulibState* pState);
|
||||||
|
void NState_SetArchiveFilename(NulibState* pState, const char* archiveFilename);
|
||||||
|
char* const* NState_GetFilespecPointer(const NulibState* pState);
|
||||||
|
void NState_SetFilespecPointer(NulibState* pState, char* const* filespec);
|
||||||
|
long NState_GetFilespecCount(const NulibState* pState);
|
||||||
|
void NState_SetFilespecCount(NulibState* pState, long filespecCount);
|
||||||
|
|
||||||
|
Boolean NState_GetModUpdate(const NulibState* pState);
|
||||||
|
void NState_SetModUpdate(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModFreshen(const NulibState* pState);
|
||||||
|
void NState_SetModFreshen(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModRecurse(const NulibState* pState);
|
||||||
|
void NState_SetModRecurse(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModJunkPaths(const NulibState* pState);
|
||||||
|
void NState_SetModJunkPaths(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModNoCompression(const NulibState* pState);
|
||||||
|
void NState_SetModNoCompression(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModComments(const NulibState* pState);
|
||||||
|
void NState_SetModComments(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModConvertText(const NulibState* pState);
|
||||||
|
void NState_SetModConvertText(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModConvertAll(const NulibState* pState);
|
||||||
|
void NState_SetModConvertAll(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModOverwriteExisting(const NulibState* pState);
|
||||||
|
void NState_SetModOverwriteExisting(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModAddAsDisk(const NulibState* pState);
|
||||||
|
void NState_SetModAddAsDisk(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModPreserveType(const NulibState* pState);
|
||||||
|
void NState_SetModPreserveType(NulibState* pState, Boolean val);
|
||||||
|
Boolean NState_GetModPreserveTypeExtended(const NulibState* pState);
|
||||||
|
void NState_SetModPreserveTypeExtended(NulibState* pState, Boolean val);
|
||||||
|
|
||||||
|
#endif /*__State__*/
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* NuFX archive manipulation library
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Library General Public License, see the file COPYING.LIB.
|
||||||
|
*
|
||||||
|
* This file was adapted from Devin Reade's "sunos4.h" in NuLib 3.2.5.
|
||||||
|
* It is provided for compilation under SunOS 4.x, when an ANSI compiler
|
||||||
|
* (such as gcc) is used. The system header files aren't quite sufficient
|
||||||
|
* to eliminate hordes of warnings.
|
||||||
|
*/
|
||||||
|
#ifndef __SunOS4__
|
||||||
|
#define __SunOS4__
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
extern int _flsbuf(int, FILE*);
|
||||||
|
extern int _filbuf(FILE*);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void bcopy(char*, char*, int);
|
||||||
|
extern int fclose(FILE*);
|
||||||
|
extern int fflush(FILE*);
|
||||||
|
extern int fprintf(FILE*, const char*, ...);
|
||||||
|
extern int fread(char*, int, int, FILE *);
|
||||||
|
extern int fseek(FILE*, long, int);
|
||||||
|
extern int ftruncate(int, off_t);
|
||||||
|
extern int fwrite(const char*, int, int, FILE*);
|
||||||
|
extern char* mktemp(char *template);
|
||||||
|
extern time_t mktime(struct tm*);
|
||||||
|
extern int perror(const char*);
|
||||||
|
extern int printf(const char*, ...);
|
||||||
|
extern int remove(const char*);
|
||||||
|
extern int rename(const char*, const char*);
|
||||||
|
extern int tolower(int);
|
||||||
|
extern int setvbuf(FILE*, char*, int, int);
|
||||||
|
extern int sscanf(char*, const char*, ...);
|
||||||
|
extern int strcasecmp(const char*, const char*);
|
||||||
|
extern int strncasecmp(const char*, const char*, size_t);
|
||||||
|
extern long strtol(const char *, char **, int);
|
||||||
|
extern int system(const char*);
|
||||||
|
extern time_t timelocal(struct tm*);
|
||||||
|
extern time_t time(time_t*);
|
||||||
|
extern int toupper(int);
|
||||||
|
extern int vfprintf(FILE*, const char *, va_list);
|
||||||
|
extern char* vsprintf(char *str, const char *format, va_list ap);
|
||||||
|
|
||||||
|
#endif /*__SunOS4__*/
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* Nulib2
|
||||||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||||||
|
* This is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License, see the file COPYING.
|
||||||
|
*
|
||||||
|
* This was adapted from gzip's "tailor.h" and NuLib's "nudefs.h".
|
||||||
|
*/
|
||||||
|
#ifndef __SysDefs__
|
||||||
|
#define __SysDefs__
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VERBOSE
|
||||||
|
# define DEBUG_MSGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* these should exist everywhere */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* 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 uchar unsigned char
|
||||||
|
# define ushort unsigned short
|
||||||
|
# define uint unsigned int
|
||||||
|
# define ulong unsigned long
|
||||||
|
# define inline /*Visual C++6.0 can't inline ".c" files*/
|
||||||
|
# define mode_t int
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include <io.h>
|
||||||
|
# define FOPEN_WANTS_B
|
||||||
|
# define HAVE_CHSIZE
|
||||||
|
# define snprintf _snprintf
|
||||||
|
# define vsnprintf _vsnprintf
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_MALLOC_H
|
||||||
|
# include <malloc.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_STDLIB_H
|
||||||
|
# include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_STAT_H
|
||||||
|
# include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_TIME_H
|
||||||
|
# include <sys/time.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
# include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WINDOWS_LIKE)
|
||||||
|
# ifndef F_OK
|
||||||
|
# define F_OK 02
|
||||||
|
# endif
|
||||||
|
# ifndef R_OK
|
||||||
|
# define R_OK 04
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__unix__) || defined(__unix) || defined(__BEOS__) || \
|
||||||
|
defined(__hpux) || defined(_AIX) || defined(__APPLE__)
|
||||||
|
# define UNIX_LIKE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__MSDOS__) && !defined(MSDOS)
|
||||||
|
# define MSDOS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__OS2__) && !defined(OS2)
|
||||||
|
# define OS2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(OS2) && defined(MSDOS) /* MS C under OS/2 */
|
||||||
|
# undef MSDOS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* this ought to get trimmed down */
|
||||||
|
#ifdef MSDOS
|
||||||
|
# ifndef __GNUC__
|
||||||
|
# ifdef __TURBOC__
|
||||||
|
# define NO_OFF_T
|
||||||
|
# ifdef __BORLANDC__
|
||||||
|
# define DIRENT
|
||||||
|
# else
|
||||||
|
# define NO_UTIME
|
||||||
|
# endif
|
||||||
|
# else /* MSC */
|
||||||
|
# define HAVE_SYS_UTIME_H
|
||||||
|
# define NO_UTIME_H
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
# define PATH_SEP '\\'
|
||||||
|
# define PATH_SEP2 '/'
|
||||||
|
# define PATH_SEP3 ':'
|
||||||
|
# ifdef MAX_PATH
|
||||||
|
# define MAX_PATH_LEN MAX_PATH
|
||||||
|
# else
|
||||||
|
# define MAX_PATH_LEN 128
|
||||||
|
# endif
|
||||||
|
# define NO_MULTIPLE_DOTS
|
||||||
|
# define MAX_EXT_CHARS 3
|
||||||
|
# define NO_CHOWN
|
||||||
|
# define PROTO
|
||||||
|
# ifndef HAVE_CONFIG_H
|
||||||
|
# define STDC_HEADERS
|
||||||
|
# endif
|
||||||
|
# define NO_SIZE_CHECK
|
||||||
|
# define SYSTEM_DEFAULT_EOL "\r\n"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef WIN32 /* Windows 95/98/NT */
|
||||||
|
# define HAVE_SYS_UTIME_H
|
||||||
|
# define NO_UTIME_H
|
||||||
|
# define PATH_SEP '\\'
|
||||||
|
# define PATH_SEP2 '/'
|
||||||
|
# define PATH_SEP3 ':'
|
||||||
|
# ifdef MAX_PATH
|
||||||
|
# define MAX_PATH_LEN MAX_PATH
|
||||||
|
# else
|
||||||
|
# define MAX_PATH_LEN 260
|
||||||
|
# endif
|
||||||
|
# define PROTO
|
||||||
|
# define STDC_HEADERS
|
||||||
|
# define SYSTEM_DEFAULT_EOL "\r\n"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MACOS
|
||||||
|
# define PATH_SEP ':'
|
||||||
|
# define NO_CHOWN
|
||||||
|
# define NO_UTIME
|
||||||
|
# define SYSTEM_DEFAULT_EOL "\r"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(APW) || defined(__ORCAC__)
|
||||||
|
# define __appleiigs__
|
||||||
|
# pragma lint -1
|
||||||
|
# pragma memorymodel 1
|
||||||
|
# pragma optimize 7
|
||||||
|
/*# pragma debug 25 */
|
||||||
|
# define PATH_SEP ':'
|
||||||
|
# define SYSTEM_DEFAULT_EOL "\r"
|
||||||
|
|
||||||
|
# ifdef GNO
|
||||||
|
# define HAS_DIRENT
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GNUC__ /* this was missing from BeOS __MWERKS__, and probably others */
|
||||||
|
# define HAS__FUNCTION__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__sun__) && !defined(__SVR4)
|
||||||
|
# include "SunOS4.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* general defaults, mainly for UNIX */
|
||||||
|
|
||||||
|
#ifndef PATH_SEP
|
||||||
|
# define PATH_SEP '/'
|
||||||
|
#endif
|
||||||
|
#ifndef SYSTEM_DEFAULT_EOL
|
||||||
|
# define SYSTEM_DEFAULT_EOL "\n"
|
||||||
|
#endif
|
||||||
|
#ifndef MAX_PATH_LEN
|
||||||
|
# define MAX_PATH_LEN 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /*__SysDefs__*/
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,109 @@
|
||||||
|
/* config.h.in. Generated automatically from configure.in by autoheader. */
|
||||||
|
|
||||||
|
/* Define to empty if the keyword does not work. */
|
||||||
|
#undef const
|
||||||
|
|
||||||
|
/* Define if utime(file, NULL) sets file's timestamp to the present. */
|
||||||
|
#undef HAVE_UTIME_NULL
|
||||||
|
|
||||||
|
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef mode_t
|
||||||
|
|
||||||
|
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef off_t
|
||||||
|
|
||||||
|
/* Define if the setvbuf function takes the buffering type as its second
|
||||||
|
argument and the buffer pointer as the third, as on System V
|
||||||
|
before release 3. */
|
||||||
|
#undef SETVBUF_REVERSED
|
||||||
|
|
||||||
|
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||||
|
#undef size_t
|
||||||
|
|
||||||
|
/* Define if you have the ANSI C header files. */
|
||||||
|
#undef STDC_HEADERS
|
||||||
|
|
||||||
|
/* Define if your <sys/time.h> declares struct tm. */
|
||||||
|
#undef TM_IN_SYS_TIME
|
||||||
|
|
||||||
|
/* Define to `unsigned char' if <sys/types.h> doesn't define. */
|
||||||
|
#undef uchar
|
||||||
|
|
||||||
|
/* Define to `unsigned short' if <sys/types.h> doesn't define. */
|
||||||
|
#undef ushort
|
||||||
|
|
||||||
|
/* Define to `unsigned int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef uint
|
||||||
|
|
||||||
|
/* Define to `unsigned long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef ulong
|
||||||
|
|
||||||
|
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||||
|
#undef mode_t
|
||||||
|
|
||||||
|
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||||
|
#undef off_t
|
||||||
|
|
||||||
|
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||||
|
#undef size_t
|
||||||
|
|
||||||
|
/* Define if you have the memmove function. */
|
||||||
|
#undef HAVE_MEMMOVE
|
||||||
|
|
||||||
|
/* Define if you have the mkdir function. */
|
||||||
|
#undef HAVE_MKDIR
|
||||||
|
|
||||||
|
/* 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 <dirent.h> header file. */
|
||||||
|
#undef HAVE_DIRENT_H
|
||||||
|
|
||||||
|
/* Define if you have the <fcntl.h> header file. */
|
||||||
|
#undef HAVE_FCNTL_H
|
||||||
|
|
||||||
|
/* Define if you have the <limits.h> header file. */
|
||||||
|
#undef HAVE_LIMITS_H
|
||||||
|
|
||||||
|
/* Define if you have the <malloc.h> header file. */
|
||||||
|
#undef HAVE_MALLOC_H
|
||||||
|
|
||||||
|
/* Define if you have the <stdlib.h> header file. */
|
||||||
|
#undef HAVE_STDLIB_H
|
||||||
|
|
||||||
|
/* Define if you have the <ndir.h> header file. */
|
||||||
|
#undef HAVE_NDIR_H
|
||||||
|
|
||||||
|
/* Define if you have the <strings.h> header file. */
|
||||||
|
#undef HAVE_STRINGS_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/dir.h> header file. */
|
||||||
|
#undef HAVE_SYS_DIR_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/ndir.h> header file. */
|
||||||
|
#undef HAVE_SYS_NDIR_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/stat.h> header file. */
|
||||||
|
#undef HAVE_SYS_STAT_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/time.h> header file. */
|
||||||
|
#undef HAVE_SYS_TIME_H
|
||||||
|
|
||||||
|
/* Define if you have the <sys/types.h> header file. */
|
||||||
|
#undef HAVE_SYS_TYPES_H
|
||||||
|
|
||||||
|
/* Define if you have the <unistd.h> header file. */
|
||||||
|
#undef HAVE_UNISTD_H
|
||||||
|
|
||||||
|
/* Define if we want to use the dmalloc library (--enable-dmalloc). */
|
||||||
|
#undef USE_DMALLOC
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
||||||
|
dnl Process this file with autoconf to produce a configure script.
|
||||||
|
AC_INIT(Main.c)
|
||||||
|
AC_CONFIG_HEADER(config.h)
|
||||||
|
|
||||||
|
dnl Checks for programs.
|
||||||
|
AC_CANONICAL_HOST
|
||||||
|
AC_PROG_CC
|
||||||
|
AC_PROG_INSTALL
|
||||||
|
|
||||||
|
dnl Checks for libraries.
|
||||||
|
dnl Replace `main' with a function in -lr:
|
||||||
|
dnl AC_CHECK_LIB(r, main)
|
||||||
|
|
||||||
|
dnl Checks for header files.
|
||||||
|
AC_HEADER_DIRENT
|
||||||
|
AC_HEADER_STDC
|
||||||
|
AC_CHECK_HEADERS(fcntl.h limits.h malloc.h stdlib.h strings.h sys/stat.h \
|
||||||
|
sys/time.h sys/types.h unistd.h)
|
||||||
|
|
||||||
|
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||||
|
AC_C_CONST
|
||||||
|
AC_TYPE_MODE_T
|
||||||
|
AC_TYPE_OFF_T
|
||||||
|
AC_TYPE_SIZE_T
|
||||||
|
AC_STRUCT_TM
|
||||||
|
AC_CHECK_TYPE(uchar, unsigned char)
|
||||||
|
AC_CHECK_TYPE(ushort, unsigned short)
|
||||||
|
AC_CHECK_TYPE(uint, unsigned int)
|
||||||
|
AC_CHECK_TYPE(ulong, unsigned long)
|
||||||
|
|
||||||
|
dnl Checks for library functions.
|
||||||
|
dnl AC_FUNC_SETVBUF_REVERSED
|
||||||
|
AC_FUNC_UTIME_NULL
|
||||||
|
AC_CHECK_FUNCS(memmove mkdir strtoul strcasecmp strncasecmp strerror)
|
||||||
|
|
||||||
|
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 Figure out what the build and link flags should be
|
||||||
|
if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then
|
||||||
|
dnl BeOS/PPC with Metrowerks compiler
|
||||||
|
CC=cc
|
||||||
|
GCC=
|
||||||
|
CFLAGS='-proc 603 -opt full'
|
||||||
|
echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
dnl if we're using gcc, include gcc-specific warning flags
|
||||||
|
dnl (
|
||||||
|
if test -z "$GCC"; then
|
||||||
|
BUILD_FLAGS='$(OPT)'
|
||||||
|
else
|
||||||
|
BUILD_FLAGS='$(OPT) $(GCC_FLAGS)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_SUBST(BUILD_FLAGS)
|
||||||
|
|
||||||
|
DMALLOC=
|
||||||
|
AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc: do dmalloc stuff], \
|
||||||
|
[ echo "--- enabling dmalloc";
|
||||||
|
DMALLOC="-L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ])
|
||||||
|
AC_SUBST(DMALLOC)
|
||||||
|
|
||||||
|
AC_OUTPUT(Makefile)
|
|
@ -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
|
|
@ -0,0 +1,34 @@
|
||||||
|
#! /bin/sh
|
||||||
|
# mkinstalldirs --- make directory hierarchy
|
||||||
|
# Author: Noah Friedman <friedman@prep.ai.mit.edu>
|
||||||
|
# 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
|
|
@ -0,0 +1,178 @@
|
||||||
|
.\" nulib2.1
|
||||||
|
.\" Copyright (C) 2000 by Andy McFadden. All Rights Reserved.
|
||||||
|
.\" This is free software; you can redistribute it and/or modify it under the
|
||||||
|
.\" terms of the GNU General Public License, see the file COPYING.
|
||||||
|
.\"
|
||||||
|
.\" The general structure of this man page was borrowed from "zip.1" in
|
||||||
|
.\" the Red Hat Linux 6.0 distribution.
|
||||||
|
.\"
|
||||||
|
.TH NULIB2 1L "17 Jan 2000"
|
||||||
|
.SH NAME
|
||||||
|
nulib2 \- package and compress (archive) files
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B nulib2
|
||||||
|
.RB \-command[modifiers]
|
||||||
|
.I archive
|
||||||
|
.I [ filenames ]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.I nulib2
|
||||||
|
is a disk and file archiver for NuFX (ShrinkIt) files. It can add files
|
||||||
|
to and extract files from
|
||||||
|
.IR .SHK ,
|
||||||
|
.IR .BXY ,
|
||||||
|
.IR .SEA
|
||||||
|
(as created by GS/ShrinkIt), and
|
||||||
|
.I .BSE
|
||||||
|
files.
|
||||||
|
.LP
|
||||||
|
When extracting, testing, or listing the contents of an archive, you can
|
||||||
|
specify "-" for the archive name. The archive will be read from stdin.
|
||||||
|
.LP
|
||||||
|
Filenames are considered case-sensitive.
|
||||||
|
.\" .LP
|
||||||
|
.\" The
|
||||||
|
.\" .I filenames
|
||||||
|
.\" will be compared in a case-sensitive fashion. While this would be
|
||||||
|
.\" inappropriate for most UNIX systems, it makes sense for Apple II archives,
|
||||||
|
.\" because most Apple II filesystems are case-insensitive.
|
||||||
|
.LP
|
||||||
|
This man page contains a summary of available options. For full
|
||||||
|
documentation and the latest versions, visit http://www.nulib.com/.
|
||||||
|
.SH "OPTIONS"
|
||||||
|
.TP
|
||||||
|
.B \-a
|
||||||
|
Add files to an archive. If the archive does not exist, a new one
|
||||||
|
will be created. The list of files to add must be given.
|
||||||
|
.TP
|
||||||
|
.B \-d
|
||||||
|
Delete files from an archive. The set of files to delete must be provided.
|
||||||
|
.TP
|
||||||
|
.B \-i
|
||||||
|
Integrity test. If no files are listed, all files in the archive are
|
||||||
|
tested.
|
||||||
|
.TP
|
||||||
|
.B \-p
|
||||||
|
Pipe extraction. All extracted files are written to stdout instead of
|
||||||
|
a file on disk. Normal archive progress messages are suppressed.
|
||||||
|
.TP
|
||||||
|
.B \-t
|
||||||
|
Table of contents. Provides a simple list of files in the archive, one
|
||||||
|
per line.
|
||||||
|
.TP
|
||||||
|
.B \-v
|
||||||
|
Verbose table of contents. Output similar to what ShrinkIt displays is
|
||||||
|
shown.
|
||||||
|
.TP
|
||||||
|
.B \-x
|
||||||
|
Extract files from an archive. If no files are listed, all files in
|
||||||
|
the archive are extracted.
|
||||||
|
.\" There's also a '-z' command that does a verbose archive dump, but it's
|
||||||
|
.\" only available if NufxLib was built with debugging enabled.
|
||||||
|
.SH "MODIFIERS"
|
||||||
|
.TP
|
||||||
|
.B \-0
|
||||||
|
Don't use compression. Files added will be stored without compression.
|
||||||
|
.TP
|
||||||
|
.B \-c
|
||||||
|
Comments. When extracting, comments will be displayed. When adding,
|
||||||
|
you will be prompted to enter a one-line comment for every file.
|
||||||
|
.TP
|
||||||
|
.B \-e
|
||||||
|
Preserve ProDOS file types. See the ProDOS File Type Preservation document
|
||||||
|
on http://www.nulib.com/ for details on how this works.
|
||||||
|
.TP
|
||||||
|
.B \-ee
|
||||||
|
Preserve file types, using extended names. A file extension is appended
|
||||||
|
to extracted files. Useful on operating systems like Windows, where
|
||||||
|
filename extensions are important. When adding files,
|
||||||
|
.I nulib2
|
||||||
|
will try to guess at correct file types by examining the filename extension.
|
||||||
|
.TP
|
||||||
|
.B \-f
|
||||||
|
Freshen files. When adding, files in the archive that are older than files
|
||||||
|
on disk are "freshened", meaning that no new files are added, and files
|
||||||
|
that are the same age or newer aren't touched. Works similarly when
|
||||||
|
extracting.
|
||||||
|
.TP
|
||||||
|
.B \-j
|
||||||
|
Junk directory names. Only the filename is kept; the rest of the pathname
|
||||||
|
is thrown away. Empty directories aren't stored. Works when adding or
|
||||||
|
extracting.
|
||||||
|
.TP
|
||||||
|
.B \-k
|
||||||
|
Store files as disk images. Files that are a multiple of 512 bytes will
|
||||||
|
be added as disk images rather than normal files. This does not override
|
||||||
|
the "-e" flag.
|
||||||
|
.TP
|
||||||
|
.B \-l
|
||||||
|
Auto-convert text files. A reasonably smart algorithm is used to identify
|
||||||
|
which files are text and which aren't during extraction. It then converts
|
||||||
|
whatever EOL
|
||||||
|
indicator is being used by the text file into something appropriate for
|
||||||
|
the current system.
|
||||||
|
.TP
|
||||||
|
.B \-ll
|
||||||
|
Auto-convert all files. All files being extracted are considered text,
|
||||||
|
and will be converted. Don't use this unless you're sure you need it.
|
||||||
|
.TP
|
||||||
|
.B \-r
|
||||||
|
Recurse into subdirectories. When adding, this causes
|
||||||
|
.I nulib2
|
||||||
|
to descend into subdirectories and store all of the files found. When
|
||||||
|
extracting, testing, or deleting, this causes the files listed to match
|
||||||
|
against all records whose prefix matches, allowing you to extract, test,
|
||||||
|
or delete entire subdirectories from the archive.
|
||||||
|
.TP
|
||||||
|
.B \-u
|
||||||
|
Update files. When adding, files in the archive that are older than files
|
||||||
|
on disk are updated. Files in the archive that are the same age or newer
|
||||||
|
aren't touched. New files will be added. Works similarly when extracting.
|
||||||
|
.SH "EXAMPLES"
|
||||||
|
A simple example:
|
||||||
|
.IP
|
||||||
|
\fCnulib2 a foo.shk *\fP
|
||||||
|
.LP
|
||||||
|
creates the archive
|
||||||
|
.I foo.shk
|
||||||
|
(assuming it doesn't exist) and stores all of the files in the current
|
||||||
|
directory in it, in compressed form.
|
||||||
|
.LP
|
||||||
|
If you wanted to add all the files in the current directory, as well as
|
||||||
|
all files in all subdirectories, you could use:
|
||||||
|
.IP
|
||||||
|
\fCnulib2 ar foo.shk *\fP
|
||||||
|
.LP
|
||||||
|
to recursively descend into the directory tree.
|
||||||
|
.LP
|
||||||
|
Using the command:
|
||||||
|
.IP
|
||||||
|
\fCnulib2 xe foo.shk\fP
|
||||||
|
.LP
|
||||||
|
would extract all files from
|
||||||
|
.I foo.shk,
|
||||||
|
preserving ProDOS file types. If you then used the command:
|
||||||
|
.IP
|
||||||
|
\fCnulib2 aer foo.shk *\fP
|
||||||
|
.LP
|
||||||
|
you would add the files,
|
||||||
|
preserving the file types of anything that was extracted with the
|
||||||
|
"-e" flag set.
|
||||||
|
.LP
|
||||||
|
A handy way to look at text documents is to use:
|
||||||
|
.IP
|
||||||
|
\fCnulib2 xeel foo.shk\fP
|
||||||
|
.LP
|
||||||
|
to convert end-of-line terminators (e.g. CRLF to LF) as the files are
|
||||||
|
being extracted. The "-ee" flag adds ".TXT" to all files with a ProDOS
|
||||||
|
file type of TXT ($04).
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
compress(1),
|
||||||
|
tar(1),
|
||||||
|
zip(1L),
|
||||||
|
unzip(1L),
|
||||||
|
nulib(1L)
|
||||||
|
.SH BUGS
|
||||||
|
Probably.
|
||||||
|
.SH AUTHOR
|
||||||
|
Copyright (C) 2000 by Andy McFadden. All Rights Reserved.
|
||||||
|
.\" end of file
|
Loading…
Reference in New Issue