{$M-}  

{This is the source for the QUASAR interface.  See QUASAR.PAS for
 documentation}

program qsr,startqueue,qunit,qlimit,qusername,quser,doqueue,qsrinit ;
const
  ntries=10;
{jsys definitions}
  msend=510B;
  mrecv=511B;
  mutil=512B;
  erstr=11B;
  disms=167B;
{parameters}
  priou=101B;
  ipciw=1B;
  muqry=20B;
  mursp=25B;
  spqsr=2;
  mucre=6;
  ipcf16=601035B;
  
type 

 Strng = Packed Array[1..80] of Char ;

{packetdescriptor describes the IPCF packet descriptor, which is
what you give to MSEND, MRECV, etc.  See Monitor calls for details.
The case Boolean is because "page mode" IPCF needs a page number,
which we handle as an integer, but normal mode needs an address,
which we handle as a pointer.}
     packetdescriptor=packed record
	ipcfl:integer;
	ipcfs:integer;
	ipcfr:integer;
	case Boolean of
	  true: (ipcfpl:0..777777B;ipcfpn:0..777777B;
		 ipcfd:integer);
	  false: (ipcxx:0..777777B;ipcfpt:^sendmessage)
	end;
    messtypes=(type0);

{sendmessage is used messages we send to Quasar.  For simplicity, we
always send in page mode.  Here is an explanation of the general
structure of messages to QUASAR:
	length of whole message,,message type code
	flags - bit 0 is always a flag asking for an acknowlegement
		typically the acknowlegement is the text saying
		"5 jobs killed", that the EXEC prints with [] around it.
	acknowlegement code - we could supply a code so we can tell
		the acknowlegements apart from each other
	message-specific flags
	count of arguments.  The rest of the message consists of
		"arguments", each of which has the following form.  This
		is the number of them.
	length of argument including this word,,argument type code
	data for argument}
    sendmessage=record case messtypes of
	type0: (qsrmsg:array[0..511]of integer)
	end;
    smesspt = ^sendmessage;

{message is used for received messages. The fields described below are the
standard QUASAR message fields.  We typically ignore the header, which
contains a count and message type.  The next word contains some flags,
of which we are only interested in MORE.  This indicates when a message
is too long, and is continued into another one.  NUMARGS indicates the
number of fields in this message.  The rest of it then contains the
messages in the usual QUASAR form of COUNT,,MSGTYPE followed by data.
We let you access halfwords in order to read these headers, and characters
in order to get at the data, which is text for the messages we are
interested in.  Note that some messages we receive are in page mode and
some in normal mode.  However we always use the same area, so no one
needs to know about this except that routine that does the MRECV.}
    message=packed record
      header:array[0..2]of integer;
      dumflg:Boolean; more:Boolean;
      numargs:integer;
      case Boolean of
        true: (halfs:packed array[0..1013]of 0..777777B);
        false: (chars:packed array[0..2534]of char)
      end;
    messpt=^message;

{The following types are used to define parameters to various switches.
QUEUE is sort of interesting.  There are two different representations
used when talking to QUASAR to tell it what kind of object you are
talking about.  The most common one is an integer.  But in the LIST
command there is a word of bits, with one bit for each object type.
This lets you ask to see any combination of queues you want.  The
QUEUE type is defined so that QUEUESET gives you the right bits.  To
get the integer version for use in the other commands, you have to
translate using the table QUEUETRAN.  We prefer to always use the
same representation for queues.  We chose the one used for LIST because
the other one has gaps in the assignments, as you will see if you
look at QUEUETRAN.}
    queue=(batch,printer,cardpunch,papertape,plotter,dumqueue,mount,retrieve);
    queueset = set of queue;
    brevity=(notverbose,verbose,brief);
{Fstring is used to define arguments which are going to be passed as
STRING.  When the caller specifies STRING, the callee see two variables:
the data and a length code.  Normally the data is defined as a packed
array of char, but for this program we chose to define it as words.
Note that Pascal will not make sure they are ASCIZ.  Thus the routines
that take string arguments use the length code to figure out how many
words to copy, and then clear the rest of the last word.}
    fstring=array[0..100]of integer;
    ptpform=(tapedum,ptpascii,ptpimage,ptpimagebinary,ptpbinary);
    outputlog=(outlogdum,lognever,logalways,logerror);    
    notification=(notifdum,notifytty,notifymail,notifyjob);
    batchlog=(batlogdum,logappend,logsupercede,logspool);
    sstring=packed array[1..12]of char;
     
