[comp.sources.amiga] v90i041: BBSindex 1.0 - file database utility for BBS-PC!, Part01/03

Amiga-Request@cs.odu.edu (Amiga Sources/Binaries Moderator) (02/03/90)

Submitted-by: Eddy Carroll <ECARROLL%vax1.tcd.ie@CUNYVM.CUNY.EDU>
Posting-number: Volume 90, Issue 041
Archive-name: util/bbsindex-1.0/part01

BBSindex is a utility for use with the BBS-PC! bulletin board package. It
allows you to list the contents of the file database in a wide variety
of ways. It has its own script language with 20 commands which allows a
wide variety of tasks to be performed.

Eddy Carroll                                      ecarroll@vax1.tcd.ie

#!/bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 3)."
# Contents:  scripts scripts/bbsindex.scr scripts/filenote.scr
#   scripts/info.scr scripts/move.scr scripts/offline.scr src
#   src/bbs.h src/bbsindex.h src/checkfiles.c src/expression.c
#   src/makefile src/sort.c src/system.h src/tiny.a
# Wrapped by tadguy@xanth on Fri Feb  2 14:54:35 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test ! -d 'scripts' ; then
    echo shar: Creating directory \"'scripts'\"
    mkdir 'scripts'
fi
if test -f 'scripts/bbsindex.scr' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'scripts/bbsindex.scr'\"
else
echo shar: Extracting \"'scripts/bbsindex.scr'\" \(2360 characters\)
sed "s/^X//" >'scripts/bbsindex.scr' <<'END_OF_FILE'
X#
X# This script builds sorted file catalogues for the Amiga and IBM
X# file areas (i.e. sections 3, 4, 6 and 1), on the Infomatique BBS
X# (Phone: Dublin 302970 (+353-1-302970),  V21/22bis/23)
X#
X# The following constants define the output files for the various filelists
X# Change them to suit.
X
XAmigaFiles	= "Amiga.Files"
XIBMFiles	= "IBM.Files"
X
X#
X# This macro outputs the header for a filelist
X#
Xmacro header # SectionNumber, No. of dirs, SectionName, Filename
X	select Section = $1
X	scan
X	echo "Sorted list of $3 files - %d %w %t"
X	echo ""
X	echo "This file is available in the files section as $4"
X	echo "Files which are not currently available are marked with a *"
X	echo ""
X	echo "Total of %n files in $2 sections, occupying %m Megabytes."
X	echo ""
Xendm
X
X#
X# This macro takes a Section, Directory and title, and produces a list of
X# all the files in that section and directory.
X#
Xmacro sublist # Section, Directory, Title
X	echo "%{$2. $3}\n%u-"
X	select Section = $1 AND Directory = $2
X	list
X	echo ""
Xendm
X
X#
X# Standard file stuff, the same for everything
X#
Xnorequest
Xcheckfiles
Xformat "%15n %w %-6x-%b{B,T}%v{ ,*} %c"
Xsort Section, Directory, Name
X
X#
X# List Amiga files
X#
Xopen $(AmigaFiles)
Xheader 3, 14, "Amiga", "AMIGA.FILES"
Xsublist 3,  1, "General text files"
Xsublist 3,  2, "Technical text files"
Xsublist 3,  3, "Archived text files"
Xsublist 3,  4, "Games"
Xsublist 3,  5, "Screen Hacks"
Xsublist 3,  6, "Graphics, sound & demos"
Xsublist 3,  7, "Graphics & sound programs"
Xsublist 3,  8, "DOS utilities"
Xsublist 3,  9, "Programming utilities"
Xsublist 3, 10, "Resident utilities "
Xsublist 3, 11, "Applications"
Xsublist 3, 12, "Comms programs"
Xsublist 3, 13, "Miscellaneous"
Xsublist 3, 14, "Hardware"
X#
X# List IBM files
X#
Xopen $(IBMfiles)
Xheader 4, 16, "IBM", "IBM.FILES"
Xsublist 4,  1, "General, Miscellaneous"
Xsublist 4,  2, "Miscellaneous Text Files"
Xsublist 4,  3, "Digestives"
Xsublist 4,  4, "DOS File Utilities"
Xsublist 4,  5, "Archive Utilities"
Xsublist 4,  6, "Pop-Up Utilities"
Xsublist 4,  7, "Games & Fun"
Xsublist 4,  8, "Graphics Progs"
Xsublist 4,  9, "Graphics Images & Viewers"
Xsublist 4, 10, "Sound & Music"
Xsublist 4, 11, "Source Code & Programming"
Xsublist 4, 12, "Viruses"
Xsublist 4, 13, "Comms Packages & Utils"
Xsublist 4, 14, "Mainstream Applications"
Xsublist 4, 15, "Education/Tutorials"
Xsublist 4, 16, "Hardware Utils, Performance Tests"
END_OF_FILE
if test 2360 -ne `wc -c <'scripts/bbsindex.scr'`; then
    echo shar: \"'scripts/bbsindex.scr'\" unpacked with wrong size!
fi
# end of 'scripts/bbsindex.scr'
fi
if test -f 'scripts/filenote.scr' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'scripts/filenote.scr'\"
else
echo shar: Extracting \"'scripts/filenote.scr'\" \(511 characters\)
sed "s/^X//" >'scripts/filenote.scr' <<'END_OF_FILE'
X#
X#  This script generates an AmigaDOS batch file to go through all the
X#  files on the system and add a filenote to them, giving the following
X#  information:
X#
X#       (Section, Directory)  BBS Filename    Uploader   File description
X#
X
XOpen "filenote.exec"
X
Xnorequest
Xcheckfiles
X
Xselect Online
X
Xformat 'filenote %f "%6{(%s,%r)} %15n %15o %c"'
Xecho ";"
Xecho "; Batchfile to add comments to all the BBS files in the system
Xecho ";"
Xlist
Xecho ";"
Xecho 'echo "Total of %n files successfully filenoted"'
Xecho ";"
END_OF_FILE
if test 511 -ne `wc -c <'scripts/filenote.scr'`; then
    echo shar: \"'scripts/filenote.scr'\" unpacked with wrong size!
fi
# end of 'scripts/filenote.scr'
fi
if test -f 'scripts/info.scr' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'scripts/info.scr'\"
else
echo shar: Extracting \"'scripts/info.scr'\" \(806 characters\)
sed "s/^X//" >'scripts/info.scr' <<'END_OF_FILE'
X###########################################################################
X#
X# This simple script file merely prints all possible information about
X# each file in the catalogue. It serves as a good demonstration of how
X# to use extended command lines. Practically the whole script is a single
X# extended format command!
X#
X###########################################################################
X
Xnorequest
Xcheckfiles
X
Xformat "\
XFilename      : %n\n\
XUploaded by   : %o\n\
XComment       : %c\n\
XFiletype      : %b{Binary,Text}\n\
XDisk filename : %f\n\
XOrigin        : %l{Local, Remote}\n\
XStatus        : %i{Online, Offline}\n\
XFile state    : %v{Valid, Invalid}\n\
XAccesses      : %a\n\
XSection       : %s\n\
XDirectory     : %r\n\
XUpload date   : %w\n\
XFile size     : %x\n\
X----------"
Xsort name
Xlist
END_OF_FILE
if test 806 -ne `wc -c <'scripts/info.scr'`; then
    echo shar: \"'scripts/info.scr'\" unpacked with wrong size!
fi
# end of 'scripts/info.scr'
fi
if test -f 'scripts/move.scr' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'scripts/move.scr'\"
else
echo shar: Extracting \"'scripts/move.scr'\" \(1001 characters\)
sed "s/^X//" >'scripts/move.scr' <<'END_OF_FILE'
X#
X# Simple script to generate and execute an AmigaDOS batch file to move
X# all the files in a particular section into # a new common directory.
X#
X
X#
X# Set the output file, destination directory and section no's appropriately.
X#
X
XOutFile = "Move.exec"
XDest	= "DH1:Somedirectory"
XSection	= 3
X
X#
X# Use the following MV definition if you have a MV that supports moves
X# across disk boundaries (such as Edwin Hoogerbeets' excellent one).
X#
X
XMV	= "mv %f $(Dest)"
X
X#
X# Use the following MV definition if you want to use plain AmigaDOS
X#
X
X# MV	= "copy %f $(Dest)\ndelete $f"
X
X#
X# Now generate the list of files. We sort by directory name to try
X# and localise disk access
X#
X
Xformat "$(MV)"
XNoRequest
XCheckFiles
XSelect Online and Section = $(Section)
XSort Diskname
Xecho ";"
Xecho "; BBSindex generated AmigaDOS batch file to move files in"
Xecho "; section $(Section) to directory $(Dest)."
Xecho ";"
Xecho "; Created on %d %w %r"
Xecho ";"
Xlist
Xecho ";"
Xecho 'Echo "Total of %n files moved successfully"'
Xecho ";"
END_OF_FILE
if test 1001 -ne `wc -c <'scripts/move.scr'`; then
    echo shar: \"'scripts/move.scr'\" unpacked with wrong size!
