-- UserCredentialsUnsafeImpl.mesa -- last edited by Levin on March 11, 1983 11:31 am DIRECTORY Ascii: TYPE USING [ BS, ControlA, ControlQ, ControlR, ControlV, ControlW, ControlX, CR, DEL, ESC, SP], BodyDefs: TYPE USING [maxRNameLength], BootSwap: TYPE USING [mdsiGerm], DESFace: TYPE USING [Block, DecryptBlock, EncryptBlock, MakeKey], DiskChannel: TYPE USING [ CompletionHandle, Create, CreateCompletionObject, Delete, DiskPageCount, DiskPageNumber, Drive, GetDriveAttributes, GetNextDrive, Handle, InitiateIO, IORequest, Label, nullDrive, nullHandle, WaitAny], Environment: TYPE USING [LongNumber, wordsPerPage], File: TYPE USING [ID], Heap: TYPE USING [systemMDSZone, systemZone], Inline: TYPE USING [BITXOR], LongString: TYPE USING [AppendChar, AppendString, EquivalentStrings, StringBoundsFault], NameInfoDefs: TYPE USING [Authenticate, Outcome], PilotDisk: TYPE USING [], PilotMP: TYPE USING [cClient], PrincOpsRuntime: TYPE USING [GFT], Process: TYPE USING [InitializeMonitor, Pause, SecondsToTicks], ProcessorFace: TYPE USING [SetMP], SDDefs: TYPE USING [SD, sGFTLength], Space: TYPE USING [ Create, Delete, GetHandle, Handle, LongPointer, Map, PageFromLongPointer, virtualMemory], SpecialSpace: TYPE USING [MakeResident, MakeSwappable], System: TYPE USING [UniversalID], UserCredentialsUnsafe: TYPE USING [defaultOptions, GetProc, LoginOptions, PutProc, State]; UserCredentialsUnsafeImpl: MONITOR IMPORTS DESFace, DiskChannel, Heap, Inline, NameInfoDefs, Process, ProcessorFace, Space, SpecialSpace, String: LongString EXPORTS UserCredentialsUnsafe SHARES PilotDisk -- to get at structure of a disk label -- = BEGIN IllegalParameter: ERROR = CODE; PanelCode: TYPE = CARDINAL; bug: PanelCode = 998; -- = CedarInitPrivate.ErrorCode[implementationBug] hardDiskError: PanelCode = 992; diskOffline: PanelCode = 993; -- Declarations for data structures on disk State: TYPE = UserCredentialsUnsafe.State; credentialsImplID: CARDINAL = 08312; obsoleteImplID: CARDINAL = credentialsImplID - 1; -- anything other than credentialsImplID decryptedID: DESFace.Block = ALL[credentialsImplID]; Credentials: TYPE = LONG POINTER TO CredentialsObject; CredentialsObject: TYPE = MACHINE DEPENDENT RECORD [ implementationID(0): CARDINAL ← credentialsImplID, checksum(1): CARDINAL ← 0, fill(2:0..13): CARDINAL[0..17777B] ← 0, kind(2:14..14+2+6*16-1): SELECT state(2:14..15): State FROM noCredentials => NULL, name => [ encryptedID(3): DESFace.Block ← NULL, userName(7): StringBody ← [length: 0, maxlength: NULL, text: NULL] -- The text portion of the userName follows here ], nameHint, nameAndPassword => [ userName(3): StringBody ← [length: 0, maxlength: NULL, text: NULL] -- The text portion of the userName follows here -- If state = nameAndPassword, another StringBody follows ], ENDCASE ]; bogusCredentials: CredentialsObject = [implementationID: obsoleteImplID, kind: noCredentials[]]; diskCredentialsPage: DiskChannel.DiskPageNumber = 2; -- should come from DiskFace or somewhere diskCredentialsPages: DiskChannel.DiskPageCount = (SIZE[nameAndPassword CredentialsObject] - SIZE[StringBody[0]] + 2*SIZE[StringBody[BodyDefs.maxRNameLength]] + Environment.wordsPerPage - 1)/Environment.wordsPerPage; UniversalID: TYPE = ARRAY [0..SIZE[System.UniversalID]) OF WORD; credentialsFileID: File.ID = LOOPHOLE[UniversalID[076543B, 021076B, 054321B, 076543B, 021076B]]; credentialsLabel: DiskChannel.Label = [ fileID: credentialsFileID, filePageLo: 0, filePageHi: 0, immutable: FALSE, temporary: FALSE, zeroSize: FALSE, type: [0], bootChainLink: [0, 0]]; DiskNotReady: ERROR = CODE; BrokenDisk: ERROR = CODE; -- Declarations for data structures in memory (germ) GermString: TYPE = LONG POINTER TO LONG STRING; germMDSln: Environment.LongNumber = [num[highbits: BootSwap.mdsiGerm, lowbits: 0]]; germMDS: LONG POINTER = germMDSln.lp; -- compiler coughs if combined with above. germGFT: LONG POINTER = LOOPHOLE[germMDS+LOOPHOLE[PrincOpsRuntime.GFT, CARDINAL]]; germSD: LONG POINTER TO ARRAY [0..0) OF UNSPECIFIED = LOOPHOLE[germMDS+LOOPHOLE[SDDefs.SD, CARDINAL]]; nameInGerm: GermString = LOOPHOLE[germGFT+LOOPHOLE[germSD[SDDefs.sGFTLength], CARDINAL]]; passwordInGerm: GermString = nameInGerm + SIZE[LONG STRING]; germFreeSpace: LONG POINTER = passwordInGerm + SIZE[LONG STRING]; germFreeSpaceCount: CARDINAL = (germSD[SDDefs.sGFTLength] + Environment.wordsPerPage - 1)/Environment.wordsPerPage - (germSD[SDDefs.sGFTLength] + 2*SIZE[LONG STRING]); -- Exports to UserCredentialsUnsafe Login: PUBLIC ENTRY PROC [ startInteraction: PROC RETURNS [UserCredentialsUnsafe.GetProc, UserCredentialsUnsafe.PutProc], endInteraction: PROC, options: UserCredentialsUnsafe.LoginOptions ← UserCredentialsUnsafe.defaultOptions] = { ENABLE UNWIND => NULL; credentials: Credentials ← NIL; dirty: BOOL ← FALSE; terminalOn: BOOL ← FALSE; EnsureTerminalOn: PROC = {IF ~terminalOn THEN {[getChar, putChar] ← startInteraction[]; terminalOn ← TRUE}}; EnsureTerminalOff: PROC = {IF terminalOn THEN endInteraction[]}; HandleDiskNotReady: PROC = { ProcessorFace.SetMP[diskOffline]; EnsureTerminalOn[]; PutString["Disk is not ready...will retry..."L]; Process.Pause[Process.SecondsToTicks[5]]; PutString["retrying\N"L]; ProcessorFace.SetMP[PilotMP.cClient]; }; CleanupOnAbort: INTERNAL PROC = { IF ~options.ignoreDiskEntirely THEN ReleaseDiskCredentials[credentials, FALSE]; FreeCredentials[credentials]; SetUserCredentialsInternal[NIL, NIL]; }; EnsureInitialized[]; IF ~options.alwaysPrompt AND nameInGerm↑.length > 0 THEN RETURN; -- already logged in credentials ← AllocateCredentials[]; IF options.ignoreDiskEntirely THEN credentials↑ ← bogusCredentials ELSE AcquireDiskCredentials[credentials ! DiskNotReady => {HandleDiskNotReady[]; RETRY}]; IF credentials.implementationID = credentialsImplID THEN { CheckCredentials: INTERNAL PROC [credentials: Credentials] RETURNS [ok: BOOL ← TRUE] = --INLINE-- { Downgrade: INTERNAL PROC [credentials: Credentials, state: State] = --INLINE-- {[] ← ChangeCredentialsStateInternal[credentials, state]; dirty ← TRUE; ok ← FALSE}; WITH cred: credentials SELECT FROM nameHint => { diskUserName: LONG STRING = @cred.userName; EnsureTerminalOn[]; SetUserCredentialsInternal[diskUserName, NIL]; [] ← GetAndAuthenticate[ ! UNWIND => CleanupOnAbort[]]; IF (dirty ← ~String.EquivalentStrings[diskUserName, nameInGerm↑]) THEN [] ← ChangeCredentialsStateInternal[credentials, nameHint]; }; nameAndPassword => { diskUserName: LONG STRING = @cred.userName; diskPassword: LONG STRING = diskUserName + SIZE[StringBody[diskUserName.length]]; SetUserCredentialsInternal[diskUserName, diskPassword]; SELECT AuthenticateInternal[diskUserName, diskPassword] FROM individual, allDown => NULL; notFound, group => Downgrade[credentials, nameHint]; badPwd => Downgrade[credentials, name]; ENDCASE => HardStop[bug]; }; name => { EnsureTerminalOn[]; SetUserCredentialsInternal[@cred.userName, NIL]; DO -- loops only if GV down and password supplied doesn't work. block: DESFace.Block; SELECT GetAndAuthenticate[passwordOnly ! UNWIND => CleanupOnAbort[]] FROM ok => { -- Grapevine is up, and the credentials are valid. The password used to encrypt -- the information in the credentials file may not be the same one that was used -- (successfully) to authenticate the user to Grapevine. After all, the user may -- have changed his password in the Grapevine database since this procedure -- was last called. However, since the user name presented to Grapevine -- was forced to come from the disk and Grapevine authenticated the user, we -- can be confident that this is the same user who requested that the encrypted -- information be stored on the disk originally. We therefore encrypt with -- the new password and rewrite the credentials file. In effect, this gives -- us a cache of the last successful Grapevine authentication, which will be -- useful if Grapevine proves to be down on a subsequent Login. DESFace.DecryptBlock[ key: DESFace.MakeKey[passwordInGerm↑], from: @cred.encryptedID, to: @block]; IF (dirty ← dirty OR (block ~= decryptedID)) THEN [] ← ChangeCredentialsStateInternal[credentials, name]; }; bogusGVName => Downgrade[credentials, nameHint]; gvDown => { -- Grapevine is down, so we must fall back on our cached information on -- the disk. We use the password supplied by the user to decrypt then compare -- it with the expected value. If it matches, the user is assumed to be -- authentic, and the password he supplied is highly likely to be the one -- he last successfully used to authenticate himself with Grapevine. DESFace.DecryptBlock[ key: DESFace.MakeKey[passwordInGerm↑], from: @cred.encryptedID, to: @block]; IF block ~= decryptedID THEN {PutString["Wrong credentials!\N"L]; LOOP}; }; ENDCASE; EXIT ENDLOOP; }; ENDCASE => HardStop[bug]; }; UNTIL CheckCredentials[credentials] DO ENDLOOP; } ELSE { ENABLE UNWIND => CleanupOnAbort[]; RewriteDisk: PROCEDURE RETURNS [rewrite: BOOLEAN] = INLINE { IF options.ignoreDiskEntirely THEN RETURN[FALSE]; IF ~options.confirmCredentialsOverwrite THEN RETURN[TRUE]; RETURN[~Confirm["Disk credentials missing or obsolete; shall I leave it that way? "L]] }; EnsureTerminalOn[]; IF (dirty ← RewriteDisk[]) THEN [] ← ChangeCredentialsStateInternal[credentials, IF ~options.prohibitDiskProtection AND GetAndAuthenticate[] = ok AND Confirm["Do you wish to prevent others from accessing your disk? "L] THEN name ELSE nameHint] ELSE [] ← GetAndAuthenticate[]; }; IF ~options.ignoreDiskEntirely THEN ReleaseDiskCredentials[credentials, dirty ! BrokenDisk => { EnsureTerminalOn[]; PutString["Disk error rewriting credentials file...I give up\N"L]; HardStop[hardDiskError]; }; DiskNotReady => {HandleDiskNotReady[]; RETRY}; ]; FreeCredentials[credentials]; EnsureTerminalOff[]; }; GetCredentialsState: PUBLIC ENTRY PROC RETURNS [state: State] = { credentials: Credentials = AllocateCredentials[]; AcquireDiskCredentials[credentials]; state ← GetCredentialsStateInternal[credentials]; ReleaseDiskCredentials[credentials, FALSE]; FreeCredentials[credentials]; }; ChangeCredentialsState: PUBLIC ENTRY PROC [new: State] RETURNS [old: State] = { -- The internal signals from AcquireDiskCredentials and ReleaseDiskCredentials are -- presently uncaught, since we don't have any better way of dealing with them. credentials: Credentials = AllocateCredentials[]; AcquireDiskCredentials[credentials]; old ← ChangeCredentialsStateInternal[credentials, new]; ReleaseDiskCredentials[credentials, TRUE]; FreeCredentials[credentials]; }; GetUserCredentials: PUBLIC ENTRY PROC [name, password: LONG STRING] = { EnsureInitialized[]; GetUserCredentialsInternal[name, password]; }; SetUserCredentials: PUBLIC ENTRY PROC [name, password: LONG STRING] = -- The implementation of SetUserCredentialsInternal is such that we need not call -- EnsureInitialized (indeed, note how EnsureInitialized is implemented). {SetUserCredentialsInternal[name, password]}; Authenticate: PUBLIC ENTRY PROC RETURNS [outcome: NameInfoDefs.Outcome] = { EnsureInitialized[]; RETURN[AuthenticateInternal[nameInGerm↑, passwordInGerm↑]] }; -- *** For emergency use only *** BreakIn: PROC = { BreakInInternal: ENTRY PROC = INLINE { credentials: CredentialsObject; [] ← ChangeCredentialsStateInternal[@credentials, noCredentials]; ReleaseDiskCredentials[@credentials, TRUE]; }; Process.InitializeMonitor[@LOCK]; BreakInInternal[]; }; -- Internal procedures EnsureInitialized: INTERNAL PROC = -- Note: one might think that this could be done at module start time, rather than -- on every call to a public procedure of this module. However, the contents of the -- germ mds can change after this module is started (e.g., a checkpoint is taken, then -- a physical boot causes a rollback). {IF nameInGerm↑ = NIL THEN SetUserCredentialsInternal[NIL, NIL]}; GetUserCredentialsInternal: INTERNAL PROC [name, password: LONG STRING] = { IF name ~= NIL THEN {name.length ← 0; String.AppendString[name, nameInGerm↑]}; IF password ~= NIL THEN {password.length ← 0; String.AppendString[password, passwordInGerm↑]}; }; SetUserCredentialsInternal: INTERNAL PROC [name, password: LONG STRING] = { empty: LONG STRING ← [0]; IF name = NIL THEN name ← empty; IF password = NIL THEN password ← empty; IF SIZE[StringBody[name.length]] + SIZE[StringBody[password.length]] > germFreeSpaceCount OR name.length > BodyDefs.maxRNameLength OR password.length > BodyDefs.maxRNameLength THEN ERROR IllegalParameter; nameInGerm↑ ← germFreeSpace; nameInGerm↑↑ ← StringBody[maxlength: name.length, text: ]; String.AppendString[nameInGerm↑, name]; passwordInGerm↑ ← nameInGerm↑ + SIZE[StringBody[name.length]]; passwordInGerm↑↑ ← StringBody[maxlength: password.length, text: ]; String.AppendString[passwordInGerm↑, password]; }; GetCredentialsStateInternal: INTERNAL PROC [credentials: Credentials] RETURNS [State] = INLINE {RETURN[credentials.state]}; ChangeCredentialsStateInternal: INTERNAL PROC [credentials: Credentials, new: State] RETURNS [old: State] = { words: CARDINAL; old ← credentials.state; SELECT new FROM noCredentials => { credentials↑ ← bogusCredentials; words ← SIZE[noCredentials CredentialsObject]; }; name => { credentials↑ ← [kind: name[encryptedID: , userName: StringBody[maxlength: nameInGerm↑.length, text: ]]]; WITH cred: credentials SELECT FROM name => { block: DESFace.Block ← decryptedID; DESFace.EncryptBlock[ key: DESFace.MakeKey[passwordInGerm↑], from: @block, to: @cred.encryptedID]; String.AppendString[@cred.userName, nameInGerm↑]; }; ENDCASE => HardStop[bug]; words ← SIZE[name CredentialsObject] + (SIZE[StringBody[nameInGerm↑.length]] - SIZE[StringBody[0]]); }; nameHint => { credentials↑ ← [kind: nameHint[userName: StringBody[maxlength: nameInGerm↑.length, text: ]]]; WITH cred: credentials SELECT FROM nameHint => String.AppendString[@cred.userName, nameInGerm↑]; ENDCASE => HardStop[bug]; words ← SIZE[nameHint CredentialsObject] + (SIZE[StringBody[nameInGerm↑.length]] - SIZE[StringBody[0]]); }; nameAndPassword => { credentials↑ ← [kind: nameAndPassword[userName: StringBody[maxlength: nameInGerm↑.length, text: ]]]; WITH cred: credentials SELECT FROM nameAndPassword => { diskUserName: LONG STRING = @cred.userName; diskPassword: LONG STRING = @cred.userName + SIZE[StringBody[nameInGerm↑.length]]; String.AppendString[@cred.userName, nameInGerm↑]; diskPassword↑ ← StringBody[maxlength: passwordInGerm↑.length, text: ]; String.AppendString[diskPassword, passwordInGerm↑]; }; ENDCASE => HardStop[bug]; words ← SIZE[nameAndPassword CredentialsObject] + (SIZE[StringBody[nameInGerm↑.length]] - SIZE[StringBody[0]]) + SIZE[StringBody[passwordInGerm↑.length]]; }; ENDCASE => HardStop[bug]; credentials.checksum ← ComputeChecksum[DESCRIPTOR[credentials, words]]; }; ComputeChecksum: INTERNAL PROCEDURE [input: LONG DESCRIPTOR FOR ARRAY OF WORD] RETURNS [checksum: WORD ← 0] = { FOR i: CARDINAL IN [0..LENGTH[input]) DO checksum ← Inline.BITXOR[checksum, input[i]]; ENDLOOP; }; ValidChecksum: INTERNAL PROCEDURE [input: LONG DESCRIPTOR FOR ARRAY OF WORD] RETURNS [ok: BOOLEAN] = { checksum: WORD ← 0; FOR i: CARDINAL IN [0..LENGTH[input]) DO checksum ← Inline.BITXOR[checksum, input[i]]; ENDLOOP; RETURN[checksum = 0] }; AuthenticateInternal: INTERNAL PROC [name, password: LONG STRING] RETURNS [outcome: NameInfoDefs.Outcome] = { nameMDS: STRING ← Heap.systemMDSZone.NEW[StringBody[name.length]]; passwordMDS: STRING ← Heap.systemMDSZone.NEW[StringBody[password.length]]; String.AppendString[nameMDS, name]; String.AppendString[passwordMDS, password]; outcome ← NameInfoDefs.Authenticate[nameMDS, passwordMDS]; Heap.systemMDSZone.FREE[@nameMDS]; Heap.systemMDSZone.FREE[@passwordMDS]; }; GetAndAuthenticate: INTERNAL PROCEDURE [askFor: {both, passwordOnly} ← both] RETURNS [outcome: {ok, bogusGVName, gvDown} ← ok] = { name: LONG STRING ← Heap.systemZone.NEW[StringBody[BodyDefs.maxRNameLength]]; password: LONG STRING ← Heap.systemZone.NEW[StringBody[BodyDefs.maxRNameLength]]; defaultRegistry: LONG STRING = ".pa"L; cancelString: LONG STRING = " XXX\N"L; invalidMsg: LONG STRING = " too long!\N"L; PutString["Login please...\N"L]; DO ENABLE UNWIND => PutString[cancelString]; PutString["Name: "L]; GetUserCredentialsInternal[name: name, password: NIL]; IF askFor = passwordOnly THEN PutString[name] ELSE { [] ← GetID[name ! Rubout => {PutString[cancelString]; LOOP}; String.StringBoundsFault => {PutString[invalidMsg]; LOOP}; ]; FOR i: CARDINAL DECREASING IN [0..name.length) DO IF name[i] = '. THEN EXIT; REPEAT FINISHED => { PutString[defaultRegistry]; String.AppendString[name, defaultRegistry ! String.StringBoundsFault => {PutString[invalidMsg]; LOOP}]; }; ENDLOOP; }; PutString[" password: "L]; password.length ← 0; [] ← GetID[password, FALSE ! Rubout => {PutString[cancelString]; LOOP}; String.StringBoundsFault => {PutString[invalidMsg]; LOOP}; ]; SetUserCredentialsInternal[name, password]; PutString[" GV..."L]; SELECT AuthenticateInternal[name, password] FROM individual => {PutString["ok\N"L]; EXIT}; notFound, group => { PutString["invalid user name"L]; IF askFor = passwordOnly THEN {PutString["\N"L]; outcome ← bogusGVName; EXIT}; }; badPwd => PutString["incorrect password"L]; allDown => {PutString["Grapevine down, proceeding anyway\N"L]; outcome ← gvDown; EXIT}; ENDCASE; PutString[", try again.\N"L]; ENDLOOP; Heap.systemZone.FREE[@name]; Heap.systemZone.FREE[@password]; }; -- TTY-like Interaction Utilities Rubout: ERROR = CODE; getChar: UserCredentialsUnsafe.GetProc; -- used for the duration of Login only putChar: UserCredentialsUnsafe.PutProc; -- used for the duration of Login only PutString: PROC [s: LONG STRING] = {FOR i: CARDINAL IN [0..s.length) DO putChar[s[i]]; ENDLOOP}; GetID: PROC [s: LONG STRING, echo: BOOL ← TRUE] RETURNS [c: CHARACTER] = BEGIN OPEN Ascii; firstTime: BOOLEAN ← TRUE; Done: PROCEDURE [c: CHARACTER] RETURNS [yes: BOOLEAN] = INLINE { IF firstTime AND echo THEN { SELECT c FROM ControlA, BS, ControlQ, ControlW, ControlX, CR, SP => NULL; ENDCASE => { THROUGH [0..s.length) DO putChar[BS] ENDLOOP; s.length ← 0}; firstTime ← FALSE}; RETURN[c = SP OR c = CR] }; IF echo THEN PutString[s]; c ← getChar[]; UNTIL Done[c] DO SELECT c FROM DEL => ERROR Rubout; ControlA, BS => -- backspace IF s.length > 0 THEN {IF echo THEN putChar[BS]; s.length ← s.length - 1}; ControlW, ControlQ => {-- backword -- text to be backed up is of the form ...<li><v><ti>, the <v> and <ti> are to -- be removed. state: {ti, v, li} ← ti; FOR i: CARDINAL DECREASING IN [0..s.length) DO SELECT s[i] FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => IF state = ti THEN state ← v; ENDCASE => IF state = v THEN state ← li; IF state = li THEN GO TO done; IF echo THEN putChar[BS]; REPEAT done => s.length ← i + 1; FINISHED => s.length ← 0; ENDLOOP; }; ControlX => {-- back everything IF echo THEN FOR i: CARDINAL IN [0..s.length) DO putChar[BS] ENDLOOP; s.length ← 0; }; ControlR => -- refresh -- IF echo THEN {putChar[CR]; PutString[s]}; ControlV => -- quote next char {String.AppendChar[s, c ← getChar[]]; IF echo THEN putChar[c]}; ENDCASE => {String.AppendChar[s, c]; IF echo THEN putChar[c]}; c ← getChar[]; ENDLOOP; END; Confirm: PROCEDURE [message: LONG STRING ← NIL] RETURNS [BOOLEAN] = { DO IF message ~= NIL THEN PutString[message]; SELECT getChar[ ! UNWIND => PutString[" XXX\N"L]] FROM 'y, 'Y, Ascii.SP, Ascii.CR, Ascii.ESC => {PutString["Yes\N"L]; RETURN[TRUE]}; 'n, 'N, Ascii.DEL => {PutString["No\N"L]; RETURN[FALSE]}; ENDCASE; ENDLOOP}; channel: DiskChannel.Handle ← DiskChannel.nullHandle; completion: DiskChannel.CompletionHandle; rewriteLabel: BOOL; AllocateCredentials: INTERNAL PROC RETURNS [credentials: Credentials] = { space: Space.Handle = Space.Create[size: diskCredentialsPages, parent: Space.virtualMemory]; credentials ← Space.LongPointer[space]; -- Unfortunately, the following consumes unnecessary backing storage, but it isn't much. Space.Map[space]; SpecialSpace.MakeResident[space]; }; FreeCredentials: INTERNAL PROC [credentials: Credentials] = { space: Space.Handle = Space.GetHandle[Space.PageFromLongPointer[credentials]]; SpecialSpace.MakeSwappable[space]; Space.Delete[space]; }; AcquireDiskCredentials: INTERNAL PROC [credentials: Credentials] = { request: DiskChannel.IORequest; label: DiskChannel.Label ← credentialsLabel; IF channel ~= DiskChannel.nullHandle THEN HardStop[bug]; channel ← DiskChannel.Create[GetDrive[], completion ← DiskChannel.CreateCompletionObject[]]; -- I'd like to use a constructor, but the declaration isn't conducive (PRIVATE fields) request.command ← vvr; request.channel ← channel; request.diskPage ← diskCredentialsPage; request.memoryPage ← Space.PageFromLongPointer[credentials]; request.count ← diskCredentialsPages; request.label ← @label; request.dontIncrement ← FALSE; DiskChannel.InitiateIO[@request]; IF DiskChannel.WaitAny[completion] ~= @request THEN HardStop[bug]; rewriteLabel ← FALSE; SELECT request.status FROM goodCompletion => { words: CARDINAL; WITH cred: credentials SELECT FROM noCredentials => words ← SIZE[noCredentials CredentialsObject]; name => words ← SIZE[name CredentialsObject] + (SIZE[StringBody[cred.userName.length]] - SIZE[StringBody[0]]); nameHint => words ← SIZE[nameHint CredentialsObject] + (SIZE[StringBody[cred.userName.length]] - SIZE[StringBody[0]]); nameAndPassword => { diskUserName: LONG STRING = @cred.userName; diskPassword: LONG STRING = @cred.userName + SIZE[StringBody[diskUserName.length]]; words ← SIZE[nameAndPassword CredentialsObject] + (SIZE[StringBody[diskUserName.length]] - SIZE[StringBody[0]]) + SIZE[StringBody[diskPassword.length]]; }; ENDCASE; IF ~ValidChecksum[DESCRIPTOR[credentials, words]] THEN credentials↑ ← bogusCredentials; }; labelDoesNotMatch, labelError => {rewriteLabel ← TRUE; credentials↑ ← bogusCredentials}; seekFailed, dataError, hardwareError => -- The disk is there, but it doesn't seem to work, treat as "no credentials" now. credentials↑ ← bogusCredentials; notReady => {CleanupIO[]; ERROR DiskNotReady}; noSuchPage, notPilotVolume, invalidChannel, checkError => HardStop[bug]; ENDCASE; }; ReleaseDiskCredentials: INTERNAL PROC [credentials: Credentials, dirty: BOOL] = { IF dirty THEN { request: DiskChannel.IORequest; label: DiskChannel.Label ← credentialsLabel; IF channel = DiskChannel.nullHandle THEN HardStop[bug]; -- I'd like to use a constructor, but the declaration isn't conducive (PRIVATE fields) request.command ← IF rewriteLabel THEN vww ELSE vvw; request.channel ← channel; request.diskPage ← diskCredentialsPage; request.memoryPage ← Space.PageFromLongPointer[credentials]; request.count ← diskCredentialsPages; request.label ← @label; request.dontIncrement ← FALSE; DiskChannel.InitiateIO[@request]; IF DiskChannel.WaitAny[completion] ~= @request THEN HardStop[bug]; SELECT request.status FROM goodCompletion => NULL; labelDoesNotMatch => HardStop[bug]; seekFailed, labelError, dataError, hardwareError => {CleanupIO[]; ERROR BrokenDisk}; notReady => ERROR DiskNotReady; noSuchPage, notPilotVolume, invalidChannel, checkError => HardStop[bug]; ENDCASE; }; CleanupIO[]; }; CleanupIO: INTERNAL PROC = {DiskChannel.Delete[channel]; channel ← DiskChannel.nullHandle}; GetDrive: PROC RETURNS [drive: DiskChannel.Drive ← DiskChannel.nullDrive] = { -- At present, this is rather a crock. It returns a disk channel for the first drive -- holding an online, asserted-Pilot physical volume. WHILE (drive ← DiskChannel.GetNextDrive[drive]) ~= DiskChannel.nullDrive DO IF DiskChannel.GetDriveAttributes[drive].state = pilot THEN EXIT; ENDLOOP; }; HardStop: PROC [panelCode: PanelCode] = { ProcessorFace.SetMP[panelCode]; DO ENDLOOP; }; -- Start code Initialize: ENTRY PROC = {EnsureInitialized[]}; Initialize[]; END.