-- PDQueueImpl.mesa -- Copyright (C) 1984, Xerox Corporation. All rights reserved. -- Michael Plass, April 10, 1985 5:28:48 pm PST -- Tim Diebert, 5-Sep-86 14:00:44 -- DIRECTORY Time, Checksum, Environment, Inline, PDInterpSysCalls, PDQueue, String, Process; PDQueueImpl: MONITOR IMPORTS Time, Checksum, Inline, PDInterpSysCalls, String, Process EXPORTS PDQueue = BEGIN Request: TYPE = PDQueue.Request; readProc: PROC [address: LONG POINTER, nwords: CARDINAL] ← NIL; writeProc: PROC [address: LONG POINTER, nwords: CARDINAL] ← NIL; maxStateWords: CARDINAL ← 16*1024; deferSaving: BOOLEAN ← FALSE; saveDeferred: BOOLEAN ← FALSE; state: LONG POINTER TO StringSeqRep ← NIL; messages: LONG POINTER TO StringSeqRep ← NIL; StringSeqRep: TYPE = MACHINE DEPENDENT RECORD [ nStrings: CARDINAL, end: CARDINAL, mark: CARDINAL, nMarked: CARDINAL, nDiscarded: CARDINAL ← 0, check: CARDINAL, nWords: CARDINAL, stringSpace: SEQUENCE COMPUTED CARDINAL OF WORD ]; StringRep: TYPE = RECORD [ nChars: CARDINAL, char: PACKED ARRAY [0..0) OF CHAR ]; Validate: INTERNAL PROC [s: LONG POINTER TO StringSeqRep] RETURNS [ok: BOOLEAN] = { nStrings: CARDINAL ← 0; nMarked: CARDINAL ← 0; offset: CARDINAL ← 0; IF s.check # CalculateCheck[s] THEN RETURN [FALSE]; IF s.mark > s.end THEN RETURN [FALSE]; WHILE offset < s.end DO words: CARDINAL; nStrings ← nStrings + 1; IF offset > s.mark AND nMarked = 0 THEN RETURN [FALSE]; IF offset >= s.mark THEN nMarked ← nMarked + 1; words ← (s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1; offset ← offset + words; ENDLOOP; IF offset # s.end THEN RETURN [FALSE]; IF nStrings # s.nStrings THEN RETURN [FALSE]; IF nMarked # s.nMarked THEN RETURN [FALSE]; RETURN [TRUE] }; DiscardMessages: INTERNAL PROC [maxWords: CARDINAL] = { WHILE messages.end > maxWords DO Discard[messages, 1] ENDLOOP; }; Discard: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, n: CARDINAL] = { newStart: CARDINAL ← StringOffset[s, n]; Inline.LongCOPY[from: @(s[newStart]), nwords: s.end-newStart, to: @(s[0])]; s.nStrings ← s.nStrings - n; s.end ← s.mark ← s.end-newStart; s.nDiscarded ← s.nDiscarded + n; }; StringOffset: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, n: CARDINAL] RETURNS [offset: CARDINAL] = { offset ← 0; IF n = 0 THEN RETURN; IF n > s.nStrings THEN ERROR; FOR i: NAT IN [0..n) DO offset ← NextStringOffset[s, offset]; ENDLOOP; }; NextStringOffset: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, offset: CARDINAL] RETURNS [CARDINAL] = INLINE { IF offset >= s.end THEN ERROR ELSE { words: CARDINAL ← (s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1; IF words > s.end-offset THEN ERROR; offset ← offset + words; RETURN [offset]; }; }; Append: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, string: LONG STRING] = { body: LONG POINTER TO StringRep; chars: CARDINAL ← IF string = NIL THEN 0 ELSE string.length; words: CARDINAL ← (string.length+Environment.bytesPerWord-1)/Environment.bytesPerWord+1; IF s.mark # s.end THEN ERROR; IF s.end+words > s.nWords THEN ERROR; body ← LOOPHOLE[@(s[s.end])]; body.nChars ← chars; FOR i: NAT IN [0..chars) DO body.char[i] ← string[i]; ENDLOOP; IF chars MOD 2 = 1 THEN body.char[chars] ← '\000; s.mark ← s.end ← s.end+words; s.nStrings ← s.nStrings + 1; }; AppendToString: INTERNAL PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, offset: CARDINAL] = { body: LONG POINTER TO StringRep; IF offset >= s.end THEN ERROR; IF offset+(s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1 > s.end THEN ERROR; body ← LOOPHOLE[@(s[offset])]; FOR i: NAT IN [0..body.nChars) DO string[string.length] ← body.char[i]; string.length ← string.length + 1; ENDLOOP; }; MoveToString: INTERNAL PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, offset: CARDINAL] = { string.length ← 0; AppendToString[string, s, offset]; }; GetNumberDiscardedMessages: ENTRY PROC RETURNS [CARDINAL] = { IF messages = NIL THEN Restore[]; RETURN [messages.nDiscarded]; }; GetString: ENTRY PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, n: CARDINAL] RETURNS [ok: BOOLEAN] = { string.length ← 0; IF n-s.nDiscarded > 32767 THEN RETURN [TRUE]; -- funny condition due to unsigned arithmetic . . . nDiscarded may wrap IF n-s.nDiscarded >= s.nStrings THEN RETURN [FALSE]; AppendToString[string, s, StringOffset[s, n-s.nDiscarded]]; RETURN [TRUE]; }; MoveMarkedStrings: INTERNAL PROC [from, to: LONG POINTER TO StringSeqRep] = { nwords: CARDINAL ← from.end-from.mark; IF to.nWords < to.mark+nwords THEN ERROR; to.nStrings ← to.nStrings - to.nMarked; to.nMarked ← 0; to.end ← to.mark; Inline.LongCOPY[from: @(from[from.mark]), nwords: nwords, to: @(to[to.mark])]; to.nStrings ← to.nStrings + from.nMarked; to.nMarked ← from.nMarked; to.end ← to.end + nwords; from.nStrings ← from.nStrings-from.nMarked; from.nMarked ← 0; from.end ← from.mark; }; CalculateCheck: INTERNAL PROC [s: LONG POINTER TO StringSeqRep] RETURNS [CARDINAL] = { cs: CARDINAL ← Checksum.ComputeChecksum[nWords: 5, p: s]; cs ← Checksum.ComputeChecksum[cs: cs, nWords: 1, p: s+6]; RETURN [cs]; }; Save: INTERNAL PROC = { IF deferSaving THEN {saveDeferred ← TRUE; RETURN}; IF state = NIL THEN ERROR; DiscardMessages[state.nWords-state.end]; messages.nMarked ← messages.nStrings; messages.mark ← 0; MoveMarkedStrings[from: messages, to: state]; state.check ← CalculateCheck[state]; IF NOT Validate[state] THEN ERROR; IF writeProc # NIL THEN { usingDisk ← Process.GetCurrent[]; writeProc[state, SIZE[StringSeqRep[state.end]]]; usingDisk ← NIL; }; MoveMarkedStrings[from: state, to: messages]; messages.nMarked ← 0; messages.mark ← messages.end; saveDeferred ← FALSE; }; Restore: INTERNAL PROC = { IF state # NIL THEN ERROR; IF readProc # NIL THEN { temp: LONG POINTER TO StringSeqRep ← PDInterpSysCalls.AllocateSpace[256]; usingDisk ← Process.GetCurrent[]; readProc[temp, 256]; usingDisk ← NIL; IF temp.check = CalculateCheck[temp] THEN { words: CARDINAL ← SIZE[StringSeqRep[temp.end]]; wordsAllocated: CARDINAL ← MAX[maxStateWords, words+255]/256*256; IF wordsAllocated <= maxStateWords THEN { state ← PDInterpSysCalls.AllocateSpace[wordsAllocated]; usingDisk ← Process.GetCurrent[]; readProc[state, words]; usingDisk ← NIL; state.nWords ← wordsAllocated-SIZE[StringSeqRep[0]]; IF Validate[state] THEN { AllocMessages[]; MoveMarkedStrings[from: state, to: messages]; messages.nMarked ← 0; messages.mark ← messages.end; firstUnprintedMessage ← messages.nDiscarded+messages.nStrings-24; -- it should be ok if this wraps around. } ELSE { PDInterpSysCalls.FreeSpace[state]; state ← NIL }; }; }; PDInterpSysCalls.FreeSpace[temp]; temp ← NIL; }; IF state = NIL THEN InternalReset[]; }; AllocMessages: INTERNAL PROC = { wordsAllocated: CARDINAL ← SIZE[StringSeqRep[state.nWords]]; IF messages # NIL THEN {PDInterpSysCalls.FreeSpace[messages]; messages ← NIL}; messages ← PDInterpSysCalls.AllocateSpace[wordsAllocated]; messages.nWords ← wordsAllocated-SIZE[StringSeqRep[0]]; messages.nStrings ← messages.end ← messages.mark ← messages.nMarked ← messages.nDiscarded ← 0; }; InternalReset: INTERNAL PROC = { IF state # NIL THEN {PDInterpSysCalls.FreeSpace[state]; state ← NIL}; state ← PDInterpSysCalls.AllocateSpace[maxStateWords]; state.nWords ← maxStateWords-SIZE[StringSeqRep[0]]; state.nStrings ← state.end ← state.mark ← state.nMarked ← state.nDiscarded ← 0; AllocMessages[]; }; Reset: PUBLIC ENTRY PROC = {InternalReset[]; Save[]}; newRequest: CONDITION; stringsPerRequest: CARDINAL = 6; requestLimit: CARDINAL ← 1000; -- Requests are numbered modulo this number. ReqNumberFromStringNumber: PROC [stringNumber: CARDINAL] RETURNS [requestNumber: CARDINAL] = { requestNumber ← (stringNumber/stringsPerRequest) MOD requestLimit; }; DecodeCopies: PROC [tag: LONG STRING] RETURNS [copies: CARDINAL] = { copies ← ORD[tag[2]]*256+ORD[tag[3]]; }; QueueRequest: PUBLIC ENTRY PROC [request: Request] RETURNS [requestNumber: INT] = { ENABLE UNWIND => NULL; tag: LONG STRING ← [4]; wordsNeeded: INT ← 2*(INT[4]+request.fileName.length+request.requestTime.length+request.requestor.length+request.requestorPassword.length+request.separator.length) + stringsPerRequest*(SIZE[StringRep]+1); IF state = NIL THEN Restore[]; IF state.nStrings >= stringsPerRequest*requestLimit THEN RETURN [-1]; IF state.end+wordsNeeded+10 >= state.nWords THEN RETURN [-1]; requestNumber ← ReqNumberFromStringNumber[state.nStrings+state.nDiscarded]; String.AppendString[tag, " "]; String.AppendChar[tag, VAL[Inline.HighByte[CARDINAL[request.copies]]]]; String.AppendChar[tag, VAL[Inline.LowByte[CARDINAL[request.copies]]]]; Append[state, tag]; Append[state, request.fileName]; Append[state, request.requestTime]; Append[state, request.requestor]; Append[state, request.requestorPassword]; Append[state, request.separator]; IF stringsPerRequest # 6 THEN ERROR; BEGIN msg: LONG STRING ← [250]; String.AppendString[msg, request.fileName]; IF request.copies#1 THEN { String.AppendString[msg, " ("]; String.AppendDecimal[msg, request.copies]; String.AppendString[msg, " copies)"]; }; InternalLogMessage[msg, requestNumber, request.requestor]; END; Save[]; NOTIFY newRequest; }; reprintCopies: CARDINAL ← 0; reprintCancelled: BOOLEAN ← FALSE; Reprint: PUBLIC ENTRY PROC [copies: CARDINAL] = { reprintCancelled ← FALSE; reprintCopies ← reprintCopies + copies; NOTIFY newRequest; }; CancelReprint: PUBLIC ENTRY PROC = { reprintCancelled ← TRUE; reprintCopies ← 0; }; ReprintCancelled: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN [reprintCancelled]}; printingSuspended: BOOLEAN ← FALSE; GetSuspended: PUBLIC ENTRY PROC RETURNS [BOOLEAN] = {RETURN [printingSuspended]}; SetSuspended: PUBLIC ENTRY PROC [suspended: BOOLEAN] RETURNS [old: BOOLEAN] = { old ← printingSuspended; printingSuspended ← suspended; NOTIFY newRequest; }; abortCurrent: BOOLEAN; FirstRequest: ENTRY PROC RETURNS [requestNumber: CARDINAL] = { ENABLE UNWIND => NULL; IF state = NIL THEN Restore[]; requestNumber ← ReqNumberFromStringNumber[state.nDiscarded]; }; FindRequest: ENTRY PROC [requestNumber: CARDINAL, tag: LONG STRING, request: Request] = { ENABLE UNWIND => NULL; stringNumber: CARDINAL ← requestNumber*stringsPerRequest; offset: CARDINAL ← 0; IF state = NIL THEN Restore[]; WHILE stringNumber < state.nDiscarded DO stringNumber ← stringNumber + requestLimit*stringsPerRequest ENDLOOP; IF stringNumber-state.nDiscarded >= state.nStrings THEN RETURN; offset ← StringOffset[state, stringNumber-state.nDiscarded]; MoveToString[tag, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.fileName, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestTime, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestor, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestorPassword, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.separator, state, offset]; IF stringsPerRequest # 6 THEN ERROR; }; GetRequest: ENTRY PROC [request: Request] RETURNS [requestNumber: INT, copies: CARDINAL] = { ENABLE UNWIND => NULL; tag: LONG STRING ← [4]; IF state = NIL THEN Restore[]; DO offset: CARDINAL ← 0; WHILE printingSuspended OR (state.nStrings = 0 AND reprintCopies = 0) DO WAIT newRequest ENDLOOP; abortCurrent ← FALSE; reprintCancelled ← FALSE; IF reprintCopies # 0 THEN { requestNumber ← -1; copies ← reprintCopies; reprintCopies ← 0; RETURN; }; requestNumber ← ReqNumberFromStringNumber[state.nDiscarded]; MoveToString[tag, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.fileName, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestTime, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestor, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.requestorPassword, state, offset]; offset ← NextStringOffset[state, offset]; MoveToString[request.separator, state, offset]; copies ← DecodeCopies[tag]; IF stringsPerRequest # 6 THEN ERROR; IF tag[0] = ' THEN EXIT; Discard[state, stringsPerRequest]; WHILE state.nDiscarded >= stringsPerRequest*requestLimit DO state.nDiscarded ← state.nDiscarded - stringsPerRequest*requestLimit; ENDLOOP; ENDLOOP; }; RemoveRequest: ENTRY PROC [requestNumber: CARDINAL] = { ENABLE UNWIND => NULL; IF state = NIL THEN Restore[]; IF requestNumber # ReqNumberFromStringNumber[state.nDiscarded] THEN ERROR; Discard[state, stringsPerRequest]; WHILE state.nDiscarded >= stringsPerRequest*requestLimit DO state.nDiscarded ← state.nDiscarded - stringsPerRequest*requestLimit; ENDLOOP; Save[]; }; DeferSaving: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; deferSaving ← TRUE; }; DoDeferredSave: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; deferSaving ← FALSE; IF saveDeferred THEN Save[]; }; DoRequest: PUBLIC PROC [action: PROC [request: Request, requestNumber: CARDINAL, abort: LONG POINTER TO BOOLEAN]] = { fileName: LONG STRING ← [80]; requestTime: LONG STRING ← [20]; requestor: LONG STRING ← [80]; requestorPassword: LONG STRING ← [80]; separator: LONG STRING ← [80]; requestNumber: INT; body: LONG POINTER TO StringRep; copies: CARDINAL; aborted: BOOLEAN ← FALSE; [requestNumber, copies] ← GetRequest[[fileName, requestTime, requestor, requestorPassword, separator, 0]]; IF requestNumber < 0 THEN { LogMessage["Reprint."]; action[[NIL, NIL, NIL, NIL, NIL, copies], NAT.LAST, @abortCurrent ! ABORTED => {aborted ← TRUE; CONTINUE}]; LogMessage["Reprint completed."]; } ELSE { body ← LOOPHOLE[@(state[0])]; IF body.nChars # 4 THEN ERROR; body.char[1] ← 'P; LogMessage["Started.", requestNumber, requestor]; action[[fileName, requestTime, requestor, requestorPassword, separator, copies], requestNumber, @abortCurrent ! ABORTED => {aborted ← TRUE; CONTINUE}]; IF aborted THEN LogMessage["Aborted.", requestNumber, requestor] ELSE LogMessage["Completed.", requestNumber, requestor]; RemoveRequest[requestNumber]; }; }; CancelRequest: PUBLIC ENTRY PROC [requestNumber: CARDINAL] RETURNS [ok: BOOLEAN] = { stringNumber: CARDINAL ← requestNumber*stringsPerRequest; body: LONG POINTER TO StringRep; IF state = NIL THEN Restore[]; WHILE stringNumber < state.nDiscarded DO stringNumber ← stringNumber+requestLimit*stringsPerRequest ENDLOOP; IF stringNumber-state.nDiscarded >= state.nStrings THEN RETURN [FALSE]; body ← LOOPHOLE[@(state[StringOffset[state, stringNumber-state.nDiscarded]])]; IF body.nChars # 4 THEN ERROR; IF body.char[0] # ' THEN RETURN [FALSE]; body.char[0] ← 'X; IF stringNumber = state.nDiscarded THEN abortCurrent ← TRUE; Save[]; RETURN [TRUE]; }; -- RequestStatus: TYPE = PDQueue.RequestStatus; CheckRequest: PUBLIC PROC [requestNumber: CARDINAL, action: PROC [request: Request, status: RequestStatus]] = { tag: LONG STRING ← [4]; status: RequestStatus; fileName: LONG STRING ← [80]; requestTime: LONG STRING ← [20]; requestor: LONG STRING ← [80]; requestorPassword: LONG STRING ← [80]; separator: LONG STRING ← [80]; tag.length ← 0; FindRequest[requestNumber, tag, [fileName, requestTime, requestor, requestorPassword, separator, 0]]; IF tag.length = 0 THEN status ← notFound ELSE IF tag[0] = 'X THEN status ← canceled ELSE IF tag[1] = 'P THEN status ← printing ELSE status ← waiting; action[[fileName, requestTime, requestor, requestorPassword, separator, DecodeCopies[tag]], status]; }; CountRequests: PUBLIC ENTRY PROC RETURNS [requestCount: NAT] = { IF state = NIL THEN Restore[]; requestCount ← state.nStrings/stringsPerRequest; }; EnumerateRequests: PUBLIC PROC [action: PROC [requestNumber: CARDINAL, request: Request, status: RequestStatus] RETURNS [continue: BOOLEAN]] = { firstRequest: NAT ← FirstRequest[]; nRequests: NAT ← CountRequests[]; requestNumber: NAT ← firstRequest MOD requestLimit; THROUGH [0..nRequests) DO continue: BOOLEAN ← TRUE; myAction: PROC [request: Request, status: RequestStatus] = { IF status = printing OR status = waiting THEN continue ← action[requestNumber, request, status]; }; CheckRequest[requestNumber MOD requestLimit, myAction]; IF NOT continue THEN EXIT; requestNumber ← (requestNumber + 1) MOD requestLimit; ENDLOOP; }; RegisterDisk: PUBLIC ENTRY PROC [read, write: PROC [address: LONG POINTER, nwords: CARDINAL], maxWords: CARDINAL] = { IF state # NIL THEN { PDInterpSysCalls.FreeSpace[state]; state ← NIL; }; readProc ← read; writeProc ← write; maxStateWords ← maxWords; }; usingDisk: PROCESS ← NIL; -- Records the process using the disk under the monitor, to avoid deadlocks in case of disk errors. LogMessage: PUBLIC PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = { IF usingDisk = Process.GetCurrent[] THEN { IF writeLineProc = NIL THEN ERROR; -- Remote debug if it ever gets this bad. writeLineProc["Help!" ! ABORTED => CONTINUE]; writeLineProc[message ! ABORTED => CONTINUE]; } ELSE LogMessageEntry[message, requestNumber, userName]; }; LogMessageEntry: ENTRY PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = { InternalLogMessage[message, requestNumber, userName]; }; InternalLogMessage: INTERNAL PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = { msg: LONG STRING ← [250]; Time.AppendCurrent[msg]; msg[msg.length] ← ' ; msg.length ← msg.length + 1; IF requestNumber >= 0 THEN { String.AppendChar[msg, 'R]; FOR i: CARDINAL ← requestLimit/10, i/10 WHILE i#0 DO String.AppendDecimal[msg, (requestNumber/i) MOD 10]; ENDLOOP; String.AppendChar[msg, ' ]; }; String.AppendString[msg, message]; IF userName#NIL THEN { String.AppendString[msg, " ("]; String.AppendString[msg, userName]; String.AppendChar[msg, ')]; }; IF state = NIL THEN Restore[]; DiscardMessages[messages.nWords-SIZE[StringBody[msg.length]]]; Append[messages, msg]; TryToPrintMessages[]; }; loggingMessages: BOOLEAN ← FALSE; firstUnprintedMessage: CARDINAL ← 0; writeLineProc: PROC[LONG STRING] ← NIL; RegisterTTY: PUBLIC ENTRY PROC [writeLine: PROC[LONG STRING]] = { writeLineProc ← writeLine; TryToPrintMessages[]; }; TryToPrintMessages: INTERNAL PROC = { msg: LONG STRING ← [250]; GetMsg: INTERNAL PROC [n: CARDINAL] RETURNS [ok: BOOLEAN] = { msg.length ← 0; IF n-messages.nDiscarded > 32767 THEN RETURN [TRUE]; -- funny condition due to unsigned arithmetic . . . nDiscarded may wrap IF n-messages.nDiscarded >= messages.nStrings THEN RETURN [FALSE]; AppendToString[msg, messages, StringOffset[messages, n-messages.nDiscarded]]; RETURN [TRUE]; }; IF NOT loggingMessages OR writeLineProc = NIL THEN RETURN; FOR i: CARDINAL ← firstUnprintedMessage, i+1 UNTIL NOT GetMsg[i] DO writeLineProc[msg ! ABORTED => CONTINUE]; firstUnprintedMessage ← i+1; ENDLOOP; }; SetLogState: PUBLIC ENTRY PROC [logging: BOOLEAN] RETURNS [old: BOOLEAN] = { old ← loggingMessages; loggingMessages ← logging; IF messages # NIL THEN TryToPrintMessages[]; }; EnumerateMessages: PUBLIC PROC [action: PROC[message: LONG STRING] RETURNS [continue: BOOLEAN]] = { msg: LONG STRING ← [250]; FOR i: CARDINAL ← GetNumberDiscardedMessages[], i+1 UNTIL NOT GetString[msg, messages, i] DO IF NOT action[msg].continue THEN EXIT; ENDLOOP; }; END.