fi
# end of 'scripts/move.scr'
fi
if test -f 'scripts/offline.scr' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'scripts/offline.scr'\"
else
echo shar: Extracting \"'scripts/offline.scr'\" \(847 characters\)
sed "s/^X//" >'scripts/offline.scr' <<'END_OF_FILE'
X##############################################
X#
X# This script lists all the files in the
X# BBS catalogue that are offline or invalid
X#
X##############################################
X
Xnorequest
Xcheckfiles
Xsort Section, Name
Xformat "%s:%15n   %-6x-%b{B,T} | %15d\n%28{} | %c"
X
Xecho ""
Xecho "BBS-PC! file report - %d %w %t"
Xecho ""
X
X##############################################
X#
X#             List offline files
X#
X##############################################
X
Xselect Offline
Xecho "%{Files offline}\n%u-"
Xlist
Xecho ""
Xecho "(Total of %n files offline.)"
Xecho ""
X
X##############################################
X#
X#             List invalid files
X#
X##############################################
X
Xselect Online and Invalid
Xecho "%{Files online, but possibly corrupt}\n%u-"
Xlist
Xecho ""
Xecho "(Total of %n files possibly corrupt.)"
Xecho ""
Xecho ""
END_OF_FILE
if test 847 -ne `wc -c <'scripts/offline.scr'`; then
    echo shar: \"'scripts/offline.scr'\" unpacked with wrong size!
fi
# end of 'scripts/offline.scr'
fi
if test ! -d 'src' ; then
    echo shar: Creating directory \"'src'\"
    mkdir 'src'
fi
if test -f 'src/bbs.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/bbs.h'\"
else
echo shar: Extracting \"'src/bbs.h'\" \(3354 characters\)
sed "s/^X//" >'src/bbs.h' <<'END_OF_FILE'
X/*
X *		Standard BBS-PC! headers (with a few modifications)
X *
X *		Important note: This header file cannot be precompiled with
X *		Lattice C V5.02, because of a bug which prevents bit fields
X *		from working properly when they are precompiled.
X */
X
X/* Official Micro-Systems Software Structures */
X
X#define NUM_TERM	10		/* Number of computer types		*/
X#define NUM_SECT	16		/* Number of section names		*/
X
X#define CAT_LEN		15		/* File catalogue name lengths	*/
X#define DESC_LEN	40		/* File catalogue desc length	*/
X#define	FNAME_LEN	12		/* File name lengths			*/
X#define FPATH_LEN	30		/* File name path lengths		*/
X#define	NAME_LEN	24		/* User name length				*/
X#define PASS_LEN	10		/* User password length			*/
X#define SECT_LEN	20		/* Section name lengths			*/
X#define TERM_LEN	15		/* Computer name lengths		*/
X
X/* UDHEAD.DAT record structure */
X
X/* Note some new additions to this structure, for internal tracking */
X
Xtypedef struct {
X	BYTE	type;
X	int		local:1;					/* True if local upload			*/
X	int		bin:1;						/* True if binary file			*/
X	int		valid:1;					/* True if valid          	NEW */
X	int		online:1;					/* True if file online		NEW	*/
X	int		dirnum:5;					/* Real dir num of file		NEW */
X	int  	:7;							/* Reserved 	    	CHANGED */
X	char	cat_name[CAT_LEN];			/* Catalog filename (key1)		*/
X	UWORD	date;						/* Upload date serial			*/
X	BYTE	dir;						/* Dir number (key2 - seg1)		*/
X	BYTE	section;					/* Section number (key2 - seg2)	*/
X	UWORD	accesses;					/* Number of accesses			*/
X	LONG	length;						/* File length					*/
X	char	disk_name[FNAME_LEN+1];		/* Disk filename				*/
X	char	owner[NAME_LEN+1];			/* Owner's name					*/
X	char	desc[DESC_LEN+1];			/* Description text				*/
X} UDHEAD;
X
X#define UDSIZE		sizeof(UDHEAD)
X
X
X/* Terminal parameters */
X
Xtypedef struct {
X	int		linefeed: 1;
X	int		:31;
X	BYTE	nuls;
X	BYTE	protocol;
X	BYTE	align;
X	BYTE	page[2];
X	BYTE	cls[4];
X	BYTE	bs[3];
X	char	name[TERM_LEN+1];
X} TRMNL;
X
X
X/* CFGINFO.DAT record structure */
X
Xtypedef struct {
X	int		dir0_ok: 1;		/* Directory 0 downloads			*/
X	int		by_call: 1;		/* Time limit per call				*/
X	short	max_msg;		/* Maximum messages in system		*/
X	short	max_user;		/* Maximum users in system			*/
X	short	max_log;		/* Maximum call log					*/
X	short	max_ud;			/* Maximum U/D files				*/
X	short	reward;			/* Upload reward					*/
X	short	sleeptime;		/* sleep timeout (adjusted)			*/
X	long	dummy;			/* Reserved							*/
X	BYTE	log_p1;			/* Log privilege low				*/
X	BYTE	log_p2;			/* Log privilege high				*/
X	BYTE	hi_men;			/* Highest menu set					*/
X	BYTE	log_type;		/* Login method						*/
X	short	limit[2];		/* Guest/member time limits 		*/
X	short	priv[2];		/* Guest/member privileges			*/
X	UWORD	rd_acc[2];		/* Guest/member read access			*/
X	UWORD	wr_acc[2];		/* Guest/member write access		*/
X	UWORD	up_acc[2];		/* Guest/member upload access		*/
X	UWORD	dn_acc[2];		/* Guest/member download access		*/
X	BYTE	sav_sec[2];		/* Guest/member save section		*/
X	BYTE	sec_flg[NUM_SECT];					/* Section flags			*/
X	char	sec_name[NUM_SECT] [SECT_LEN+1];	/* Section names			*/
X	char	ud_alt[NUM_SECT] [FPATH_LEN+1];		/* Alternate UD paths		*/
X	char	syspass[PASS_LEN+1];				/* Sysop password 			*/
X	BYTE	menu[2];							/* Guest/Member menu sets	*/
X	BYTE	align;
X	long	dummy2;
X	long	dummy3;
X	TRMNL	trmnl[NUM_TERM];					/* Terminal parameters		*/
X} CFGINFO;
X
X#define CFGSIZE		sizeof(CFGINFO)
END_OF_FILE
if test 3354 -ne `wc -c <'src/bbs.h'`; then
    echo shar: \"'src/bbs.h'\" unpacked with wrong size!