var packet:packetdescriptor;
    mess:smesspt;  {the place we put messages we are sending}
    smesspage:integer;  {page number of mess^}
    memdone: Boolean;  {indicates we have done memory allocation}
    m:messpt;		{the place we receive messages}
    messpage:integer;  {page number of m^}

{nextindex is used by the command to create queue entries.  This
builds a message incrementally, and nextindex keeps track of the
place where the next option should go in the block we are building.
MYPID is the pid for this process, which is assigned once at the
beginning.  QSRPID is Quasar's pid.  We get it each time we want
to do a command, in case Quasar happens to have crashed and restarted.}
    nextindex,mypid,qsrpid:integer;
    ret:integer;
    userlen,i,j:integer;
    conv:packed record case Boolean of
	true:(word:integer);
	false:(queues:queueset)
	end;

{Queuetran is used to convert from our normal representation of
queue types to one often used by Quasar.  See the definition of
QUEUE above for details}
    queuetran:array[queue]of integer;

{TEST is probably not used at the moment.  It is useful for testing
when we need to build an FSTRING}
    test:record case Boolean of
	true:(words:fstring);
	false:(chars:packed array[1..300]of char)
	end;

{TESTS is also used for testing}
    tests:sstring;

	{Here is the conversion table from the bit numbers used in LIST to
	the integers used in the other request types.}

initprocedure;
  begin
  queuetran[batch] := 4b;
  queuetran[printer] := 3b;
  queuetran[cardpunch] := 5b;
  queuetran[papertape] := 6b;
  queuetran[plotter] := 7b;
  queuetran[mount] := 14b;
  queuetran[retrieve] := 17B;
  end;

procedure getpages(num:integer;var page:integer;var loc:messpt); extern;
procedure getpagex(num:integer;var page:integer;var loc:smesspt); extern;

procedure quit; extern;

function to6(x:alfa):integer; extern;

procedure fatal;
{Print most recent error and quit}
  begin
  jsys(erstr,3;priou,400000B:-1,0);
  quit  
  end;

