This is uncompresshelp.m in view mode; [Download] [Up]
// uncompresshelp.m
// cc uncompresshelp.m -Wall -g -O -s -o uncompresshelp -lIndexing_s
// cc -arch m68k -arch i386 uncompresshelp.m -Wall -g -O -s -o uncompresshelp -lIndexing_s
// cc -arch m68k -arch i386 -arch hppa uncompresshelp.m -Wall -g -O -s -o uncompresshelp -lIndexing_s
//
// Copyright 1993, 1994, Scott Hess. This source code may be
// redistributed and modified without restriction. Well, one
// restriction - do not claim that you wrote it. If the user
// receives non-trivial advantage from the use of this program, a
// payment of $50-$100 would be appreciated.
//
// [I guess that would make this semi-shareware. I'm not much
// interested in turning a profit (Ha!) on this program. Rather,
// I am interested in getting some small return on time and energy
// invested so that I can work on other, similar programs. You
// know, little programs that aren't worth doing for their own
// sake, but when you need them, you NEED them. I love poking
// around in the obscure corners of NeXTSTEP, but too often I
// can't spend much time doing it because it's hard to justify
// the hours. It's like a hobby that brings in a small income.
// You wouldn't want to live on it, because in reality the income
// barely covers equipment and supplies, but it's useful as
// justification when someone says "Why do you keep wasting your
// time with that?" I also want to know if people find stuff like
// this _useful_, or just interesting ...]
//
// Scott Hess
// 12901 Upton Avenue South, #326
// Burnsville, MN 55337
//
//
// To muck about with the Help.store files:
// o Open an IXStoreFile instance on the file.
// o Within the store, open an IXBTree on block one, which contains
// entries mapping the name of the helpfile element to the
// files which are contained within it.
// o Within the store, open an IXBTree on block two, which contains
// entries mapping integer indices to file contents.
// o The directory btree's values are of the form
// "filename key;[filename key;...]". The filenames are files
// that are contained within the helpfile element, while the
// keys are integers which index into the file btree's to find
// the contents of the file.
// o Individual file content keys may appear more than once in
// the directory. This means that the specified files are the
// same [implemented as a call to link() in extractFile].
// o File's which end in .rtf are compressed using /usr/ucb/compress.
// So is .index.store, apparently.
//
//
// Version history:
//
// Wednesday, January 12, 1994:
// Fix inverted cur test in extractFile().
//
// Rewrite uncompress call in terms of raw fork/execl/wait4.
// Previously it used system(), which did not work correctly
// when the filename contained spaces. It also wouldn't have
// worked if the filename contained shell metacharacters (which
// was the reasoning behind not just putting quotes or
// double-quotes in the system call).
//
// Added more descriptive and debugging output.
//
// Added -h option to display help info which is taken from this file
// header. -AFI
//
// Reordered statements so that the absence of a help.store file will
// terminate utility before "Extracting helpfile.store into outputdir"
// message is printed. -AFI
//
// Monday, January 10, 1994:
// First public release.
//
static const char *usage =
"Usage: uncompresshelp [-h | [helpfile.store [outputdir]]]";
static const char *help =
"uncompresshelp extracts help files from a help storefile and\n"
"stores them back onto the filesystem. helpfile.store is a help\n"
"storefile as generated by compresshelp. outputdir is the\n"
"directory to store the files in. helpfile.store defaults to\n"
"Help.store. outputdir defaults to the input file with .store\n"
"stripped from the end.";
#import <libc.h>
#import <ansi.h>
#import <indexing/indexing.h>
#import <objc/HashTable.h>
static HashTable *extractedFiles=nil;
int uncompress( const char *filename)
{
int pid;
if( !(pid=fork())) {
execl( "/usr/ucb/uncompress", "/usr/ucb/uncompress", filename, NULL);
exit( 1);
} else if( pid==-1) {
perror( "uncompresshelp: uncompress error");
return -1;
} else {
union wait status;
int ret=wait4( pid, &status, 0, NULL);
if( ret==-1) {
perror( "uncompresshelp: waiting for uncompress");
return -1;
} else if( WIFSIGNALED( status)) {
fprintf( stderr, "uncompresshelp: compress terminated on signal %d.\n", status.w_termsig);
return -1;
} else if( status.w_retcode) {
fprintf( stderr, "uncompresshelp: compress exited with %d.\n", status.w_retcode);
return -1;
}
}
return 0;
}
void extractFile( IXBTreeCursor *fileCursor, unsigned fileId, const char *target)
{
// If we've already extracted that index, just link the
// target to it.
const char *cur=[extractedFiles valueForKey:(void *)fileId];
if( cur) {
fprintf( stderr, "\tLinking to %s\n", cur);
if( link( cur, target)==-1) {
perror( "uncompresshelp: linking to extracted file");
}
// Otherwise, we have to reach into the btree using fileCursor
// and find it.
} else {
void *file=NULL;
unsigned fileLen;
// The index is stored in big-endian form.
unsigned indexFileId=NXSwapHostLongToBig( fileId);
// Make certain we have a table to map extracted files.
if( !extractedFiles) {
extractedFiles=[[HashTable alloc] initKeyDesc:"i" valueDesc:"%"];
}
// Store the mapping for this index.
[extractedFiles insertKey:(void *)fileId value:(void *)NXUniqueString( target)];
// Try to find the index in the table.
if( [fileCursor setKey:(void *)(&indexFileId) andLength:sizeof( indexFileId)]) {
int fd;
const char *slash, *dot;
// Read the contents of the file.
fileLen=[fileCursor readValue:&file];
// Open the file and stuff the contents into it.
fd=open( target, O_WRONLY|O_CREAT, 0644);
if( fd!=-1) {
if( write( fd, file, fileLen)<fileLen) {
perror( "uncompresshelp: Writing file");
}
if( close( fd)==-1) {
perror( "uncompresshelp: Closing file");
}
} else {
perror( "uncompresshelp: Opening file");
}
// Free the contents.
free( file);
// If the file is .rtf, uncompress it.
slash=rindex( target, '/');
slash=(slash ? slash+1 : target);
dot=rindex( slash, '.');
if( dot) {
if( !strcmp( dot, ".rtf") || !strcmp( slash, ".index.store")) {
char targetZ[ MAXPATHLEN+1];
sprintf( targetZ, "%s.Z", target);
fprintf( stderr, "\tRenaming %s\n\t to %s\n", target, targetZ);
rename( target, targetZ);
fprintf( stderr, "\tUncompressing %s\n", targetZ);
uncompress( targetZ);
}
}
}
}
}
// Create the directory, creating parent directories as needed.
int mkdirs( char *dir, int mode)
{
if( access( dir, F_OK)) {
int ret=0;
char *slash=rindex( dir, '/');
if( slash) {
*slash='\0';
ret=mkdirs( dir, mode);
*slash='/';
}
if( !ret) {
ret=mkdir( dir, mode);
}
return ret;
}
return 0;
}
// *files is a file list in the format "filename key;[ filename
// key;...]". Parse out the first filename/key pair into
// outputp/*indexp, and put the end position back into *files.
// Return YES if a filename was found. This routine is more
// complex than needed because I was concerned that my only
// knowledge of what's possible for filenames is empirical -
// I know what's in some sample files, but I don't know whether
// there are other possibilities (consider spaces in filenames,
// for instance). Better safe than sorry.
BOOL getNextFile( const char **files, char *outputp, unsigned *indexp)
{
const char *s, *p;
for( s=*files; *s; s++) {
// Get to a space followed by a digit.
if( *s==' ' && isdigit( s[ 1])) {
// Start accumulating the index.
unsigned i=s[ 1]-'0';
// Store away the end of the name.
p=s;
// Accumulate the rest of the index.
for( s+=2; isdigit( *s); s++) {
i*=10;
i+=s[ 0]-'0';
}
// It _must_ end with ; to be valid.
if( *s==';') {
// Store away the file name.
strncpy( outputp, *files, p-(*files));
outputp[ p-(*files)]='\0';
// Store away the index found.
*indexp=i;
// Return the new search position, skipping
// the extra space after interior elements of
// the list.
*files=(s[ 1] ? s+2 : s+1);
return YES;
}
}
}
return NO;
}
void main( int argc, char **argv)
{
const char *inputFile="Help.store";
char outputDir[ MAXPATHLEN+1]="Help";
unsigned outputDirLen;
IXStoreFile *helpStore;
IXBTree *dirTree, *fileTree;
IXBTreeCursor *dirCursor, *fileCursor;
// Decode command-line parameters.
if( argc>1) {
inputFile=argv[ 1];
if( argc>2) {
strcpy( outputDir, argv[ 2]);
} else {
const char *dot;
int c;
while( (c=getopt( argc, argv, "h"))!=EOF) {
switch (c) {
case 'h' :
fprintf( stderr, "%s\n\n%s\n", help, usage);
exit( 0);
case '?' :
default :
fprintf( stderr, "%s\n", usage);
exit( 1);
}
}
if( (dot=rindex( inputFile, '.')) && !strcmp( dot, ".store")) {
strncpy( outputDir, inputFile, dot-inputFile);
outputDir[ dot-inputFile]='\0';
} else {
fprintf( stderr, "uncompresshelp:\tUnable to generate default outputDir:\n\t\t%s doesn't end in .store.\n", inputFile);
exit( 1);
}
}
}
// Open up the storefile.
helpStore=[[IXStoreFile alloc] initFromFile:inputFile forWriting:NO];
if( !helpStore) {
fprintf( stderr, "Couldn't open %s\n", inputFile);
fprintf( stderr, "%s\n\n%s\n", help, usage);
exit( 1);
}
outputDirLen=strlen( outputDir);
outputDir[ outputDirLen++]='/';
fprintf( stderr, "Extracting %s into %s\n", inputFile, outputDir);
// Open up a tree to list our directories and files in
// them.
dirTree=[[IXBTree alloc] initFromBlock:1 inStore:helpStore];
dirCursor=[[IXBTreeCursor alloc] initWithBTree:dirTree];
// Open up a tree to map indices to files.
fileTree=[[IXBTree alloc] initFromBlock:2 inStore:helpStore];
fileCursor=[[IXBTreeCursor alloc] initWithBTree:fileTree];
// Start from the first entry in the directory list.
if( [dirCursor setFirst]) {
do {
char *dir=NULL, *files=NULL;
unsigned dirLen, filesLen;
// Get the directory entry.
if( [dirCursor getKey:(void **)&dir andLength:&dirLen]) {
unsigned i;
const char *s;
// Append the entry to the target directory.
strncpy( outputDir+outputDirLen, dir, dirLen);
outputDir[ outputDirLen+dirLen-1]='\0';
// Read the list of files for that entry.
filesLen=[dirCursor readValue:(void **)&files];
// Look for the first file in the list.
s=files;
if( getNextFile( &s, outputDir+outputDirLen+dirLen, &i)) {
// If that's the end, check to see if the
// directory entry's basename matches the
// only entry in the directory.
if( !*s) {
char *b=rindex( outputDir, '/');
if( b && !strcmp( b+1, outputDir+outputDirLen+dirLen)) {
// If they match, then extract directly
// into the name specified by the
// directory entry.
fprintf( stderr, "Extracting simple file %s\n", outputDir);
*b='\0';
mkdirs( outputDir, 0755);
*b='/';
extractFile( fileCursor, i, outputDir);
// And go to the next entry.
continue;
}
}
// Make certain the directory(s) for the
// entry exist.
mkdirs( outputDir, 0755);
fprintf( stderr, "Extracting compound file %s\n", outputDir);
// Put a slash between the parent dir and
// the filenames within it.
outputDir[ outputDirLen+dirLen-1]='/';
// Extract each of the contained files.
do {
fprintf( stderr, " Extracting %s\n", outputDir);
extractFile( fileCursor, i, outputDir);
} while( getNextFile( &s, outputDir+outputDirLen+dirLen, &i));
}
free( files);
}
} while( [dirCursor setNext]);
}
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.