fi
# end of 'src/bbs.h'
fi
if test -f 'src/bbsindex.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/bbsindex.h'\"
else
echo shar: Extracting \"'src/bbsindex.h'\" \(12473 characters\)
sed "s/^X//" >'src/bbsindex.h' <<'END_OF_FILE'
X/*
X *		BBSINDEX.H
X *	
X *		All the standard variables and other stuff used by all the modules
X *		of BBSindex, and standard BBS-PC! headers (with a few modifications).
X *
X *		Important note: This header file cannot be precompiled with
X *		Lattice C V5.02, because of a bug which prevents bit fields
X *		from working properly when they are precompiled.
X */
X
X#include "bbs.h"
X
X#define TRUE	1
X#define FALSE	0
X
X#define BTOK(x) (((x)+1023)>>10)	/* Convert file size into K */
X
X/*
X *		Default values for command line parameters and script commands
X */
X
X#define FORMAT		"%15n %w %-6x-%b{B,T}  %c\n"
X#define UDNAME		"UDHEAD.DAT"
X#define CFGNAME		"CFGINFO.DAT"
X#define PROGSCRIPT	"BBSCRIPT"
X#define DEFSCRIPT	"BBSindex.scr"
X
X/*
X *	Blocksize determines the largest contiguous memory segment which is
X *	needed. Larger values give faster processing, but need more continuous
X *	memory.
X */
X
X#define	BLOCKSIZE	160				/* Number of records/block (~16K)		*/
X#define MAXOUT		1024			/* The maximum length of an output line	*/
X#define	MAXCOM		1024			/* Maximum size of a single command		*/
X#define MAXSUB		256				/* Maximum size of a sub format string	*/
X#define FRAGBLOCK	8192			/* Memory block to allocate in mymalloc */
X#define FRAGTHRESH	200				/* Threshold for mem reqs in mymalloc	*/
X#define BUFSIZE		8192			/* Maximum size of output buffer		*/
X#define MAXEXPR		100				/* Max number of items in an expression	*/
X#define MAXDIRENT	1000			/* Maximum number of unknown dir files	*/
X#define DIRFRAG		100				/* Number of dir entries/block alloced	*/
X#define DIRNAMESIZE	80				/* Maximum length of directory name		*/
X#define MACROLEN	20				/* Maximum length of macro name			*/
X#define MAXMACRO	50				/* Maximum number of macros allowed		*/
X#define MAXNEST		10				/* Max number of macro nesting levels	*/
X#define MAXCONST	20				/* Maximum length of a constant name	*/
X
X/* Note: MAXSUB above is allocated on the stack, so don't make it too big */
X
X/*
X *		This structure is used to build a tree structure representing
X *		the expression given with the SELECT command.
X */
Xstruct expr {
X	int		field;			/* Field to test, or boolean operator	*/
X	int		op;				/* Operator to test against, if any		*/
X	int		num;			/* First data field						*/
X	char	*text;			/* Second data field					*/
X	struct	expr *left;		/* Left subtree							*/
X	struct	expr *right;	/* Right subtree						*/
X};
X
Xtypedef struct expr EXPR;
X
X/*
X *		This structure is used to hold the name of a file found in
X *		a BBS-PC! file directory during CHECKFILES, which does not
X *		exist in the BBS-PC! file catalogue.
X */
Xtypedef struct {
X	char	name[32];		/* Disk filename						*/
X	short	date;			/* In BBS-PC! format, max 16 bits		*/
X	short	dirnum;			/* The directory number it was in		*/
X	long	size;			/* The size of the file, in bytes		*/
X} DIRENTRY;
X
X#define DIRENTRYSIZE	sizeof(DIRENTRY)
X
X/*
X *		This structure holds the names of files to be "ignored" during a
X *		CHECKFILES. I.e. they are marked as valid, even if their filesize
X *		on disk doesn't match that in the file catalogue. This structure
X *		is built up with the IGNORE command.
X */
Xstruct ignore {
X	struct ignore *next;
X	char name[CAT_LEN+1];
X};
X
Xtypedef struct ignore IGNORE;
X
X/*
X *		This structure is used to store macro definitions. Note that
X *		a single block is used to store both the macro and its definition.
X *		The structure is dynamically sized at runtime, to fit whatever
X *		size definition is given. The text[] array (nominally 1) gets
X *		expanded to hold the definition.
X */
Xtypedef struct {
X	char	name[MACROLEN];	/* Name of this macro					*/
X	int		size;			/* Size of macro text		 			*/
X	char	text[1];		/* Start of macro text					*/
X} MACRO;
X
X#define MACROSIZE		(sizeof(MACRO) - 1)		/* The -1 is for text[1] */
X
X/*
X *		This structure holds a block of parameters for a macro that is
X *		executing.
X */
Xtypedef struct {
X	int		size;			/* Size of parameter block				*/
X	char	params[1];		/* Start of parameter block				*/
X} PARAM;
X
X#define PARAMSIZE		(sizeof(PARAM) - 1)		/* The -1 is for params[1] */
X
X
X/*
X *		This macro checks to make sure that the file database has been
X *		read in. This is delayed until as late as possible, so that if
X *		the script file contains errors, the errors will be spotted BEFORE
X *		the database is read in. The primary goal here is to save the user
X *		having to wait for 200K or so of database to be read in, just so
X *		they can see they have an error in their script. Instead, the
X *		database is only read in when a command cannot execute without
X *		having access to the files. Such commands are SORT, SCAN, LIST,
X *		CHECKFILES and FOREIGN.
X */
X#define CHECKDATABASE() {if (!readfiles) readdatabase(databasename);}
X
X/*
X *		Global variables, accessible to all modules
X */
X
X#ifdef GLOBAL
X#undef GLOBAL
X#endif
X#ifdef MAIN
X#define GLOBAL
X#else
X#define GLOBAL extern
X#endif
X
XGLOBAL char *script;			/* Array for storing the script				*/
XGLOBAL long scriptsize;			/* The size of the current script			*/
XGLOBAL long scriptpos;			/* Position in the current script			*/
XGLOBAL long linenum;			/* Line number in script file				*/
XGLOBAL char out[MAXOUT];		/* Array for storing the output lines		*/
XGLOBAL char combuf[MAXCOM];		/* Buffer to hold a single command			*/
XGLOBAL char formatstring[MAXCOM];/* Used to store the output format string	*/
XGLOBAL char databasename[256];	/* The name of the BBS-PC! file database	*/
XGLOBAL char configname[256];	/* The name of the BBS-PC! config file		*/
XGLOBAL char scriptname[256];	/* The name of the current script file		*/
XGLOBAL UDHEAD **ptrblock;		/* Array of pointers to file records		*/
XGLOBAL long numrecs;			/* The number of file headers read in		*/
XGLOBAL long compos;				/* Position on the current command line		*/
XGLOBAL long comlen;				/* Length of current command line			*/
XGLOBAL BPTR outfile;			/* Output file (Default is stdout)			*/
XGLOBAL BPTR errorfile;			/* Standard error file (screen usually)		*/
XGLOBAL BPTR dirlock;			/* Lock used when scanning directories		*/
XGLOBAL struct FileInfoBlock *fib;/* Global fib struct on longword boundary	*/
XGLOBAL int checkfiles;			/* TRUE if file directories were scanned	*/
XGLOBAL int readfiles;			/* TRUE if database file has been read in	*/
XGLOBAL int toscreen;			/* TRUE if output is to screen				*/
XGLOBAL int totalbytes;			/* Total number of bytes output so far		*/
XGLOBAL int totalfiles;			/* Total number of files output so far		*/
XGLOBAL int curbytes;			/* Number of bytes output by last LIST/SCAN	*/
XGLOBAL int curfiles;			/* Number of files output by last LIST/SCAN	*/
XGLOBAL int sorted;				/* True if file array has been sorted		*/
XGLOBAL EXPR tree[MAXEXPR];		/* Array to hold parsed SELECT expression	*/
XGLOBAL int numdirentries;		/* Number of fake directory entries			*/
XGLOBAL DIRENTRY *direntries[MAXDIRENT];/* Storage for ptrs to dir entries	*/
XGLOBAL char dirnames[NUM_SECT][DIRNAMESIZE];/* Storage for directory names	*/
XGLOBAL CFGINFO config[1];		/* BBS-PC! Configuration file structure		*/
XGLOBAL MACRO *macros[MAXMACRO];	/* Array of ptrs to macro definitions		*/
XGLOBAL int nummacros;			/* Number of macros currently defined		*/
XGLOBAL PARAM *params[MAXNEST];	/* Array of pointers to macro parameters	*/
XGLOBAL int nestlevel;			/* Current macro nest level					*/
XGLOBAL int tracemode;			/* Trace mode; TRUE if tracing enabled		*/
XGLOBAL IGNORE *firstignore;		/* Pointer to first filename to ignore		*/
X
X/*
X *		Global functions, accessible everywhere
X */
X
Xchar *format();		/* Format output string from file header + format spec	*/
Xchar *echoformat();	/* Formats output string for ECHO command				*/
Xchar *itoa();		/* Convert integer into ASCII format					*/
Xchar *getstring();	/* Get next string from command buffer					*/
Xvoid *mymalloc();	/* Safe memory tracker that handles out of memory 		*/
Xvoid *SafeAllocMem();/* Safe AllocMem that handles out of memory			*/
Xvoid execscript();	/* Executes all the commands in the current script		*/
Xvoid Cleanup();		/* Frees resources and exits program					*/
Xvoid chkabort();	/* Checks for Control-C, and aborts if detected			*/
Xvoid putstring();	/* Outputs string to standard I/O						*/
Xvoid flushout();	/* Flushes data buffer to output file					*/
Xvoid parse();		/* Parse command line and build expression tree			*/
Xvoid readdatabase();/* Reads in the BBS-PC! UDHEAD.DAT file database		*/
Xvoid readconfigfile();/* Reads in the BBS-PC! CFGINFO.DAT file				*/
Xvoid print();		/* Prints a string to stderr							*/
Xvoid com_select();	/* SELECT, specifies criteria for files to select		*/
Xvoid com_checkfiles();/* Scans file directories, updating catlague entries	*/
Xvoid com_sort();	/* Sorts file catalogue into a particular order			*/
Xvoid com_foreign();	/* Prints a list of all the unknown foreign files		*/
Xvoid com_norequest();/* Stop AmigaDos from putting up requesters			*/
Xint match();		/* Returns TRUE if record matches current selection		*/
Xint scandir();		/* Scans directory for files, returns TRUE if continue	*/
Xint sortcmp();		/* Internal routine used for sorting files				*/
X
X/*
X *		print3()
X *		--------
X *		Prints 3 strings to standard error
X */
X#define print2(s1,s2)	 (print(s1), print(s2))
X#define print3(s1,s2,s3) (print(s1), print(s2), print(s3))
X
X/*
X *		The list of identifiers used by SORT and SELECT.
X */
X
X#define MAXINDEX 16
X
X#define I_ANY			0
X#define I_ACCESS		1
X#define I_BINARY		2
X#define I_COMMENT		3
X#define I_DISKNAME		4
X#define I_SECTION		5
X#define I_ONLINE		6
X#define I_LOCAL			7
X#define I_NAME			8
X#define I_OWNER			9
X#define I_PATHNAME		10
X#define I_DIRECTORY		11
X#define I_DISKDIRNUM	12
X#define I_VALID			13
X#define I_DATE			14
X#define I_SIZE			15
X#define I_KSIZE			16
X
XGLOBAL struct {
X	int tag;
X	char *name;
X} indexes[MAXINDEX]
X#ifdef MAIN
X= {
X
X{	I_ACCESS,		"ACCESS"},		/* Number of times file was accessed	*/
X{	I_BINARY,		"BINARY"},		/* True if Binary, False if text		*/
X{	I_COMMENT,		"COMMENT"},		/* The file comment						*/
X{	I_DISKNAME,		"DISKNAME"},	/* The name of the file on disk			*/
X{	I_SECTION,		"SECTION"},		/* # of the section the file is in		*/
X{	I_ONLINE,		"ONLINE"},		/* True if file online					*/
X{	I_LOCAL,		"LOCAL"},		/* True if file uploaded locally		*/
X{	I_NAME,			"NAME"},		/* The name of the file in the catalog	*/
X{	I_OWNER,		"OWNER"},		/* The name of uploader of the file		*/
X{	I_PATHNAME,		"PATHNAME"},	/* Pathname to file on disk				*/
X{	I_DIRECTORY,	"DIRECTORY"},	/* # of directory file is in			*/
X{	I_DISKDIRNUM,	"DISKDIRNUM"},	/* # of disk directory file is in		*/
X{	I_VALID,		"VALID"},		/* True if file is Valid				*/
X{	I_DATE,			"DATE"},		/* The date the file was uploaded		*/
X{	I_SIZE,			"SIZE"},		/* The size of the file					*/
X{	I_KSIZE,		"KSIZE"}		/* The size of the file in K			*/
X}
X#endif
X;
X
XGLOBAL char *months[]
X#ifdef MAIN
X= {
X	"xxx",
X	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
X}
X#endif
X;
X
X/*
X *		Special characters that can occur in the script
X */
X#define CHAR_TAB		'\t'
X#define CHAR_SPACE		' '
X#define CHAR_HASH		'#'
X#define CHAR_NL			'\n'
X#define CHAR_ESC		'\\'
X#define CHAR_QUOTE		'\''
X#define CHAR_QUOTES		'\"'
X#define CHAR_SEMI		';'
X#define CHAR_COMMA		','
X#define CHAR_EQUALS		'='
X#define CHAR_DOLLAR		'$'
X#define CHAR_NULL		'\0'
X
X#define CHAR_ASCEND		'+'
X#define CHAR_DESCEND	'-'
X
X/*
X *		The following symbols are used in the expression tree
X */
X
X/*
X *		Boolean operations, which share storage with field selectors
X */
X
X#define E_AND		30	/* <left> AND <right>	*/
X#define	E_OR		31	/* <left> OR <right>	*/
X#define	E_NOT		32	/* NOT <left>			*/
X#define E_ALL		33	/* Always true			*/
X
X/*
X *		Comparison operators, used for comparing record elements
X */
X#define	E_EQ		34	/* Record == Value		*/
X#define E_NE		35	/* Record != Value		*/
X#define E_LT		36	/* Record <  Value		*/
X#define E_GT		37	/* Record >  Value		*/
X#define E_LE		38	/* Record <= Value		*/
X#define E_GE		39	/* Record >= Value		*/
X
X#define E_OPENPAR	40	/* Open parameter token		*/
X#define E_CLOSEPAR	41	/* Close parameter token	*/
X
X#define E_NUMBER	42	/* Any numeric value		*/
X#define E_STRING	43	/* Anything in quotes		*/
X#define E_END		44	/* End of command line		*/
X
X#define E_TEXT		45	/* File is a text file		*/
X#define E_REMOTE	46	/* File is a remote file	*/
X#define E_INVALID	47	/* File is invalid			*/
X#define E_OFFLINE	48	/* File is offline			*/
X
X
X/*
X *		Wildcard special fields for strings
X */
X#define tokentowild(x) ((x)+50)
X
X#define W_OWNER		tokentowild(I_OWNER)
X#define W_NAME		tokentowild(I_NAME)
X#define W_DISKNAME	tokentowild(I_DISKNAME)
X#define W_COMMENT	tokentowild(I_COMMENT)
END_OF_FILE
if test 12473 -ne `wc -c <'src/bbsindex.h'`; then
    echo shar: \"'src/bbsindex.h'\" unpacked with wrong size!