function getqsr:integer;
{Returns Quasar's PID}
	var argblk:array[0..2]of integer;
  begin
  argblk[0] := mursp;  {get a cataloged PID}
  argblk[1] := spqsr;  {Quasar}
  jsys(mutil,2,ret;3,argblk);  {Now ask for the pid}
  if ret <> 2
    then fatal;
  getqsr := argblk[2]
  end;

function getme:integer;
{Gets and returns a PID for me}
	var argblk:array[0..2]of integer;
  begin
  argblk[0] := mucre;  {create a pid}
  argblk[1] := 400000B; {for me}
  jsys(mutil,2,ret;3,argblk);  {Now ask for the pid}
  if ret <> 2
    then fatal;
  getme := argblk[2]
  end;  

procedure getmem;
{Allocate memory}
  begin
  getpagex(1,smesspage,mess);
  getpages(1,messpage,m);  {a page for page mode, since it must be
			    on a page boundary}
  end;

procedure reqlist(howmuch:brevity;which:queueset;user:integer);
{Request a listing of queues}
  begin
  with mess^ do
{create a quasar message asking for a listing of the queues}
    begin
    qsrmsg[0] := 7000010B;	{length,,message type code}
    qsrmsg[1] := 400000000000B;  {ask for acknowlege message}
    qsrmsg[2] := 0;		{no ack code}
    case howmuch of		{flags - we use only the verbosity flag}
      notverbose: qsrmsg[3] := 0;
      verbose: qsrmsg[3] := 100000000000B;
      brief: qsrmsg[3] := 200000000000B
      end;
    qsrmsg[4] := 1;		{number of args}
    qsrmsg[5] := 2000000B;	{2 words,,arg type=tell us about these queues}
    conv.queues := which;
    qsrmsg[6] := conv.word;	{bits, one per queue}
    if user <> 0		{if he specifies a user name}
      then begin
      qsrmsg[0] := 11000010B;	{the block is longer}
      qsrmsg[4] := 2;		{it has 2 args}
      qsrmsg[7] := 2000001B;	{here is the extra arg: 2 words,,user spec}
      qsrmsg[10B] := user	{and the data: user number}
      end
    end;
  with packet do
    begin
    ipcfl := 0;
    ipcfs := mypid;
    ipcfr := qsrpid;
    ipcfpl := 9;
    ipcfpt := mess;
    end;
  jsys(msend,2,ret;4,packet);  {Now send the message}
  if ret <> 2
    then fatal;
  end;

procedure getack(dobrackets:Boolean);
{GETACK is a general routine used to wait for Quasar to send a response.
For this program, we will always get back a response that has some text
in it that should be printed on the terminal.  We don't bother to
analyze the messages.  If they have anything in them at all, the last
argument will be text.  So we just get the last argument.  Note that
sometimes the text will be too long for one message, in which case the
MORE bit will be set.  There is a slight complexity in that we don't
know in advance whether the messge will be send in page mode or normal
mode.  Quasar looks at the length, and uses page mode for big ones and
normal for small ones.  We first try page mode, and if that doesn't
work, try normal mode.  DOBRACKETS indicates we want to print [] around
it.  We do that in this routine instead of outside for reasons of
timing.  If you tried WRITE('['); GETACK; WRITE(']'), the result would
be [<long pause><message>].  The long pause is unesthetic, so we delay
doing the [ until we have the response to print.  That requires it to
be done in this routine.}

	var i,j,pos:integer;
	    pagemode:Boolean;
  begin
  repeat  {keep going as long as last message has MORE set, as it
	   indicates continuations}
    repeat  {keep going until we get something from QUASAR, in case someone
	     else sends us junk mail.  Also throw away trivial messages
	     from QUASAR.  Not sure why they happen}
      with packet do  {first try page mode}
        begin
        ipcfl := 200000B;  {Page mode}
        ipcfs := 0;
        ipcfr := mypid;
        ipcfpl := 512;
        ipcfpn := messpage
        end;
      jsys(mrecv,2,ret;4,packet;i);  {wait until there is something}
      if ret <> 2	    {Failed}
        then if i = ipcf16  {We get this if they sent normal mode}
               then begin   {since we tried to read it in page mode}
	       with packet do  {Try again in normal mode}
	         begin
	         ipcfl := 0; {normal mode}
	         ipcfs := 0;
	         ipcfr := mypid;
	         ipcfpl := 512;
	         ipcfpn := messpage * 1000B
	         end;
	       jsys(mrecv,2,ret;4,packet);  {get ack}
	       if ret <> 2		{If this fails, too, real error}
		 then fatal
	       end
	     else fatal;  {some other failure cause - real error}
     until (packet.ipcfs = qsrpid) and (m^.numargs > 0);
		{keep going until we get something from QUASAR and it is
		 non-trivial}

{The following delightful code finds the last "argument" in the message.
 POS is the position of the header of the argument we are currently looking
 at.  The only way to find the Nth argument is to skip the previous N-1}

    pos := 0;  {POS is kept in half-words}
    for i := 2 to m^.numargs do  {skip arguments til the last}
      pos := pos + 2*m^.halfs[pos];
    j := ((pos div 2) + 1) * 5;  {now convert offset to this arg to char's}
    if dobrackets then write(tty,'[');
    for i := j to j + 5*m^.halfs[pos] - 1 do  {write out the chars.
					       5*m^.half[pos] is the number of
					       chars.  m^.half[pos] is simply
					       the left half of the header
					       for the last arg}
      if m^.chars[i] = chr(0)  {it is ASCIZ, so stop on nul}
        then goto 1
        else write(tty,m^.chars[i]);
1:  if dobrackets then writeln(tty,']');
   until not m^.more
  end;

procedure dolist(howmuch:brevity;which:queueset;user:integer);
{INFO OUTPUT - main routine}
  begin
  qsrpid := getqsr;
  reqlist(howmuch,which,user);
  getack(false)
  end;


Function StrgLen (var str : String) : Integer ;
Label 1 ;
Var I : Integer ;

Begin   (* strglen *)

I := 0 ;   (* initialize count *)
While (Str [I] <> chr(00)) AND (Str [I] <> CHR(32)) Do
 Begin
 I := I + 1 ;
 If I = 81 Then
  Begin
  I := 80 ;
  goto 1 ;
  End
 End ;
1:
Strglen := I ;

End ;   (* strglen *)


function cntchars(arr:fstring) : integer ;
{ This integer function counts the length of 'arr', since its length
  is not properly calculated for both literals and the contents of
  variables at the point of call.}

var lendone : boolean ;
    count   : integer ;
    lup     : integer ;
    inner   : integer ;
    conv    : record case Boolean of
	       true:(word:integer);
	       false:(chars:packed array[0..4]of char)
	      end;

begin   (* cntchars *)

lendone := false ;
lup := 0 ;
count := 0 ;
while not lendone do begin
 conv.word := arr[lup] ;
 for inner := 0 to 4 do begin
  if (conv.chars[inner] = CHR(00)) or (conv.chars[inner] = CHR(32))
   then
    lendone := TRUE
   else
    count := count + 1 ;
 end ;   (* for *)
 lup := lup + 1 ;
end ;   (* while *)

cntchars := count ;

end ;   (* cntchars *)


procedure startqueue(what:queue;jobname:alfa;var fname:fstring;flen:integer);
{PRINT, SUBMIT, etc.  These have to be done in three pieces because of
the presence of multiple switches.  It is not practical to have that many
arguments to the procedure.  So we have this procedure, which sets up
the part of the message containing required arguments.  Then the user
calls a routine for each switch he wants.  Each of these just appends an
argument specifying the data needed for that switch.  Finally, DOQUEUE
puts in a final count and sends off the message, waiting for ack if the
user wants to.}

	var conv:record case Boolean of
		true:(word:integer);
		false:(chars:packed array[0..4]of char)
		end;
	    i:integer;
            tmplen:integer ;
  begin
  qsrpid := getqsr;
  if flen = 80
   then
    tmplen := cntchars(fname)
   else
    tmplen := flen ;

  with mess^ do
    begin
    qsrmsg[0] := 37B;	{message type - count will come at the end}
    qsrmsg[1] := 0;	{no switches}
    qsrmsg[2] := 0;	{no ack code}
    qsrmsg[3] := 0;	{no message-specific switches}
    qsrmsg[4] := 3;	{3 required arguments}
    qsrmsg[5] := 2000024B;	{count,,type of queue}
    qsrmsg[6] := queuetran[what];  {here is the queue type}
    qsrmsg[7] := 2000032B;	{count,,job name}
    qsrmsg[8] := to6(jobname);	   {here is the job name (sixbit)}
    qsrmsg[9] := (tmplen div 5 + 2) * 1000000B + 10B;  {count,,file name}
    for i := 0 to tmplen div 5 do	   {copies the file name}
      qsrmsg[10+i] := fname[i];
    conv.word := fname[tmplen div 5];  {make asciz by setting unused char's}
    for i := tmplen mod 5 to 4 do	     {in last word to 0}
      conv.chars[i] := chr(0);
    qsrmsg[10 + tmplen div 5] := conv.word;
    nextindex := 11 + tmplen div 5;    {set up NEXTINDEX so switch routines
				      know where to put their data}
    end;
  end;

procedure queueswitch(index:integer;value:integer);
{general routine to add a switch.  INDEX is the argument type code for
the particular switch.  VALUE is the value of the switch}

  begin
  mess^.qsrmsg[4] := mess^.qsrmsg[4] + 1;   {say we have one more arg}
  mess^.qsrmsg[nextindex] := index+ 2000000B;  {count,,switch type code}
  mess^.qsrmsg[nextindex+1] := value;	{value}
  nextindex := nextindex+2;	{advance so next switch goes after this}
  end;

procedure qtextswitch(index:integer;var name:fstring;len:integer);
{This is for switches whose value is text.  If you understand the last
two routines, you should understand this.}

	var conv:record case Boolean of
		true:(word:integer);
		false:(chars:packed array[0..4]of char)
		end;
  begin
  with mess^ do
    begin
    qsrmsg[4] := qsrmsg[4] + 1;
    qsrmsg[nextindex] := (len div 5 + 2) * 1000000B + index;
    qsrmsg[nextindex + 1 + len div 5] := 0;
    for i := 0 to len div 5 do
      qsrmsg[nextindex + 1 + i] := name[i];
    conv.word := name[len div 5];	{make asciz}
    for i := len mod 5 to 4 do
     conv.chars[i] := chr(0);
    qsrmsg[nextindex + 1 + len div 5] := conv.word;
    nextindex := nextindex + 2 + len div 5;
    end;
  end;

{Now here are the switches, in numerical order}

procedure qcopies(copies:integer);
  begin
  queueswitch(11B,copies);
  end;

procedure qform(name:alfa);
  begin
  queueswitch(12B,to6(name));
  end;

procedure qtapeform(form:ptpform);
  begin
  queueswitch(13B,ord(form))
  end;

procedure qdisposedelete;
  begin
  queueswitch(14B,1);
  end;

procedure qunit(unit:integer);
  begin
  queueswitch(15B,unit+3000000B)
  end;

procedure qlower;
  begin
  queueswitch(15B,1000000B)
  end;

procedure qupper;
  begin
  queueswitch(15B,2000000B)
  end;

procedure qafter(idtime:integer);
  begin
  queueswitch(16B,idtime)
  end;

procedure qlimit(limit:integer);
  begin
  queueswitch(17B,limit)
  end;

procedure qunique(unique:Boolean);
  begin
  queueswitch(20B,1+ord(unique))
  end;

procedure qrestart(restart:Boolean);
  begin
  queueswitch(21B,1+ord(restart))
  end;

procedure qoutput(log:outputlog);
  begin
  queueswitch(22B,ord(log))
  end;

procedure qaccount(var acctname:fstring;acctlen:integer);
  var tmplen : integer ;
  begin
  if acctlen = 80
   then
    tmplen := cntchars(acctname)
   else
    tmplen := acctlen ;
  qtextswitch(23B,acctname,tmplen)
  end;

procedure qnode(node:alfa);
  begin
  queueswitch(25B,to6(node))
  end;  

procedure qusername(var username:fstring;userlen:integer);
  var tmplen : integer ;
  begin
  if userlen = 80
   then
    tmplen := cntchars(username)
   else
    tmplen := userlen ;
  qtextswitch(26B,username,tmplen)
  end;

procedure quser(user:integer);
  begin
  queueswitch(27B,user)
  end;

procedure qnotify(how:notification);
  begin
  queueswitch(30B,ord(how))
  end;

procedure qbatchlog(how:batchlog);
  begin
  queueswitch(31B,ord(how))
  end;

procedure qconnected(dir:integer);
  begin
  queueswitch(33B,dir)
  end;

procedure qnote(var notestr:sstring;notelen:integer);
{What fun!  This switch takes 1 - 12 char's, and converts them to sixbit}
	var i:integer;a:alfa;
  begin
  a := '          ';
  with mess^ do
    begin
    qsrmsg[4] := qsrmsg[4] + 1;
    if notelen <= 6   { <= 6 char's, will take one word for value}
      then begin
      qsrmsg[nextindex] := 2000034B;  {length,, arg type}
      for i := 1 to notelen do
	a[i] := notestr[i];
      qsrmsg[nextindex+1] := to6(a);
      nextindex := nextindex+2
      end
     else begin	{two words for value (3 total)}
      qsrmsg[nextindex] := 3000034B;  {length,, arg type}
      for i := 1 to 6 do
        a[i] := notestr[i];
      qsrmsg[nextindex+1] := to6(a);
      a := '          ';
      for i := 7 to notelen do
        a[i-6] := notestr[i];
      qsrmsg[nextindex+2] := to6(a);
      nextindex := nextindex + 3
      end;
    end;
  end;

procedure qbegin(block:integer);
  begin
  queueswitch(35B,block)
  end;

procedure qpriority(priority:integer);
  begin
  queueswitch(36B,priority)
  end;

procedure doqueue(doack:Boolean);
{Now we send the request}

  begin
  if doack   {If he wants an ack, set that flag}
    then mess^.qsrmsg[1] := 400000000000B;  
  mess^.qsrmsg[0] := nextindex * 1000000B + 37B;  {supply the total length}
  with packet do
    begin
    ipcfl := 200000B;  {Page mode}
    ipcfs := mypid;
    ipcfr := qsrpid;
    ipcfpl := 512;
    ipcfpn := smesspage;
    end;
  jsys(msend,2,ret;4,packet);  {Now send request}
  if ret <> 2
    then fatal;
  if doack		{and wait for ack if he wants it}
    then getack(true);
  end;

procedure dokill(what:queue;jobname:alfa;mask:integer;seqnum,reqnum:integer;
	         var owner:fstring;ownerlen:integer;doack:Boolean);
{Kill a request.  This is straightforward.  See the documentation for what
the args mean.  We just pass them to Quasar.}

	var conv:record case Boolean of
		true:(word:integer);
		false:(chars:packed array[0..4]of char)
		end;
            tmplen : integer ;

  begin
  if ownerlen = 80
   then
    tmplen := cntchars(owner)
   else
    tmplen := ownerlen ;

  qsrpid := getqsr;
  with mess^ do
    begin
    qsrmsg[0] := 20000012B;  {length,,this is a kill}
    if doack   {if he wants ack, ask for it}
      then qsrmsg[1] := 400000000000B;  
    qsrmsg[2] := 0;   {no ack code}
    qsrmsg[3] := queuetran[what];   {queue type}
    qsrmsg[4] := to6(jobname);      {job name}
    qsrmsg[5] := mask;		    {job name mask}
    qsrmsg[6] := seqnum;
    qsrmsg[7] := reqnum;
    for i := 0 to tmplen div 5 do   {owner - make it ASCIZ}
      qsrmsg[8+i] := owner[i];
    conv.word := owner[tmplen div 5];
    for i := tmplen mod 5 to 4 do
      conv.chars[i] := chr(0);
    qsrmsg[8+tmplen div 5] := conv.word;
    end;
  with packet do
    begin
    ipcfl := 0;
    ipcfs := mypid;
    ipcfr := qsrpid;
    ipcfpl := 20B;
    ipcfpt := mess;
    end;
  jsys(msend,2,ret;4,packet);  {Now ask for kill}
  if ret <> 2
    then fatal;
  if doack			{and wait for ack if he wants it}
    then getack(true);
  end;

procedure qsrinit;
  begin
  mypid := getme;
  getmem
  end
.
