[comp.sys.amiga] SPOOL12 source code

kim@amdahl.UUCP (04/13/87)

[ For all you do ... this line's for you ... ]

Here is a good example of a system of multitasking programs that communicate
with eachother using the Amiga's message passing facilities.  Together they
form a basic SPOOLing system (shades of VM/CMS!)

Seems like it would be fairly easy to add support for things like special
forms queues, printer priority sorting (based on file size, age, etc.),
routing files to devices other than printers (plotters, LAN's, what-have-you),
banner pages, ...

These programs were written by Tim Holloway, and were published in his series
of articles on using multitasking effectively in the "Ami Project" magazine
(vol. 1, nos. 5 & 6).  I downloaded this revision from the Casa Mi Amiga
BBS on 4/5/87, where it is stored as file SPOOL12.ARC.  This revision has
fixed several bugs that were present in the published version.

I've done some minimal testing of this using 1.2, with 1 Meg of Fast RAM,
and the programs seem to work well together, with only a few problems (see
the README.TOO file).

Since the uuencoded binaries are > 75K, I'm reluctant to post them to the
net (not knowing how many people would be interested in them), but I have
emailed them to Fred for inclusion in an upcoming Fish Disk.  BTW, Fred tells
me he actually *does* get a few hours sleep every couple of days, but I
dunno if I believe that ... :-)!

There should be a .signature at the end unless this gets truncated somewhere!

/kim


# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# Makefile README README.TOO SPOOL.go dates.c gripe.c login.cx pathname.c prtspool.c spool.c spool.h spooler.c

echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
# makefile for SPOOLer

#
#	11/19/86, by Tim Holloway.  This is the Makefile for the
#	multi-printer SPOOLing system featured in Ami Project magazine
#	Vol I, issue 5.
#
#	(C) 1986, by Tim Holloway.  Permission is hereby granted to
#	freely distribute this system for non-commercial use, providing
#	the copyright notices and credit to the author are retained.
#	The right for any other person or company to in any way modify
#	or restrict this copyright is NOT allowed.
#

# ==== to startup the SPOOLing system, EXECUTE SPOOL.go ===
# ==== to queue file[s] to the printer, use the SPOOL command.


#	Macros.  User must adapt as appropriate

H = spool.h
CC = execute cc
LK = execute link
ARC = Utilities:c/arc
ARCFILE = RAM:Spool

SRC = spool.c spooler.c prtspool.c login.cx dates.c gripe.c SPOOL.go read-me pathname.c

all : spooler spool prtspool nocrud

spooler : spooler.o gripe.o
	$(LK) spooler spooler.o gripe.o

spooler.o : $H spooler.c
	$(CC) spooler


spool : spool.o pathname.o
	$(LK) spool spool.o pathname.o

pathname.o : pathname.c
	$(CC) pathname

spool.o : $H login.cx spool.c
	$(CC) spool

prtspool : prtspool.o dates.o gripe.o
	$(LK) prtspool prtspool.o dates.o gripe.o

prtspool.o : $H login.cx prtspool.c
	$(CC) prtspool

gripe.o : gripe.c
	$(CC) gripe

dates.o : dates.c
	$(CC) dates

# my CC execute file tells the linker not to link by creating a dummy
# file if a compile fails

nocrud : 
	delete ram:crud	# my CC execute files tell the linker not to link

backup :
	$(ARC) a $(ARCFILE) $H makefile $(SRC) spool spooler prtspool
//E*O*F Makefile//

echo x - README
cat > "README" << '//E*O*F README//'
read-me: Simultaneous Peripheral Operation OLine

*** This is version 1.2 of the SPOOL system, updated December 25th, 1986!!! *

This is the documentation to the SPOOL system described in Ami Project
Magazine, Volume 1, issue #5. For more complete information on how it works,
rush on down to your nearest purveyor of high-quality Amiga publications.

DISTRIBUTION: Copyright (C) 1986, by Tim Holloway. This system may be freely
distributed for non-commercial use by any means, as long as this and all
other copyright notices are not removed.  These rights may not be abridged
or modified in any way except by written permission of the author.

DESCRIPTION: The SPOOL system consists of 3 programs: A queue manager,
a printer driver, and a SPOOL request program.  The queue manager can
driver multiple printers simultaneously, however present versions of
AmigaDOS only support one system printer, so other printers would have
to be driven as non-ANSI devices (unless they're ANSI printers!).  To
actually run the system, you need programs SPOOL, SPOOLER, and PRTSPOOL,
as well as the SPOOL.go startup file.  To start SPOOLING, execute SPOOL.go.
Try the AmigaDOS STATUS command and see what happens.  Whenever you wish to
print a file or files, use the command:

	SPOOL file1 [file2] [file3] [...]

The names of the files will be placed in a queue.  As soon as a printer
becomes idle, it will request another filename, open the file, and print
it.  The only limit to the queue size is available memory.  To flush
the queues and stop SPOOLing:

	SPOOL -shutdown

There is at present no provision to interrupt a print request in progress.
Type carefully, then - SPOOLing a non-text file could be messy - the only
way to abort an unwanted request is to deselect the printer, finish any
other tasks in the system and reboot (so what did you expect, for free?).

BUGS:  There's always one more, so they say.  This version corrects a bug
in the original Ami Project article where the memory occupied by queued
filenames was not released.  There's probably more, especially since logic
was modified so that PRTSPOOL and SPOOLER do all their talking through
Intuition rather than stdout and stderr.  The "dates.c" file should provide
laughs for years, particularly at the end of February.  Any and all help
with this function will be VERY gratefully accepted.

MORE BUGS: Using earlier versions of this system, PRTSPOOL was prone to
refuse to print perfectly legitimate files, saying that they weren't found.
That's because the search for files whose higher-level directories were
not explicitly given was mad based on PRTSPOOL's current directory, rather
than SPOOL's.  SPOOL now calls ExpandPath to get the true pathname.

N.B.: The pathname.c, gripe.c and dates.c files are general-purpose functions
which you can use in any programs you see fit.  Permission is granted to
include them into commercial programs as well.

The SPOOL system demonstrates the power of the Amiga's mutitasking abilities
in a very practical way.  It makes extensive use of Amiga Exec's message
passing facilities and list-handling primitives, and is extremely flexible,
both in its present form and in the ease in which it may be extended to
provide all the functionality of a minicomputer or even mainframe SPOOLing
system on a "home" computer.  Hopefully, you will find this system useful
and even inspirational (let me know what kinds of improvements you come up
with).
 
                                   Tim Holloway

			CIS 73026,2026
			BIX tholloway

			And always,
			Casa Mi AMiga: Fidonet 112/1 (904) 733-4515

Enjoy!
//E*O*F README//

echo x - README.TOO
cat > "README.TOO" << '//E*O*F README.TOO//'
Here are a few features (and a couple of bugs) that I've found whilst
giving the SPOOLER system a once over:


    1 - With Epson printer selected via preferences, the output of a
        vanilla text file gets printed in condensed type regardless
        whether PRT: or PAR: is selected as the paramater of the
        "prtspool" command.


    2 - Some number of characters of the first line of a spooled file
        are used as a page header.  With PRT: selected, this line on
        the first page seems to come out expanded and bold, while on
        subsequent pages of that file, it comes out normal-bold.  With
        PAR: selected, it seems to always come out normal-bold.

        The current date is on a line following the page header line
        on each page, and "EOF is printed at the end of the file.


    3 - A Form-Feed is issued to the printer at the beginning of printing
        of each file.


    4 - The "spool" command always puts up a "Stack Overflow" requestor
        if it is executed from inside Matt'n'Steve's "shell" (v2.05m),
        even with STACK 40000.  Dunno if this is a "spool" bug, or one
        in the "shell".

        The "spooler" and "prtspool" commands may be "run" from the shell,
        or from a regular CLI successfully, but "spool" itself can only
        be invoked from a regular (non-shell) CLI, due to the above.


    5 - Killing the CLI(s) from which "spooler" and/or "prtspool" were
        started from, will cause it/them to hang until the "spool -shutdown"
        command is issued from some other CLI.


    6 - The binaries provided had (apparently) been compiled under
        Lattice 3.03.  I haven't tried recompiling them (yet) under
        either Manx 3.40a or Lattice 3.10.


UUCP:  kim@amdahl.amdahl.com
  or:  {sun,decwrl,hplabs,pyramid,ihnp4,seismo,oliveb,cbosgd}!amdahl!kim
DDD:   408-746-8462
USPS:  Amdahl Corp.  M/S 249,  1250 E. Arques Av,  Sunnyvale, CA 94086
CIS:   76535,25


//E*O*F README.TOO//

echo x - SPOOL.go
cat > "SPOOL.go" << '//E*O*F SPOOL.go//'
.key pfile
echo "Starting SPOOLing system."
run spooler
wait 4
run prtspool <pfile$PRT:>
echo "SPOOLER started."
//E*O*F SPOOL.go//

echo x - dates.c
cat > "dates.c" << '//E*O*F dates.c//'
/* juldate.c - convert dates */

Julian_to_Gregorian(inday, year, month, day)  /* julian day to month and day */
register short int inday;
int  *year, *month, *day;
{
   register short int leap_year, m;
   static short month_days[] = {       /* in leap year */
        0,  31,  60,  91, 121, 152,
      182, 213, 244, 274, 305, 335, 366};
#define APRIL 92
#define JUNE 183
#define SEPTEMBER 275

   leap_year = ((*year % 4) == 0);   /* approximately */
   if (!leap_year && (inday > 59)) inday++;
   if (inday < JUNE)
     m = (inday < APRIL) ? 0 : 3;
   else
     m = (inday < SEPTEMBER) ? 6 : 9;

   for ( ; inday > month_days[m]; m++);   /* find month */
   *day = inday - month_days[m-1] + 1;
   *month = m;
}
//E*O*F dates.c//

echo x - gripe.c
cat > "gripe.c" << '//E*O*F gripe.c//'
/* gripe. - Standardized complainer under Intuition */

/*
**	Date written: 09/18/86
**	Author: Tim Holloway
**		Compuserve: 73026,2026
**		Bix: tholloway
**		Fido node: 112/3 (Casa Mi Amiga).
**
**	Version: 1.0
**
**	Copyright (C) 1986, by Tim Holloway.  This program may be
**	freely distributed for non-commercial use only.  Use for commercial
**	purposes without the express permission of the author is a violation
**	of copyright law.
**
**	Description:
**	  displays a requester with up to three lines of text.
**
**	Change History: 
**
**	None
**
**	Usage: Gripe (line1, line2, line3);
**
**	All are TEXT *;
**
**	Quirks: Builds stuff that MUST be done in CHIP memory.  Use
**	ATOM or something to place the DATA segment in CHIP memory!
**	Intuition Library MUST be open prior to this call.
**	If you need less than 3 lines of text, use "", although I think
**	that NULL is OK by Commodore.
*/

/* Basic Amiga system definitions */

#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <graphics/gfx.h>
#include <intuition/intuition.h>

void Gripe (msg1, msg2, msg3)
TEXT *msg1, *msg2, *msg3;
{

/*----------
	struct Window *wp;

	static struct NewWindow nwindow = {
		0, 0, 300, 50, AUTOFRONTPEN, AUTOBACKPEN, 0,
		0, NULL, NULL, "PRTSPOOL", NULL, NULL,
		0, 0, 0, 0, WBENCHSCREEN
	};
-----------*/

	static struct IntuiText msg0[3] = {
		{ AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
		  AUTOLEFTEDGE, AUTOTOPEDGE, AUTOITEXTFONT,
		  NULL,
		  &msg0[1]},

		{ AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
		  AUTOLEFTEDGE, AUTOTOPEDGE+8, AUTOITEXTFONT,
		  NULL,
		  &msg0[2]},

		{ AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
		  AUTOLEFTEDGE, AUTOTOPEDGE+16, AUTOITEXTFONT,
		  NULL,
		  NULL}
	};

	static struct IntuiText drat = {
		 AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
		 AUTOLEFTEDGE, AUTOTOPEDGE, AUTOITEXTFONT,
		 (APTR) "Rats!",
		 NULL
	};

	msg0[0].IText = msg1;
	msg0[1].IText = msg2;
	msg0[2].IText = msg3;
	(void) AutoRequest (NULL, &msg0, NULL, &drat,  0, 0, 300, 66);
}


#if STAND_ALONE_TEST

struct Library *IntuitionBase, *OpenLibrary();

main()
{
	if ( (IntuitionBase = OpenLibrary("intuition.library", 20)) == NULL)
	{
		printf ("Couldn't open Intuition library!\n");
		exit(20);
	}
	Gripe ("THis is a", "Gripe", "test");
	CloseLibrary(IntuitionBase);
}
#endif
//E*O*F gripe.c//

echo x - login.cx
cat > "login.cx" << '//E*O*F login.cx//'
/* login.cx - log ports in and out */

/* this is an #included file !!!! */

/*
**	8/18/86, Tim Holloway
**
**	Description: These routines are designed to ensure that a user does
**	not find a message port, only to have it disappear before all
**	messages have been transmitted to the destination port.  To that end,
**	the sender must log in to the the destination port, send all messages
**	and then log out.  The first byte of each message indicates log
**	status.  If the receiver changes it to LOGOUT, the sender has been
**	forced off the port and should send no further messages (including
**	logout).
**
**	Caveats: The login ID for the sender is determined by the reply
**	port address of the initial login.  You should either use the
**	same message template for all further communications, or 'clone'
**	the message.  That's the way Amiga EXEC I/O structures are handled,
**	too!
**
**	Miscellaneous: A special data structure called a PORTPAIR is
**	used in order to keep the sending and reply ports together.
**	The PORTPAIR is initialized by LogInPort, and should not be
**	modified by the user.
*/

enum portid {OUTPORT, INPORT};
/* #define OUTPORT 0 / #define INPORT 1 ... if your compiler can't enum */

typedef struct MsgPort *PORT_ADDRESS;
typedef PORT_ADDRESS PORTPAIR[2];

extern PORT_ADDRESS 	CreatePort(char *, int);	/* prototypes */
extern PORT_ADDRESS	FindPort (char *);
extern VOID 		DeletePort (PORT_ADDRESS);


/*
**	Log in a message port.  Assures that receiver will not delete port
**	out from under us!  Creates a reply port. and initializes a portpair -
**	i.e. array containing the addresses of the outbound and reply ports.
*/

BOOL
LogInPort (port_name, ports, log_message)
char *port_name;
PORTPAIR ports;
SPOOLmsg  *log_message;
{
	REGISTER PORT_ADDRESS mpin, mpout;

/*
   Note - we build a reply port FIRST, even though we may not be able to
   use it, simply because we don't want to do any more work while Forbidden
   than absolutely neccessary.  I refuse to feel guilty about the xxx
   microseconds of work wasted if the login failed.  Better that than
   playing ping-pong with Forbids and Permits.  Two Permits for one Forbid
   was bad enough!
*/

	mpin = ports[INPORT] = CreatePort (NULL, 0);	/* reply port */
	if (mpin == NULL)
	{
		error ("Wasn't able to create the reply port.");
		return FALSE;
	}

	Forbid();	/* forbid other tasks from pre-empting */

/* printf ("Reply port is at %lx.\nFind port..", mpin); */

	mpout = ports[OUTPORT]	= FindPort(port_name);
	if (mpout != NULL)
	{
		log_message->log_status = LOG_IN;
		log_message->minfo.mn_ReplyPort = mpin;
		PutMsg (mpout, log_message);

/* printf ("message sent.  Waiting.\n"); */

		(void) WaitPort(mpin);

/* printf ("operation complete.\n"); */

	Permit();	/* */
		(void) GetMsg (mpin);
		return ((BOOL) (log_message->log_status != LOG_OUT));
	}

	Permit();	/* back to the multitasking world! */
	DeletePort (mpin);
	return FALSE;
}

/* disconnect port pair and clean up */

static VOID
LogOutPort(ports, log_message)
PORTPAIR ports;
SPOOLmsg  *log_message;
{

/* printf ("log out ports\n"); */
	if (log_message->log_status != LOG_OUT)
	{
	    log_message->log_status = LOG_OUT;
	    log_message->minfo.mn_ReplyPort = ports[INPORT];
	    PutMsg (ports[OUTPORT], log_message);
	    (VOID) WaitPort (ports[INPORT]);
	    (VOID) GetMsg (ports[INPORT]);
	}

	if (ports[INPORT] != NULL)
	    DeletePort (ports[INPORT]);	/* get rid of port we allocated */
}
//E*O*F login.cx//

echo x - pathname.c
cat > "pathname.c" << '//E*O*F pathname.c//'
/* pathname.c - expand a file's pathname */

/***************************************************************************

	ExpandPath - general-purpose function to expand a pathname

	Usage:

	(void) ExpandPath (shortname, fullname);

	By: Tim Holloway
	Date Written: December 25, 1986
	Change History: none

	Copyright (C) 1986, by Tim Holloway.  May be freely distributed,
	as long as copyright notice is not removed.

	Description: Takes an AmigaDOS filename and expands it to a
	full file path description. I.e: "xxx" to "VOLxxx/yyy/zzz/.../xxx"
	Any legal AmigaDOS filename may be used.  If the name is null,
	returns the path name of the current directory (under 1.2 AmigaDOS,
	at least - this is an undocumented feature of the Lock function).
	If the named file does not exist or an error occurred, returns a
	null string as the expanded pathname.

***************************************************************************/

#include "exec/types.h"
#include "exec/memory.h"
#include "libraries/dos.h"

struct FileInfoBlock fib;

typedef ULONG LOCK;

static int
parent_name(xlock, longname)
LOCK xlock;
char *longname;
{
	LOCK ylock;
	register int i, j;

	ylock = ParentDir (xlock);
	if (ylock == 0) return 0;

	i = parent_name(ylock, longname);
	if (!Examine(ylock, &fib))
	{
		return 0;
	}
	strcpy (longname+i, fib.fib_FileName);
	j = i+strlen(fib.fib_FileName);
	if (i == 0)
		longname[j++] = ':';  /* root directory is special */
	else
		longname[j++] = '/';
	UnLock (ylock);

/*  printf ("Concatenated to create %s\n", longname); */
	return j;
}

ExpandPath(shortname, longname)
char *shortname, *longname;
{
	register int i;
	LOCK pathlock;

	longname[0] = '\0';
	if ( (pathlock=Lock(shortname, ACCESS_READ)) == 0)
	{
		return;
	}

	i = parent_name(pathlock, longname);	
	if (!Examine(pathlock, &fib))
	{
		return;
	}
	strcpy (longname+i, fib.fib_FileName);
	UnLock (pathlock);
}

#ifdef TESTMODE
main(argc, argv)
int argc;
char *argv[];
{
	char full_name[128];

	printf ("expand pathname: %s\n", argv[1]);
	ExpandPath(argv[1], full_name);
	printf ("expanded name is '%s'\n", full_name);
}
#endif
//E*O*F pathname.c//

echo x - prtspool.c
cat > "prtspool.c" << '//E*O*F prtspool.c//'
/* PRTSPOOL.c - print SPOOLed file program */

/*
**	Date written: 04/23/86
**	Author: Tim Holloway
**		Compuserve: 73026,2026
**		Bix: tholloway
**		Fido node: 112/1 (Casa Mi Amiga).
**
**	Version: 1.1 - a true spooler version
**
**	Copyright (C) 1986, by Tim Holloway.  This program may be
**	freely distributed for non-commercial use only.  Use for commercial
**	purposes without the express permission of the author is a violation
**	of copyright law.
**
**	Description:
**	   This program accepts a printer name (file name) as a parameter.
**	It opens the file and requests the names of files to be printed
**	by sending messages to the SPOOLER program, which fills in a
**	file name and returns the message to PRTSPOOL
**
**	Change History: 
**
**	11/19/86, TFH. added logic to actually drive a printer.
**		removed the tracing code, and changed the error message
**		reporting to use my Gripe procedure so display a requester
**		if the SPOOLED file could not be accessed.
**
**	Usage: See below.
**
**	Special notes: sorry, I mixed AmigaDOS file I/O and Unix-style
**	I/O.  In accordance with my policy of never programming unportable
**	unless neccessary, I had intended to use all Unix-style I/O, but
**	the Lattice version 3.03 and earlier systems buffer files that
**	according to Unix should not be buffered.  I could have used
**	the set-non-buffered function, but two wrongs don't make a right!
*/

/* Basic Amiga system definitions */

#include <exec/types.h>
#include <exec/alerts.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <graphics/gfx.h>
#include <intuition/intuition.h>

struct Library *IntuitionBase, *OpenLibrary();

/* more mundane definitions */

#include "stdio.h"
#include "spool.h"

/* define login and logout stuff in the following #include: */

#include "login.cx"

struct Process *myprocess, *FindTask();
struct FileHandle *outfile, *Open();

BOOL io_error = FALSE,
     aborting = FALSE;

usage()
{
	printf ("PRTSPOOL program v1.1 by Tim Holloway\n");
	printf ("Usage: PRTSPOOL filename\n");
}

SPOOLmsg packet = {{NULL, NULL, 0}, '?', ""};

error (msg)
char *msg;
{
	Gripe ("PRTSPOOL:", msg, "");
}

/***************************************************************************/

static int linenum = 0;
static char linebuffer[256];
FILE * infile;

#define LINES_PER_PAGE 56

static int hour, minute, year, day, month;

void
getdate()
{
   long dates[3]; int jday;

   (void) DateStamp(dates);
   hour = dates[1] / 60;
   minute = dates[1] % 60;
   year = (dates[0] * 4) / 1461;
   jday = dates[0] - (year*1461)/4;
   year += 1978;
   Julian_to_Gregorian (jday, &year, &month, &day);
}

void xputs(str)
{
	register int i;

	i = strlen(str);
	if (Write (outfile, str, i) != i)
		io_error = TRUE;
}

spool_file(fname)		/* print a file */
char *fname;
{
   register int rc;

   /* Don't use Gripe, here! - it would halt PRTSPOOL until the user */
   /* acknowledged.  Better to waste a page and keep the printer running. */

   if ((infile = fopen (fname, "r")) == 0)
   {
      xputs ("\f\n\n\n");
      xputs ("*******************************************\n");
      xputs ("**** ERROR ********************************\n");
      xputs ("Could not open file to be printed:\n");
      xputs (fname); xputs("\n");
      xputs ("*******************************************\n");
      return;
   }

   getdate();		/* get timestamp */
   getheader();		/* setup page header */
   putheader();

   do
   {
      put_a_line();
   } while (NOT io_error && get_a_line());
   if (io_error)
   {
	io_error = FALSE;
	aborting = TRUE;
	Gripe ("PRTSPOOL Terminating:", "I/O error on printer file.", "");
   }
   xputs ("\n");
   xputs ("EOF\n");
   rc = fclose (infile);

/******** do NOT do the folowing under Lattice C version 3.03 and earlier.
  They didn't realize the the Amiga Close function does not return an
  error code.

   if (rc != 0) Gripe ("PRTSPOOL:", "Error closing file being printed", "");
*********/
}

int
get_a_line()
{
   char *i;

   i = fgets(linebuffer, sizeof(linebuffer), infile);
   return (i != NULL);
}

put_a_line()
{
   if (linenum++ > LINES_PER_PAGE) putheader();

   xputs (linebuffer);
}

#define HBSIZE 32+8+2+6
static char headbuffer[HBSIZE+8];

/*
  two for ff, stretch, 32 for title, 8 for page#, 4 for shrink, 2*NL, NUL
*/

getheader()
{
   int i;

   fgets (linebuffer, 255, infile);
   headbuffer[0] = 0x0c;   /* ff */
   headbuffer[1] = 0x0e;   /* stretch */
   for (i = 0; (i<HBSIZE-14) && (linebuffer[i] >= ' '); i++)
      headbuffer[i+2] = linebuffer[i];
   i+=2;
   headbuffer[i++]= 0x0f;
   headbuffer[i++]= '\n';
   headbuffer[i++]= '\0';
}

putheader()
{
   char outfb[81];

   xputs(headbuffer);
   sprintf (outfb, "As of: %02.2d/%02.2d/%4d, %02d:%02.2d\n\n",
                             month, day, year, hour, minute);
   xputs(outfb);
   linenum = 0;
}

/***************************************************************************/

main (argc, argv)
int argc;
char *argv[];
{
   PORTPAIR ports;

   if ( (IntuitionBase = OpenLibrary("intuition.library", 0)) == NULL)
   {
	Alert (AT_Recovery + AG_OpenLib + AO_Intuition, NULL);
	exit(20);
   }

   if ((argc < 2) || (argv[1][1] == '?'))
   {
	usage();
	goto abort;
   }

   if ( (outfile= Open(argv[1], MODE_OLDFILE)) == NULL)
   {
	Gripe ("PRTSPOOL:", "Couldn't open printer file:", argv[1]);
	goto abort;
   }

   if (NOT LogInPort (GIMME_A_FILE, &ports, &packet))
   {
	error ("SPOOLER program is inactive");
	goto abort;
   }

/*
printf ("message port for %s is at %lx\n",
 GIMME_A_FILE, ports[OUTPORT]);
*/

    /* raise priority - this program is low-overhead (mostly Waiting)	*/

   myprocess = FindTask(NULL);
   (void) SetTaskPri (myprocess, PRTSPOOL_PRIORITY);

   /* The following loop goes out and solicits for a filename.		*/

   while (NOT aborting)
   {
      packet.filename[0] = NUL;	/* be neat, null out file name */
      packet.minfo.mn_ReplyPort = ports[INPORT];	/* return address */

/* printf ("send/w reply port at %lx\n", packet.minfo.mn_ReplyPort); */

      packet.minfo.mn_Length = sizeof(packet.filename);

/* printf ("===request to spool...\n"); */

      PutMsg (ports[OUTPORT], &packet);
      WaitPort(ports[INPORT]);
      (VOID) GetMsg (ports[INPORT]);
      if (packet.log_status == LOG_OUT) break;
      spool_file(packet.filename);
   }

   Close(outfile);
   LogOutPort (&ports, &packet);

abort:
   CloseLibrary(IntuitionBase);
}
//E*O*F prtspool.c//

echo x - spool.c
cat > "spool.c" << '//E*O*F spool.c//'
/* SPOOL.c - queue print file program */

/*
**	Date written: 04/23/86
**	Author: Tim Holloway
**		Compuserve: 73026,2026
**		Bix: tholloway
**		Fido node: 112/1 (Casa Mi Amiga).
**
**	As featured in Ami Project Magazine.
**
**
**	Version: 1.0
**
**	Copyright (C) 1986, by Tim Holloway.  This program may be
**	freely distributed for non-commercial use only.  Use for commercial
**	purposes without the express permission of the author is a violation
**	of copyright law.
**
**	Description:
**	   This program accepts as input one or more file names.
**	These names are passed via a message port to the SPOOLER program,
**	which prints the files.
**
**	Change History: none.
**	Usage: See below.
*/

/* Basic Amiga system definitions */

#include <exec/types.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>

/* more mundane definitions */

#include "stdio.h"
#include "spool.h"

/* define login and logout stuff in the following #include: */
#include "login.cx"


usage()
{
	printf ("SPOOLing program v1.0 by Tim Holloway\n");
	printf ("Usage: SPOOL filename1 [filename2 [filename3...]]\n");
}

SPOOLmsg packet = {{NULL, NULL, 0}, '?', ""};

error (msg)
char *msg;
{
	fprintf (stderr, "%s\n", msg);
}


main (argc, argv)
int argc;
char *argv[];
{
   int i;
   PORTPAIR ports; 

   if ((argc < 2) || (argv[1][1] == '?'))
   {
      usage();
      exit(10);
   }

   if (NOT LogInPort (SPOOL_ME, &ports, &packet))
   {
      error ("SPOOLER program is inactive\n");
      exit(15);
   }

/* printf ("message port for %s is at %lx\n", SPOOL_ME, ports[OUTPORT]); */

   for (i = 1; i < argc; i++)
   {
      if (argv[i][0] == '-')	/* command to SPOOLER */
	strcpy (packet.filename, argv[i]);
      else
      {
	ExpandPath (argv[i], packet.filename);
	if (packet.filename[0] == NUL)
	{
		printf ("ERROR: file \"%s\" could not be found!\n", argv[i]);
		continue;
	}
      }

      packet.minfo.mn_ReplyPort = ports[INPORT];	/* return address */

/* printf ("send/w reply port at %lx\n", packet.minfo.mn_ReplyPort); */

      packet.minfo.mn_Length = strlen(packet.filename)+1;

/* printf ("===Send to spool...\n"); */

      PutMsg (ports[OUTPORT], &packet);
      WaitPort(ports[INPORT]);
      (VOID) GetMsg (ports[INPORT]);
      if (packet.log_status == LOG_OUT) break;	/* ouch! forced off! */
   }
   LogOutPort (&ports, &packet);
}
//E*O*F spool.c//

echo x - spool.h
cat > "spool.h" << '//E*O*F spool.h//'
/* Spool.h - spooler */

#define NUL '\0'	/* ASCII Null character */

/* Define the priorities at which the SPOOLER and PRTSPOOL tasks are to
** run:
*/

#define SPOOLER_PRIORITY 4
#define PRTSPOOL_PRIORITY (SPOOLER_PRIORITY-1)

/* The message port names: */

#define SPOOL_ME "SPOOL system"
#define GIMME_A_FILE "SPOOL output"
#define LOGIN_PRINTER "SPOOL login"

/* log_status codes */

#define LOG_IN '+'
#define LOG_OUT '-'
#define LOGGED 'X'

/* The message structure - same for both message ports */

typedef struct {
	struct Message minfo;
	char log_status;
	char filename[128];
} SPOOLmsg;

#define SHUTDOWN "-shutdown"

/* end of spool.h */
//E*O*F spool.h//

echo x - spooler.c
cat > "spooler.c" << '//E*O*F spooler.c//'
/* SPOOLER.c - asynchronous file print program */

/*
**	Date written: 04/23/86
**	Author: Tim Holloway
**		Compuserve: 73026,2026
**		Bix: tholloway
**		Fido node: 112/1 (Casa Mi Amiga).
**
**	As featured in Ami Project Magazine.
**
**	Version: 1.1
**
**	Copyright (C) 1986, by Tim Holloway.  This program may be
**	freely distributed for non-commercial use only.  Use for commercial
**	purposes without the express permission of the author is a violation
**	of copyright law.
**
**	Description:
**	   This program is the intermediary between user requests submitted
**	via the SPOOL program, and the printers driven by copies of the
**	PRTSPOOL program.  Three queues are maintained: the logged-in user
**	queue has an entry for each user (SPOOL or PRTSPOOL) logged into
**	SPOOLER.  The filename queue is where filenames passed from SPOOL
**	are stored until they can be passed on to PRTSPOOLs.  Finally, the
**	waiting-printer queue is where requests for work from PRTSPOOL
**	are held.
**
**	This program illustrates message passing and waiting for messages.
**	Each message is the name of a file.  The named file will be printed,
**	unless the name is SHUTDOWN, which causes SPOOLER to cease
**	accepting login requests, and to respond to further messages with
**	forced logouts.  SPOOL shuts down completely when the login queue
**	has at last been emptied.
**
**	Change History:
**
**		11/19/86, TFH - added logic to FreeStruct SPOOL queue
**	entries when they were removed from the SPOOL queue.  How
**	embarassing!  Also, removed all printfs - does all talking via
**	Intuition now.
**
**	Usage: RUN SPOOLER
*/

#include <exec/types.h>
#include <exec/exec.h>
#include <exec/alerts.h>
#include <libraries/dosextens.h>

#include "stdio.h"
#include "spool.h"

#define NOT !			/* A la RJ Mical */
#define USER_NAME_LENGTH 33	/* maximum length of a logged user name */

/*
**	The following is an information hider - it insulates the workings
**	of this program against changes in the DOS and from compiler
**	dependencies.  Also makes program more readable.
*/

#define on_port(mport) (1L << mport->mp_SigBit)

/*	A convenient allocator for structures: */

#define AllocStruct(structype, pool) (structype *) \
	AllocMem (sizeof(structype), pool)

/* And its converse... */

#define FreeStruct(area, structype) FreeMem(area, sizeof(structype))

/* Hide the details of test for an empty list */

#define EmptyList(list) (list.lh_Head->ln_Succ == NULL)

#define streq(a,b) (strcmp(a,b) == 0)	/* an old favorite */

/*
**	Logged-in user queue element definition:
*/

typedef struct {
	struct Node lu_node;
	char lu_name[USER_NAME_LENGTH];
} logged_user;

/* Prototypes. Lie slightly about function return types. */

extern logged_user *Find_Name(struct List *, TEXT *);

extern SPOOLmsg *RemHead (struct List *),
		*GetMsg (struct MsgPort *);



/* Global variables. */

BOOL
	spooler_running;
	spooler_closing_down;


static void
make_id (packet, str)	/* create a unique login ID */
SPOOLmsg *packet;	/* based on the sender's reply port address */
TEXT *str;
{
	sprintf (str, "SPOOL User %lx", &packet->minfo.mn_ReplyPort);
}

static void
error (msg)		/* report error */
TEXT *msg;
{
	Gripe ("SPOOLER:", msg, "");
}

/*************************************************************************
**									**
**	Log users in and out.  Users are logged to ensure that nobody	**
**	tries to send spool requests once the spooler starts closing	**
**	down.								**
**									**
*************************************************************************/

struct List login_list;	/* anchor the list of logged-in users */

struct List file_queue;	/* ditto the list of filenames to spool */

struct List waiting_printers;	/* unemployed printer list */

static void
login(packet)
register SPOOLmsg *packet;
{
	register logged_user *user;

	packet->log_status = LOG_OUT;	/* assume the worst */
	if (NOT spooler_closing_down)
	{
/*		printf ("Log in: "); */
		user = AllocStruct (logged_user, MEMF_PUBLIC | MEMF_CLEAR);
		if (user == NULL)
			error ("Couldn't get memory to log in user");
		else
		{
			user->lu_node.ln_Name = user->lu_name;
			user->lu_node.ln_Type = NT_UNKNOWN;
			user->lu_node.ln_Pri = 0;
			make_id (packet, user->lu_name);
			AddTail (&login_list, user);
/*	printf ("%s completed\n", user->lu_name); */
			packet->log_status = LOGGED;
		}
	}
}

static void
logout(packet)
register SPOOLmsg *packet;
{
	char user_id[USER_NAME_LENGTH];
	register logged_user *user;

	make_id (packet, user_id);
	user = (logged_user *) FindName (&login_list, user_id);
	if (user == NULL)
	{
		error ("Logout failed - could not find user ID:");
		error (user_id);
	}
	else
	{
/*	printf ("%s logged out.\n", user_id); */
		Remove(user);	/* ... from the logged-user list */
		FreeStruct(user, logged_user);
		packet->log_status = LOG_OUT;
		if (EmptyList(login_list) && spooler_closing_down)
			spooler_running = FALSE;
	}
}


/*************************************************************************
**									**
**	Spooler shutdown routines.					** 
**									**
*************************************************************************/

static void
purge_queues()		/* throw away any outstanding SPOOL requests */
{			/* modified v1.1 */
	SPOOLmsg *qp;

	while ( (qp = (SPOOLmsg *) RemHead(&file_queue)) != NULL)
		FreeStruct(qp, SPOOLmsg);
}

static void
terminate_printers()		/* tell the printers to get lost! */
{
	register SPOOLmsg *bail_out;

	while (
		 (bail_out = (SPOOLmsg *)RemHead(&waiting_printers))
	          !=
		 NULL
	      )
	{

/* printf ("Force off PRT %lx\n", bail_out); */

		logout(bail_out);	/* remove from log */
		bail_out->log_status = LOG_OUT;
		ReplyMsg(bail_out);
	}
}

/*************************************************************************
**									**
**	Process inbound SPOOL requests.					**
**	If somebody wants the request, pass it on, else make a copy	**
**	of it, and queue the copy.					**
**									**
*************************************************************************/

static void
pass_request(packet)
register SPOOLmsg *packet;
{
	register SPOOLmsg *msg_requested;

	if ( (msg_requested= RemHead(&waiting_printers)) == NULL)
	{
		msg_requested =
			AllocStruct (SPOOLmsg, MEMF_PUBLIC);
		movmem (packet, msg_requested, sizeof(SPOOLmsg));

/* printf ("Queuing request for %s\n", packet->filename); */

		AddTail (&file_queue, msg_requested);
	}
	else	/* send the name to the PRTSPOOL's query */
	{
		strcpy (msg_requested->filename, packet->filename);
		ReplyMsg (msg_requested);
	}
}

/*************************************************************************
**									**
**	Process inbound SPOOL requests.					**
**									**
*************************************************************************/

static void
handle_input(packet)
register SPOOLmsg *packet;
{
/* printf ("Inbound message from %lx\n", packet); */

	switch (packet->log_status)
	{
	case LOG_IN:
		login (packet);
		break;
	case LOG_OUT:
		logout (packet);
		break;
	case LOGGED:
		if (spooler_closing_down)
			logout(packet);		/* sorry! */
		else
		if (streq(packet->filename, SHUTDOWN))
		{
			spooler_closing_down = TRUE;
			purge_queues();
			terminate_printers();
		}
		else
		    pass_request (packet);
		break;
	default: error ("SPL: Invalid log request");
	}
	ReplyMsg (packet);	/* return to owner! */
}

/*************************************************************************
**									**
**	Process outbound SPOOL requests.				**
**									**
**	Notice that since the PRTSPOOL messages contain List nodes,	**
**	they can be queued directly!					**
**									**
*************************************************************************/

static void
prt_request(packet)
register SPOOLmsg *packet;
{
	register SPOOLmsg *msg_requested;

	if ( (msg_requested= RemHead(&file_queue)) == NULL)
	{
/* printf ("Queuing PRT request from %lx.\n", packet); */

		AddTail (&waiting_printers, packet);
	}
	else
	{

/* printf ("dequeueing and outputting %s\n", msg_requested->filename); */

		strcpy (packet->filename, msg_requested->filename);
		FreeStruct (msg_requested, SPOOLmsg);	/* V1.1 */
		ReplyMsg (packet);
	}
}

/*************************************************************************
**									**
**	Process PRTSPOOL requests.					**
**									**
*************************************************************************/

static void
handle_output(packet)
register SPOOLmsg *packet;
{
/* printf ("PRTSPOOL message from %lx\n", packet); */

	switch (packet->log_status)
	{
	case LOG_IN:
		login (packet);
		break;
	case LOG_OUT:
		logout (packet);
		break;
	case LOGGED:
		if (spooler_closing_down)
			logout(packet);		/* sorry! */
		else
		{
		    prt_request (packet);
		    return;	/* reply done in prt_request */
		}
		break;
	default: error ("PRT: Invalid log request");
	}
	ReplyMsg (packet);	/* return to owner! */
}

/*************************************************************************
**									**
**	Gentlemen, start your SPOOLers.					**
**									**
*************************************************************************/

struct Library *IntuitionBase, *OpenLibrary();

void
main (argc, argv)
int argc;
char *argv[];
{
   struct MsgPort *FindPort(), *CreatePort(), *inport, *outport;
   SPOOLmsg *packet;
   struct Process *myprocess, *FindTask();

   if ( (IntuitionBase = OpenLibrary("intuition.library", 0)) == NULL)
   {
	Alert (AT_Recovery + AG_OpenLib + AO_Intuition, NULL);
	exit(20);
   }

	/* it doesn't hurt to run multiple SPOOLERS at once -
	   but why bother?  Only one will get the messages! */

   if(FindPort(SPOOL_ME) != NULL)
   {
	error ("SPOOLER is already active!");
	goto abort;
   }

   if ((inport = CreatePort(SPOOL_ME,0)) == NULL)
   {
      error ("Unable to create spool message port\n");
      goto abort;
   }

   if ((outport = CreatePort(GIMME_A_FILE,0)) == NULL)
   {
      error ("Unable to create spool message port\n");
      DeletePort (inport);
      goto abort;
   }

   NewList (&file_queue);
   NewList (&login_list);
   NewList (&waiting_printers);


     /* raise priority - this program is low-overhead (mostly Waiting) */

    myprocess = FindTask(NULL);
    (void) SetTaskPri (myprocess, SPOOLER_PRIORITY);

/* printf ("input message port for %s is at %lx\n", SPOOL_ME, inport); */

   for (spooler_running = TRUE; spooler_running ; )
   {

/* printf ("Wait for port...\n"); */
      Wait (on_port(inport) | on_port(outport)); /* '|', NOT '||' ! */
      while ( (packet = GetMsg (inport)) != NULL) handle_input(packet);

      while ( (packet = GetMsg (outport)) != NULL) handle_output(packet);
   }
/*   printf ("Spooling has been terminated\n"); */
   DeletePort (inport);
   DeletePort (outport);

abort:
   CloseLibrary(IntuitionBase);
}
//E*O*F spooler.c//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
     66    256   1552 Makefile
     73    575   3601 README
     51    298   1967 README.TOO
      6     16    113 SPOOL.go
     25    126    714 dates.c
    102    323   2326 gripe.c
    117    533   3462 login.cx
     96    273   2112 pathname.c
    274    869   6422 prtspool.c
    108    331   2348 spool.c
     34     97    650 spool.h
    428   1344  10754 spooler.c
   1380   5041  36021 total
!!!
wc  Makefile README README.TOO SPOOL.go dates.c gripe.c login.cx pathname.c prtspool.c spool.c spool.h spooler.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0


-- 
UUCP:  kim@amdahl.amdahl.com
  or:  {sun,decwrl,hplabs,pyramid,ihnp4,seismo,oliveb,cbosgd}!amdahl!kim
DDD:   408-746-8462
USPS:  Amdahl Corp.  M/S 249,  1250 E. Arques Av,  Sunnyvale, CA 94086
CIS:   76535,25

[  Any thoughts or opinions which may or may not have been expressed  ]
[  herein are my own.  They are not necessarily those of my employer. ]