fi
# end of 'src/bbsindex.h'
fi
if test -f 'src/checkfiles.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/checkfiles.c'\"
else
echo shar: Extracting \"'src/checkfiles.c'\" \(6592 characters\)
sed "s/^X//" >'src/checkfiles.c' <<'END_OF_FILE'
X/*
X * CHECKFILES.C
X *
X * This module contains routines to read in the disk directory, search
X * the file database for each filename found to see if it matches, etc.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X#include "bbsindex.h"
X
X/*
X *		addunknown()
X *		------------
X *		Adds a new entry to the list of unknown files found. If necessary,
X *		more memory will be allocated to hold the new entry.
X */
Xint addunknown(name, size, date, dirnum)
Xchar *name;
Xlong size;
Xint date, dirnum;
X{
X	static DIRENTRY *dirbase, *dir;
X
X	if (numdirentries >= MAXDIRENT) {
X		scripterror("directory table full. Skipping rest of files\n");
X		return (FALSE);
X	}
X	if ((numdirentries % DIRFRAG) == 0) 	/* Allocate new block */
X		dirbase = mymalloc(DIRENTRYSIZE * DIRFRAG);
X
X	dir = &dirbase[numdirentries % DIRFRAG];
X
X	strcpy(dir->name, name);
X	dir->size	= size;
X	dir->date	= date;
X	dir->dirnum	= dirnum;
X
X	direntries[numdirentries++] = dir;
X	return (TRUE);
X}
X
X/*
X *		find()
X *		------
X *		Does a binary search of the file records looking for the specified
X *		filename. Returns pointer to the file record if found, NULL if not
X *		found.
X */
XUDHEAD *find(name)
Xchar *name;
X{
X	long low = 0, high = numrecs -1;
X	long mid;
X	int cmp;
X
X	while (high >= low) {
X		mid = (low + high) / 2;
X		cmp = stricmp(ptrblock[mid]->disk_name, name);
X		if (!cmp && ptrblock[mid]->type == 0)		/* Found match */
X			return (ptrblock[mid]);
X		if (cmp > 0)						/* Too far ahead, go backwards */
X			high = mid-1;
X		else								/* Too far below, go forwards */
X			low  = mid+1;
X	}
X	return (NULL);
X}
X
X/*
X *		scandir()
X *		---------
X *		Scans directory, adding the files found either to the 'unknown'
X *		list or updating the pointers in the main file database appropriately.
X */
Xint scandir(dirname, dirnum)
Xchar *dirname;
Xint dirnum;
X{
X	UDHEAD *f;
X	struct tm *filedate;
X	int bbspcdate;
X
X	dirlock = Lock(dirname, ACCESS_READ);
X	if (dirlock == NULL) {
X		scripterror("can't find directory ");
X		print2(dirname, ", skipping\n");
X		return (TRUE);
X	}
X
X	if (!Examine(dirlock, fib)) {
X		scripterror("can't examine directory ");
X		print2(dirname, ", skipping\n");
X		UnLock(dirlock);
X		dirlock = NULL;
X		return (TRUE);
X	}
X
X	while (ExNext(dirlock, fib)) {
X		chkabort();
X		if (fib->fib_DirEntryType > 0)	/* Skip over directories */
X			continue;
X		if (f = find(fib->fib_FileName)) {
X			f->online = 1;
X			f->valid = (fib->fib_Size == f->length);
X			f->dirnum = dirnum;
X		} else {
X			/*
X			 *		Convert AmigaDOS date (# days since 1/1/78, # mins
X			 *		since midnight) into ANSI date (# seconds since 1/1/70)
X			 *		then convert that into BBS-PC! format.
X			 */
X			long ansidate;
X			ansidate = (fib->fib_Date.ds_Days + (365 * 8) + 2) * 86400;
X			filedate = gmtime(&ansidate);
X			bbspcdate = (((filedate->tm_year * 13) +
X						  (filedate->tm_mon+1)) * 32) +
X						   filedate->tm_mday;
X			if (!addunknown(fib->fib_FileName, fib->fib_Size, bbspcdate,
X																	dirnum)) {
X				UnLock(dirlock);
X				dirlock = NULL;
X				return (FALSE);
X			}
X
X		}
X	}
X	if (IoErr() != ERROR_NO_MORE_ENTRIES) {
X		scripterror("error reading directory ");
X		print2(dirname, ", skipping\n");
X	}
X	UnLock(dirlock);
X	dirlock = NULL;
X	return (TRUE);
X}
X
X/*
X *		diskcmp()
X *		---------
X *		Compares the disk filenames of two file headers, and returns true
X *		if they are equal. Used by the sort routine in check_files().
X */
Xint diskcmp(f1,f2)
XUDHEAD **f1, **f2;
X{
X	return (stricmp((*f1)->disk_name, (*f2)->disk_name));
X}
X
X/*
X *		com_checkfiles()
X *		----------------
X *		This command scans the file directories specified on the command
X *		line (or if none, then using those specified in the CFGINFO.DAT
X *		file). Each file in the catalogue is searched for in the file
X *		catalogue, and those that are found are marked as "Online"; if
X *		the disk filesize matches the catalogue filesize, then their
X *		"valid" flag is also set. Files which are not found are stored
X *		in a seperate array, which can be printed with the FOREIGN command.
X *		After this, any other files listed in the IGNORE list are also marked
X *		as valid.
X *
X *		Note that before the scan, the files are sorted into alphabetical
X *		order, by diskname, to allow a binary search to be done. Afterwards,
X *		if this disrupted a previous sorting order, they are sorted back
X *		to their original state.
X */
Xvoid com_checkfiles()
X{
X	int dirnum;
X	IGNORE *ig;
X	UDHEAD *f;
X
X	if (checkfiles)
X		return;
X
X	CHECKDATABASE();
X	qsort(ptrblock, numrecs, sizeof(UDHEAD *), diskcmp);
X	if (compos >= comlen) {
X		readconfigfile();
X		for (dirnum = 0; dirnum < NUM_SECT && dirnames[dirnum][0]; dirnum++) {
X			chkabort();
X			if (!scandir(dirnames[dirnum], dirnum))
X				break;
X		}
X	} else {
X		for (dirnum = 0; dirnum < NUM_SECT; dirnum++) {
X			if (compos >= comlen)
X				break;
X			strcpy(dirnames[dirnum], getstring());
X			chkabort();
X			if (!scandir(dirnames[dirnum], dirnum))
X				break;
X		}
X	}
X	/*
X	 *		Now mark any "ignored" files as valid.
X	 */
X	for (ig = firstignore; ig; ig = ig->next) {
X		if (f = find(ig->name))
X			f->valid = 1;
X	}
X	chkabort();
X	if (sorted)
X		qsort(ptrblock, numrecs, sizeof(UDHEAD *), sortcmp);
X	checkfiles = TRUE;
X}
X
X/*
X *		com_foreign()
X *		-------------
X *		This command prints out the list of foreign files, i.e. files
X *		not in the catalogue, using the format string setup with
X *		FORMAT. Not all fields are valid.
X */
Xvoid com_foreign()
X{
X	/*
X	 *		Standard file header template for foreign files.
X	 *		Note file[] is an array, so we can say file->x instead of file.x
X	 */
X	static UDHEAD file[1] = {{
X		0,						/* Record type (== valid)				*/
X		1, 1, 1, 1, 			/* Local, Binary, Valid and Online :-)	*/
X		0,						/* Directory number is set later		*/
X		"",						/* Catalogue filename, filled in later	*/
X		0,						/* Date is set later					*/
X		0, 0, 0,				/* Section 0, Directory 0, no accesses	*/
X		0,						/* Length is set later					*/
X		"",						/* Disk name is set later				*/
X		"AmigaDos",				/* The file owner						*/
X		"Directory = ",			/* File comment, set later				*/
X	}};
X
X	int i;
X
X	if (!checkfiles) {
X		scripterror("can't do FOREIGN before CHECKFILES\n");
X		Cleanup(10);
X	}
X
X	for (i = 0; i < numdirentries; i++) {
X		chkabort();
X		/* Setup actual file parameters for format() */
X		strcpy(file->disk_name, direntries[i]->name);
X		strncpy(file->cat_name, direntries[i]->name, CAT_LEN);
X		strncpy(file->desc+12, dirnames[direntries[i]->dirnum], DESC_LEN-12);
X		file->desc[DESC_LEN] = CHAR_NULL;
X		file->length	= direntries[i]->size;
X		file->date		= direntries[i]->date;
X		file->dirnum	= direntries[i]->dirnum;
X		format(out, MAXOUT, formatstring, file, checkfiles);
X		putstring(out);
X	}
X}
END_OF_FILE
if test 6592 -ne `wc -c <'src/checkfiles.c'`; then
    echo shar: \"'src/checkfiles.c'\" unpacked with wrong size!
fi
# end of 'src/checkfiles.c'
fi
if test -f 'src/expression.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/expression.c'\"
else
echo shar: Extracting \"'src/expression.c'\" \(11470 characters\)
sed "s/^X//" >'src/expression.c' <<'END_OF_FILE'
X/*
X * EXPRESSION.C
X *
X * This module contains the routines needed to parse and evaluate an
X * expression. This is used while selecting which files to print.
X *
X *	This is the BNF for the set of expressions recognised by the
X *	database searcher. It doesn't contain any semantic information however.
X *
X *	Expression			::= Boolean | Boolean Operator Expression | ALL
X *	Operator			::= AND | OR 
X *	Boolean				::= NOT Boolean				|
X *						    '(' Expression ')'		|
X *							BoolIdent				|
X *							NumIdent	Op Value	|
X *							StringIdent Op String	|
X *							DateIdent	Op Date
X *	Op					::= '<' | '>' | '<=' | '>=' | '=' | '<>'
X *	BoolIdent			::= BINARY | ONLINE | LOCAL | VALID
X *	StringIdent			::= COMMENT | DISKNAME | NAME | OWNER | PATHNAME
X *	NumIdent			::= ACCESS | SECTION | DIRECTORY | DISKDIRNUM |
X *							SIZE | KSIZE
X *	DateIdent			::= DATE
X *	String				::= (Text enclosed in quotes)
X *	Value				::= (A number)
X *	Date				::= (A date in the format "dd-mon-yy")
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X#define mystrcmp stricmp		/* Make match() insensitive to case */
X
X/*
X *		BNF procedures
X */
X
Xvoid bnf_op(), bnf_date(), bnf_expression(), bnf_boolean();
X
X/*
X *		Global variable(s)
X */
X
Xstatic int treepos;				/* Next free entry in tree array	*/
Xstatic int curtoken;			/* Current token					*/
Xstatic int curvalue;			/* Current number with E_NUMBER		*/
Xstatic char curstring[MAXCOM];	/* Current string with E_STRING		*/
X
X/*
X *		Tokens recognised as special in expressions
X */
X
Xstruct {
X	int tag;
X	char *name;
X} tokens[] = {
X	
X	{	E_EQ,			"="			},
X	{	E_NE,			"<>"		},
X	{	E_LE,			"<="		},
X	{	E_GE,			">="		},
X	{	E_LT,			"<"			},
X	{	E_GT,			">"			},
X	{	E_AND,			"AND"		},
X	{	E_OR,			"OR"		},
X	{	E_NOT,			"NOT"		},
X	{	E_ALL,			"ALL"		},
X	{	E_OPENPAR,		"("			},
X	{	E_CLOSEPAR,		")"			},
X	{	E_TEXT,			"TEXT"		},
X	{	E_REMOTE,		"REMOTE"	},
X	{	E_INVALID,		"INVALID"	},
X	{	E_OFFLINE,		"OFFLINE"	},
X	{	NULL,			NULL		}
X};
X
X/*
X *		wild()
X *		------
X *		This routine does a wildcard match on the two specified strings.
X *		The first string contains the string to check, and the second string
X *		containts the wildcard pattern to check against. The special wild
X *		card characters are '?' which matches any single characters, and
X *		'*' which matches any number of characters. 0 is returned if the
X *		the two strings match, else a +ve or -ve number.
X *
X */
Xint wild(s,w)
Xchar *s;
Xchar *w;
X{
X	char *p;
X	char ch;
X
X	while (*w && *s) {
X		switch (*w) {
X
X			case '*':
X				ch = toupper(w[1]);
X				if (!ch)
X					return (0);
X				for (p = s; *p; p++) {
X					if (toupper(*p) == ch || ch == '?') {
X						if (!wild(p,w+1))
X							return (0);
X					}
X				}
X				return (toupper(*p)-ch);
X
X			case '?':
X				break;
X
X			default:
X				if (toupper(*s) != toupper(*w))
X					return (toupper(*s)-toupper(*w));
X		}
X		w++;
X		s++;
X	}
X	return (toupper(*s)-toupper(*w));
X}
X
X
X/*
X *		Dirty great macro to compare two values according to an operator
X */
X#define e_cmp(v1,op,v2)						\
X	switch (op) {							\
X		case E_EQ: return ((v1) == (v2));	\
X		case E_NE: return ((v1) != (v2));	\
X		case E_LT: return ((v1) <  (v2));	\
X		case E_GT: return ((v1) >  (v2));	\
X		case E_LE: return ((v1) <= (v2));	\
X		case E_GE: return ((v1) >= (v2));	\
X	}
X
X#define e_numcmp(n)		e_cmp(n, e->op, e->num)
X#define e_strcmp(s)		e_cmp(mystrcmp(s,e->text), e->op, 0)
X#define e_wildcmp(s)	e_cmp(wild(s,e->text), e->op, 0)
X
X/*
X *		match()
X *		-------
X *		Scans the the parse tree, and returns TRUE if the current
X *		record matches the criteria in the tree, else FALSE.
X *
X *		Aside: Aren't C macros wonderful? Just think how long this function
X *		would be if it wasn't for the above e_cmp, e_numcmp and e_strcmp
X *		macros.
X */
Xint match(p, e)
XUDHEAD *p;
XEXPR *e;
X{
X	switch (e->field) {
X		
X		case E_AND:			return (match(p, e->left) && match(p, e->right));
X		case E_OR:			return (match(p, e->left) || match(p, e->right));
X		case E_NOT:			return (!match(p, e->left));
X		case E_ALL:			return (TRUE);
X		case I_LOCAL:		return ((int)p->local);
X		case I_BINARY:		return ((int)p->bin);
X		case I_VALID:		return ((int)p->valid);
X		case I_ONLINE:		return ((int)p->online);
X		case E_REMOTE:		return (!(int)p->local);
X		case E_TEXT:		return (!(int)p->bin);
X		case E_INVALID:		return (!(int)p->valid);
X		case E_OFFLINE:		return (!(int)p->online);
X		case I_ACCESS:		e_numcmp(p->accesses);
X		case I_DATE:		e_numcmp(p->date);
X		case I_DIRECTORY:	e_numcmp(p->dir);
X		case I_DISKDIRNUM:	e_numcmp(p->dirnum);
X		case I_KSIZE:		e_numcmp(BTOK(p->length));
X		case I_SECTION:		e_numcmp(p->section);
X		case I_SIZE:		e_numcmp(p->length);
X		case I_DISKNAME:	e_strcmp(p->disk_name);
X		case I_COMMENT:		e_strcmp(p->desc);
X		case I_NAME:		e_strcmp(p->cat_name);
X		case I_OWNER:		e_strcmp(p->owner);
X		case W_DISKNAME:	e_wildcmp(p->disk_name);
X		case W_COMMENT:		e_wildcmp(p->desc);
X		case W_NAME:		e_wildcmp(p->cat_name);
X		case W_OWNER:		e_wildcmp(p->owner);
X	}
X}
X
X/*
X *		newnode()
X *		---------
X *		This function allocates a new node from the tree array, and returns
X *		a pointer to it. If overflow occurs, it aborts with an error message.
X */
X
XEXPR *newnode()
X{
X	treepos++;
X	if (treepos >= MAXEXPR) {
X		scripterror("expression to complex\n");
X		Cleanup(10);
X	}
X	return (&tree[treepos]);
X}
X
X/*
X *		readtoken()
X *		-----------
X *		This function reads the next token from the command line, starting
X *		at position curpos. curtoken is set to the type of token received.
X *		If it was E_STRING, then curstring points to the string it
X *		represents (without the quotes). If it was E_NUM, then curvalue
X *		points to the number represented. If the end of the line is
X *		reached, E_END is automatically set.
X */
X
X#define nextch()		(ch = combuf[compos++])
X#define ungetnext()		(compos--)
X
Xvoid readtoken()
X{
X	char *p = curstring, ch;
X	int i;
X
X	if (compos >= comlen) {
X		curtoken = E_END;
X		return;
X	}
X
X	do {
X		nextch();
X	} while (ch == CHAR_SPACE);
X
X	if (ch == CHAR_QUOTES) {			/* Handle string in quotes */
X		curtoken = E_STRING;
X		nextch();
X		if (*p == CHAR_NULL) {
X			curtoken = E_END;
X			return;
X		}
X		do {
X			*p++ = ch;
X			nextch();
X		} while (ch && ch != CHAR_QUOTES);
X		*p = CHAR_NULL;
X		return;
X	}
X
X	if (isdigit(ch)) {				/* Numeric value? */
X		curtoken = E_NUMBER;
X		curvalue = 0;
X		do {
X			curvalue = curvalue * 10 + (ch - '0');
X			nextch();
X		} while (isdigit(ch));
X		ungetnext();
X		return;
X	}
X
X	if (isalpha(ch)) {				/* Alphabetic identifier? */
X		do {
X			*p++ = ch;
X			nextch();
X		} while (isalpha(ch));
X	} else if (ch == CHAR_NULL) {	/* At end of line? */
X		curtoken = E_END;
X		return;
X	} else {						/* Must be punctuation */
X		do {
X			*p++ = ch;
X			nextch();
X		} while (ch && !isalpha(ch) && !isdigit(ch) && ch != CHAR_SPACE
X									&& ch != CHAR_QUOTES);
X	}
X	*p = CHAR_NULL;
X	ungetnext();
X
X	/* Check for character A - F */
X	if (curstring[1] == CHAR_NULL) {
X		switch (curstring[0]) {
X			case 'A': case 'B': case 'C':
X			case 'D': case 'E': case 'F':
X				curvalue = curstring[0] + 10 - 'A';
X				curtoken = E_NUMBER;
X				return;
X		}
X	}
X
X	/* Now find token that matches string */
X	for (i = 0; i < MAXINDEX; i++) {
X		if (!strcmp(curstring, indexes[i].name)) {
X			curtoken = indexes[i].tag;
X			return;
X		}
X	}
X	for (i = 0; tokens[i].name; i++) {
X		if (!strcmp(curstring, tokens[i].name)) {
X			curtoken = tokens[i].tag;
X			return;
X		}
X	}
X	scripterror("unrecognised symbol '");
X	print2(curstring,"' in expression\n");
X	Cleanup(10);
X}
X
X
X/*
X *		com_select()
X *		------------
X *		This command parses the expression in the command buffer starting
X *		at position compos, and builds an expression tree representing it.
X *		This tree is stored in tree[], starting at element 0. This expression
X *		tree is used when match() is called.
X */
X
Xvoid com_select()
X{
X	treepos = 0;
X	readtoken();
X	bnf_expression(tree);
X}
X
X/*
X *		bnf_expression()
X *		----------------
X *		Parses the BNF line:
X *
X *		Expression ::= Boolean | Boolean Operator Expression | ALL
X *
X */
Xvoid bnf_expression(e)
XEXPR *e;
X{
X	EXPR dummy;
X
X	if (curtoken == E_ALL)
X		e->field = E_ALL;
X	else {
X		bnf_boolean(&dummy);
X		if (curtoken == E_AND || curtoken == E_OR) {
X			e->field   = curtoken;
X			e->left    = newnode();
X			e->right   = newnode();
X			*(e->left) = dummy;
X			readtoken();
X			bnf_expression(e->right);
X		} else
X			*e = dummy;
X	}
X}
X
X/*
X *		bnf_boolean()
X *		-------------
X *		Parses the following BNF line:
X *
X *		Boolean	::= NOT Boolean				|
X *				    '(' Expression ')'		|
X *					BoolIdent				|
X *					NumIdent	Op Value	|
X *					StringIdent Op String	|
X *					DateIdent	Op Date
X */
Xvoid bnf_boolean(e)
XEXPR *e;
X{
X	char *p;
X
X	e->field = curtoken;
X	switch (curtoken) {
X
X		case E_NOT:
X			e->left = newnode();
X			readtoken();
X			bnf_boolean(e->left);
X			break;
X
X		case E_OPENPAR:
X			readtoken();
X			bnf_expression(e);
X			if (curtoken != E_CLOSEPAR) {
X				scripterror("missing close parenthesis\n");
X				Cleanup(10);
X			}
X			readtoken();
X			break;
X
X		case I_BINARY:
X		case I_VALID:
X		case I_ONLINE:
X		case I_LOCAL:
X		case E_TEXT:
X		case E_INVALID:
X		case E_OFFLINE:
X		case E_REMOTE:
X			readtoken();
X			break;
X
X		case I_ACCESS:
X		case I_SECTION:
X		case I_DIRECTORY:
X		case I_DISKDIRNUM:
X		case I_SIZE:
X		case I_KSIZE:
X			readtoken();
X			bnf_op(e);
X			if (curtoken != E_NUMBER) {
X				scripterror("number expected in expression\n");
X				Cleanup(10);
X			}
X			e->num = curvalue;
X			readtoken();
X			break;
X
X		case I_COMMENT:
X		case I_DISKNAME:
X		case I_NAME:
X		case I_OWNER:
X		case I_PATHNAME:
X			readtoken();
X			bnf_op(e);
X			if (curtoken != E_STRING) {
X				scripterror("string expected in expression\n");
X				Cleanup(10);
X			}
X			e->text = mymalloc(strlen(curstring)+1);
X			strcpy(e->text, curstring);
X			/*
X			 *		Now check the string, and see if it contains any wildcard
X			 * 		characters. If it does, use operation W_{string}
X			 *		instead of E_{string}. If it doesn't, use the standard
X			 *		strcmp which is much faster.
X			 */
X			for (p = curstring; *p; *p++) {
X				if (*p == '*' || *p == '?') {
X					e->field = tokentowild(e->field);
X					break;
X				}
X			}
X			readtoken();
X			break;
X
X		case I_DATE:
X			readtoken();
X			bnf_op(e);
X			bnf_date(e);
X			break;
X
X		default:
X			scripterror("error in expression\n");
X			Cleanup(10);
X	}
X}
X
X/*
X *		bnf_op
X *		------
X *		Sets the operation field of the specified expression to the value
X *		of the next token on the command line.
X */
Xvoid bnf_op(e)
XEXPR *e;
X{
X	switch (curtoken) {
X		case E_EQ:
X		case E_NE:
X		case E_LT:
X		case E_GT:
X		case E_LE:
X		case E_GE:
X			e->op = curtoken;
X			readtoken();
X			break;
X
X		default:
X			scripterror("expected comparison operator\n");
X			Cleanup(10);
X	}
X}
X
X/*
X *		bnf_date()
X *		-----------
X *		Reads a date from the command line, validates it, and sets the
X *		'num' field in the expression to its numerical value. The date
X *		is in the format "dd-mon-yy", e.g. "15-Jun-89"
X */
Xvoid bnf_date(e)
XEXPR *e;
X{
X	int day, month, year;
X
X	if (curtoken != E_STRING) {
X		scripterror("expected date string in expression\n");
X		Cleanup(10);
X	}
X
X	if (strlen(curstring) != 9 ||
X			curstring[2] != '-' || curstring[6] != '-') {
X		scripterror("Invalid date format (should be dd-mon-yy)\n");
X		Cleanup(10);
X	}
X
X	day = atoi(curstring);
X	year = atoi(curstring+7);
X	for (month = 0; month < 12 && strnicmp(curstring+3, months[month], 3);
X														month++)
X		;
X	if ((day < 1 || day > 31) || (month < 1 || month > 12) ||
X				(year < 1 || year > 99)) {
X		scripterror("Invalid date format (should be dd-mon-yy)\n");
X		Cleanup(10);
X	}
X	e->num = (((year * 13) + month) << 5) + day;
X	readtoken();
X}
END_OF_FILE
if test 11470 -ne `wc -c <'src/expression.c'`; then
    echo shar: \"'src/expression.c'\" unpacked with wrong size!
fi
# end of 'src/expression.c'
fi
if test -f 'src/makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/makefile'\"
else
echo shar: Extracting \"'src/makefile'\" \(1075 characters\)
sed "s/^X//" >'src/makefile' <<'END_OF_FILE'
X#
X# Lattice LKM makefile, for Lattice C V5.04
X#
X# BBSindex (C) Copyright Eddy Carroll, December 1989
X#
X
X
XCFLAGS	= -cus -ms -j88i #-D3
XOPT		= -O
X#BFLAGS	= sc sd map ram:map addsym
XBFLAGS	= sc sd map ram:map nd
XASM		= lc:asm
X#START	= lib:catch.o
XSTART	= tiny.o
X
X.c.o:
X	lc $(CFLAGS) $(OPT) -Hsystem.sym $*.c
X.a.o:
X	$(ASM) -isys:include/ -u $*.a
X.n.doc:
X	nro >$*.doc -ms:an $*.n
X.h.sym:
X	copy $*.h to ram:t/dummy.c 
X	lc $(CFLAGS) -ph -o$*.sym ram:t/dummy.c
X	delete ram:t/dummy.c
X
X#
X# Makefile dependencies
X#
XOBJS   = bbsindex.o checkfiles.o command.o expression.o format.o sort.o
X
Xall: bbsindex
X
Xbbsindex: $(OBJS) tiny.o
X    blink from $(START) $(OBJS) to bbsindex $(BFLAGS) lib lib:lc.lib
X
Xbbsindex.doc:	bbsindex.n
X
Xsystem.sym:		system.h
Xbbsindex.o:	 	bbsindex.c		system.sym  bbsindex.h  bbs.h
Xcheckfiles.o:	checkfiles.c	system.sym  bbsindex.h  bbs.h
Xcommand.o:		command.c		system.sym  bbsindex.h  bbs.h
Xexpression.o:	expression.c	system.sym  bbsindex.h  bbs.h
Xformat.o:		format.c		system.sym  bbsindex.h  bbs.h
Xsort.o:			sort.c			system.sym  bbsindex.h  bbs.h
Xtiny.o:			tiny.a
END_OF_FILE
if test 1075 -ne `wc -c <'src/makefile'`; then
    echo shar: \"'src/makefile'\" unpacked with wrong size!
fi
# end of 'src/makefile'
fi
if test -f 'src/sort.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/sort.c'\"
else
echo shar: Extracting \"'src/sort.c'\" \(3552 characters\)
sed "s/^X//" >'src/sort.c' <<'END_OF_FILE'
X/*
X * SORT.C
X *
X * This module contains the sorting routines that read sorting order from
X * the command line, and sort the file database according to that order.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X
X/*
X *		Arrays used to determine sorting order for SORT.
X */
X
Xint i_order[MAXINDEX+1];
Xint i_ascend[MAXINDEX+1];
X
X
X/*
X *		sortcmp()
X *		---------
X *		This function is called by qsort. It compares the two specified
X *		elements, and returns -ve, 0 or +ve to indicate whether the second
X *		record is greater than, equal or less than the first, using the
X *		sorting criteria defined in SORT.
X */
X
X/*
X *		DOCMP is a macro which sets cmp equal to the value of the enclosed
X *		expression if i_ascend[i] is true, or the negative of the expression
X *		if i_ascend is false.
X */
X#define DOCMP(x)	(cmp = (i_ascend[i] ? (x) : -(x)))
Xint sortcmp(ptr1,ptr2)
XUDHEAD **ptr1, **ptr2;
X{
X	UDHEAD *p1 = *ptr1, *p2 = *ptr2;
X	int i, cmp = 0;
X
X	/*
X	 *		Note safety exit - last element of i_order is guaranteed == I_ANY
X	 */
X	for (i = 0; !cmp; i++) {
X		switch (i_order[i]) {
X
X			case I_ANY:			return(0);
X
X			case I_ACCESS:		DOCMP(p1->accesses - p2->accesses);
X								break;
X			case I_BINARY:		DOCMP(p2->bin - p1->bin);
X								break;
X			case I_COMMENT:		DOCMP(stricmp(p1->desc, p2->desc));
X								break;
X			case I_DISKNAME:	DOCMP(stricmp(p1->disk_name, p2->disk_name));
X								break;
X			case I_SECTION:		DOCMP(p1->section - p2->section);
X								break;
X			case I_ONLINE:		DOCMP(p2->online - p1->online);
X								break;
X			case I_LOCAL:		DOCMP(p2->local - p1->local);
X								break;
X			case I_NAME:		DOCMP(strcmp(p1->cat_name, p2->cat_name));
X								break;
X			case I_OWNER:		DOCMP(strcmp(p1->owner, p2->owner));
X								break;
X			case I_PATHNAME:	DOCMP(strcmp(dirnames[p1->dirnum],
X											 dirnames[p2->dirnum]));
X								break;
X			case I_DIRECTORY:	DOCMP(p1->dir - p2->dir);
X								break;
X			case I_DISKDIRNUM:	DOCMP(p1->dirnum - p2->dirnum);
X								break;
X			case I_VALID:		DOCMP(p2->valid - p1->valid);
X								break;
X			case I_DATE:		DOCMP(p1->date - p2->date);
X								break;
X			case I_SIZE:		DOCMP(p1->length - p2->length);
X								break;
X			case I_KSIZE:		DOCMP(BTOK(p1->length) - BTOK(p2->length));
X								break;
X		}
X	}
X	return (cmp);
X}
X
X
X/*
X *		com_sort()
X *		----------
X *		This command sorts the file database into order, according to
X *		the indexes specified. Each index may be optionally followed by
X *		a + or - to indirect sorting direction (ascending or descending).
X *		There must be no space between the direction and the +/-.
X */
Xvoid com_sort()
X{
X	char *index, *p;
X	int i, j;
X	int ascend;
X
X	CHECKDATABASE();
X
X	sorted = TRUE;
X	for (i = 0; i < MAXINDEX && compos < comlen; i++) {
X		index = getstring();
X		p = index + strlen(index) - 1;
X
X		/*
X		 *		Now check to see if sorting direction specified, and adjust
X		 *		string if it was. Set 'ascend' to TRUE if ascending,
X		 *		else FALSE.
X		 */
X		ascend = TRUE;
X		if (*p == CHAR_ASCEND || *p == CHAR_DESCEND) {
X			if (*p == CHAR_DESCEND)
X				ascend = FALSE;
X			*p = CHAR_NULL;
X		}
X
X		/*
X		 *		Now match index, and setup appropriate entry in array
X		 */
X
X		for (j = 0; j < MAXINDEX && strcmp(index,indexes[j].name); j++)
X			;
X		if (j == MAXINDEX) {
X			scripterror("unknown index ");
X			print2(index, "\n");
X			Cleanup(10);
X		}
X		i_order[i]	= indexes[j].tag;
X		i_ascend[i]	= ascend;
X	}
X	if (i == 0) {
X		i_order[i] = I_NAME;
X		i_ascend[i++] = TRUE;
X	}
X	i_order[i] = I_ANY;
X
X	/* Finally, do the sort! */
X	qsort(ptrblock, numrecs, sizeof(UDHEAD *), sortcmp);
X}
END_OF_FILE
if test 3552 -ne `wc -c <'src/sort.c'`; then
    echo shar: \"'src/sort.c'\" unpacked with wrong size!
fi
# end of 'src/sort.c'
fi
if test -f 'src/system.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/system.h'\"
else
echo shar: Extracting \"'src/system.h'\" \(191 characters\)
sed "s/^X//" >'src/system.h' <<'END_OF_FILE'
X/* Include files for BBSindex */
X
X#include <exec/types.h>
X#include <proto/dos.h>
X#include <proto/exec.h>
X#include <proto/intuition.h>
X#include <ctype.h>
X#include <string.h>
X#include <time.h>
END_OF_FILE
if test 191 -ne `wc -c <'src/system.h'`; then
    echo shar: \"'src/system.h'\" unpacked with wrong size!
fi
# end of 'src/system.h'
fi
if test -f 'src/tiny.a' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/tiny.a'\"
else
echo shar: Extracting \"'src/tiny.a'\" \(6132 characters\)
sed "s/^X//" >'src/tiny.a' <<'END_OF_FILE'
X*:ts=8
X****************************************************************************
X*									   *
X* TINY.A				  (C) Copyright Eddy Carroll 1989  *
X* ~~~~~~				  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  *
X*									   *
X* Replacement startup code for Lattice C V5.04. Use instead of c.o	   *
X* This has many features stripped out to allow small utilities to have	   *
X* as small a filesize as possible. In particular, don't call any of the    *
X* stdio functions.							   *
X*									   *
X****************************************************************************
X
X	INCLUDE "exec/types.i"
X	INCLUDE "exec/alerts.i"
X	INCLUDE "exec/nodes.i"
X	INCLUDE "exec/lists.i"
X	INCLUDE "exec/ports.i"
X	INCLUDE "exec/libraries.i"
X	INCLUDE "exec/tasks.i"
X	INCLUDE "libraries/dos.i"
X	INCLUDE "libraries/dosextens.i"
X	INCLUDE "workbench/startup.i"
X	INCLUDE "exec/funcdef.i"
X	INCLUDE "exec/exec_lib.i"
X	INCLUDE "libraries/dos_lib.i"
X
XMAXARGS	    EQU 100	; Maximum number of command line arguments from CLI
XAbsExecBase EQU 4	; Welcome to the only fixed point in the universe
X
X* A useful macro to let us call library routines
Xcallsys macro
X	CALLLIB _LVO\1
X	endm
X
X	xdef	XCEXIT
X	xdef	exit
X	xref	LinkerDB
X	xref	_BSSBAS
X	xref	_BSSLEN
X
X	csect	text,0,0,1,2		* xref's after this are 16-bit reloc
X	xref	main			* Name of C program to start with.
X
Xstart:
X	movem.l d1-d6/a0-a6,-(a7)
XREGSIZE EQU	(6+7)*4
X	lea	REGSIZE(a7),A5		* Determine old stack pointer
X	move.l	a0,a2			* Save command pointer
X	move.l	d0,d2			* and command length
X	lea	LinkerDB,a4		* Load base register
X
X	move.l	AbsExecBase.W,a6
X	move.l	a6,SysBase(A4)
X	move.l	a7,_StackPtr(A4)	* Save stack ptr
X
X	suba.l	a1,a1
X	callsys	FindTask		* Find out our task ID
X	move.l	d0,a3
X
X	move.l	a5,D0	  		* get top of stack
X	sub.l	4(a5),D0		* compute bottom
X	add.l	#128,D0 		* allow for parms overflow
X	move.l	D0,_base(A4)		* save for stack checking
X
X	lea	DOSName(A4),A1
X	moveq.l	#0,D0
X	callsys	OpenLibrary
X	move.l	D0,DOSBase(A4)
X	bne	getcom
XnoDOS:
X	moveq.l #100,d0
X	bra	exit2
X
X*------ find command name:
Xgetcom:
X	move.l  pr_CLI(a3),a0
X	add.l	a0,a0
X	add.l	a0,a0
X	move.l	cli_CommandName(a0),a1
X	add.l	a1,a1
X	add.l	a1,a1
X
X*------ collect parameters:
X	move.l	d2,d0			* get command line length
X	moveq.l #0,d1
X	move.b	(a1)+,d1
X	move.l	a1,_ProgramName(A4)
X	add.l	d1,d0			* add length of command name
X	addq.l	#1,d0			* allow for space after command
X
X	clr.w	-(A7)			* set null terminator for command line
X	addq.l	#1,D0			* force to even number of bytes
X	andi.w	#$fffe,D0		* (round up)
X	sub.l	D0,A7			* make room on stack for command line
X	subq.l	#2,D0
X	clr.w	0(A7,D0)
X
X*------ copy command line onto stack
X	move.l	d2,d0			* get command line length
X	subq.l	#1,d0
X	add.l	d1,d2
X
Xcopy_line:
X	move.b	0(A2,D0.W),0(A7,D2.W)	* copy command line to stack
X	subq.l	#1,d2
X	dbf	d0,copy_line
X	move.b	#' ',0(a7,d2.w) 	* add space between command and parms
X	subq.l	#1,d2
X
Xcopy_cmd:
X	move.b	0(a1,d2.w),0(a7,d2.w)	* copy command name to stack
X	dbf	d2,copy_cmd
X	move.l	a7,a1			* Get pointer to new command line
X
X	sub.l	#(MAXARGS*4),a7		* Reserve space for argv[]
X	move.l	a7,a2			* Initialise base into array
X	move.l	a2,a3			* Save base of argv
X	moveq	#0,d2			* Initialise argc
X
X*
X* From here on down, A1 is pointer into command line
X*
Xbuild_argv:
X	bsr.s	getnext			* Read next character from line
X	bcs.s	doquote			* If quote, handle
X	beq.s	build_argv		* If white space, skip over it
X
X	lea	-1(a1),a0		* Get address of this parameter
X	bsr.s	bumpargv		* Store it to argv[] array
Xbuild_2:
X	bsr.s	getnext			* Get next character
X	bne.s	build_2			* If not white space, keep looking
X	clr.b	-1(a1)			* Zero-terminate current argument
X	bra.s	build_argv		* And go back to get next argument
X
Xdoquote:
X	move.l	a1,a0			* Get pointer to this argument
X	bsr.s	bumpargv		* Output it to argv[]
Xquote_2:
X	bsr.s	getnext			* Get next character
X	bcc.s	quote_2			* If not quote, keep looking
X	clr.b	-1(a1)			* Zero-terminate current argument
Xquote_3:
X	bsr.s	getnext			* Get next character
X	bne.s	quote_3			* Skip until space reached
X	beq.s	build_argv		* Go back and read next argument
X
Xbumpargv:
X	move.l	a0,(a2)+		* Output ptr to current argument
X	addq	#1,d2			* Increment argc
X	cmpi	#MAXARGS,d2		* Used up all our arguments yet?
X	bls.s	qrts			* If not, then return
X	moveq	#110,d0			* Else set return code
X	bra.s	exit2			* And exit
X
X*
X* Reads next character from command line. If zero, never returns, but
X* drops into call to main. Else, returns, with C=1 if character is quote,
X* Z=1 if character is white space.
X*
Xgetnext:
X	move.b	(a1)+,d0		* Get character from command line
X	beq.s	get_2			* Exit if end of line
X	cmp.b	#34,d0			* Check if quote
X	beq.s	isquote			*
X	cmp.b	#32,d0			* Check if space
X	beq.s	isspace			*
X	cmp.b	#9,d0			* Or tab
X	beq.s	isspace			*
X	cmp.b	#10,d0			* Or end of line
Xisspace:
X	andi	#$1E,ccr		* Clear carry flag, retaining Z
Xqrts	rts
X
Xisquote:
X	ori	#1,ccr			* Set carry flag
X	andi	#$FB,ccr		* Clear zero flag
X	rts				* And return
X
Xget_2:
X	move.l	a3,-(a7)		* Push argv onto stack
X	move.l	d2,-(a7)		* Push argc onto stack
X
X	lea	_BSSBAS,a3		* get base of BSS
X	moveq	#0,d1
X	move.l	#_BSSLEN,d0		* get length of BSS in longwords
X	bra.s	clr_lp			* and clear for length given
Xclr_bss move.l	d1,(a3)+
Xclr_lp	dbf	d0,clr_bss
X
Xdomain:
X	jsr	main(PC)		* Call main(argc,argv)
X	moveq.l #0,d0			* Set successful status
X	bra.s	exit2
X
Xexit:
X_exit:
XXCEXIT:
X	move.l	4(SP),d0		* Extract return code
Xexit2:
X	move.l	d0,-(a7)
X	move.l	AbsExecBase.W,a6
X	move.l	DOSBase(A4),a1
X	callsys CloseLibrary		* Close Dos library
X
X*------ this rts sends us back to DOS:
XexitToDOS:
X	MOVE.L	(A7)+,D0
X	movea.l _StackPtr(a4),SP	* Restore stack ptr
X	movem.l (a7)+,d1-d6/a0-a6
X	rts
X
X*-----------------------------------------------------------------------
X* Global definitions
X*
X	csect	__MERGED,1,,2,2
X
X	xdef	NULL,SysBase,LoadAddress,DOSBase
X	xdef	_oserr,_OSERR,_ONBREAK
X	xdef	_ProgramName,_StackPtr,_base
X
XNULL	       dc.l    0
X_base	       dc.l    0
X_oserr	       equ     *
X_OSERR	       dc.l    0
X_ONBREAK       dc.l    0
XSysBase        dc.l    0
XLoadAddress    dc.l    0
X_StackPtr      dc.l    0
XDOSBase        dc.l    0
X_ProgramName   dc.l    0
XDOSName        dc.b    'dos.library',0
X
X	END
END_OF_FILE
if test 6132 -ne `wc -c <'src/tiny.a'`; then
    echo shar: \"'src/tiny.a'\" unpacked with wrong size!
fi
# end of 'src/tiny.a'
fi
echo shar: End of archive 1 \(of 3\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Submissions to comp.sources.amiga and comp.binaries.amiga should be sent to:
	amiga@cs.odu.edu	
or	amiga@xanth.cs.odu.edu	( obsolescent mailers may need this address )
or	...!uunet!xanth!amiga	( very obsolescent mailers need this address )

Comments, questions, and suggestions s should be addressed to ``amiga-request''
(only use ``amiga'' for submissions) at the above addresses.