DIRECTORY BasicTime USING [Now], Convert USING [IntFromRope], Commander USING [CommandProc, Register], CommanderOps USING [DoCommand, NextArgument], InstallationBasicComforts, IO, PFS, PFSNames, Rope USING [Cat, Concat, Equal, ROPE], RuntimeError USING [UNCAUGHT], SimpleFeedback USING [Append], SystemNames USING [MachineName, ReleaseName, SimpleHomeDirectory], UserProfile USING [ProfileChangedProc, ProfileChangeReason], UserProfileBackdoor USING [GuestProcsRec]; UserProfileImpl: CEDAR MONITOR IMPORTS BasicTime, Convert, Commander, CommanderOps, InstallationBasicComforts, IO, PFS, PFSNames, Rope, RuntimeError, SimpleFeedback, SystemNames EXPORTS UserProfile, UserProfileBackdoor = BEGIN ProfileList: TYPE = LIST OF ProfileEntry; ProfileEntry: TYPE = REF ProfileRecord; ProfileRecord: TYPE = RECORD[key: ROPE, tokens: LIST OF ROPE, position: INT]; ROPE: TYPE = Rope.ROPE; ProfileChangedItem: TYPE = RECORD [ proc: UserProfile.ProfileChangedProc, clientData: REF ANY ]; IsGuestProcess: PROC [] RETURNS [isGuest: BOOL] ¬ FalseProc; GuestProcs: REF UserProfileBackdoor.GuestProcsRec ¬ NEW[UserProfileBackdoor.GuestProcsRec ¬ [FalseProc]] ; Boolean: PUBLIC PROC [key: ROPE, default: BOOL ¬ FALSE] RETURNS [value: BOOL] = { entry: ProfileEntry; val: ROPE; IF IsGuestProcess[] THEN RETURN GuestProcs.Boolean[key, default]; entry ¬ Lookup[key]; val ¬ GetRope[entry]; IF val = NIL THEN RETURN[default]; IF val.Equal["TRUE", FALSE] THEN RETURN[TRUE]; IF val.Equal["FALSE", FALSE] THEN RETURN[FALSE]; Report[entry, Rope.Cat["\t", val, " is not a Boolean"]]; RETURN[default]; }; Number: PUBLIC PROC [key: ROPE, default: INT] RETURNS [value: INT] = { entry: ProfileEntry; val: ROPE; IF IsGuestProcess[] THEN RETURN GuestProcs.Number[key, default]; entry ¬ Lookup[key]; val ¬ GetRope[entry]; IF val = NIL THEN RETURN[default]; value ¬ Convert.IntFromRope[val ! RuntimeError.UNCAUGHT => { value ¬ default; Report[entry, Rope.Cat["\t", val, " is not an INT"]]; CONTINUE; } ]; }; Token: PUBLIC PROC [key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry; val: ROPE; IF IsGuestProcess[] THEN RETURN GuestProcs.Token[key, default]; entry ¬ Lookup[key]; val ¬ GetRope[entry]; IF val = NIL THEN RETURN[default]; value ¬ val; }; ListOfTokens: PUBLIC PROC [key: ROPE, default: LIST OF ROPE] RETURNS [value: LIST OF ROPE] = { entry: ProfileEntry; IF IsGuestProcess[] THEN RETURN GuestProcs.ListOfTokens[key, default]; entry ¬ Lookup[key]; IF entry = NIL THEN RETURN[default] ELSE RETURN[entry.tokens]; }; Line: PUBLIC PROC [key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry; IF IsGuestProcess[] THEN RETURN GuestProcs.Line[key, default]; entry ¬ Lookup[key]; IF entry = NIL THEN RETURN[default]; FOR l: LIST OF ROPE ¬ entry.tokens, l.rest UNTIL l = NIL DO IF value = NIL THEN value ¬ l.first ELSE value ¬ value.Cat[" ", l.first]; ENDLOOP; }; Lookup: ENTRY PROC [key: ROPE] RETURNS [ProfileEntry] = { ENABLE UNWIND => NULL; RETURN[LookupInternal[key]] }; LookupInternal: INTERNAL PROC [key: ROPE] RETURNS [ProfileEntry] = { << Until we sort out what it means to have a Debugger volume in PCedar IF FileBackdoor.IsDebugger[File.SystemVolume[]] THEN { s2: ROPE = Rope.Concat["Debugger.", key]; FOR l: ProfileList ¬ profileList, l.rest UNTIL l = NIL DO IF Rope.Equal[s1: l.first.key, s2: s2, case: FALSE] THEN RETURN[l.first]; ENDLOOP; };>> FOR l: ProfileList ¬ profileList, l.rest UNTIL l = NIL DO IF Rope.Equal[s1: l.first.key, s2: key, case: FALSE] THEN RETURN[l.first]; ENDLOOP; RETURN[NIL]; }; GetRope: PROC [entry: ProfileEntry] RETURNS [value: ROPE] = INLINE { IF entry = NIL OR entry.tokens = NIL THEN RETURN[NIL]; value ¬ entry.tokens.first; IF entry.tokens.rest # NIL THEN Report[entry, "\textra material on line"]; }; GetProfileName: PUBLIC PROC RETURNS [ROPE] = { IF IsGuestProcess[] THEN RETURN GuestProcs.GetProfileName[] ELSE RETURN[IF profileNameList = NIL THEN NIL ELSE profileNameList.first]; }; profileDir: PFS.PATH ~ PFS.PathFromRope[SystemNames.SimpleHomeDirectory[]]; profileList: ProfileList ¬ NIL; profileName: ROPE ¬ NIL; profileNameList: LIST OF ROPE ¬ NIL; ropeProfile: ROPE ¬ NIL; userSetProfileName: ROPE ¬ NIL; ParseProfile: ENTRY PROC [startNewErrorLog: BOOL ¬ FALSE] = { ENABLE UNWIND => NULL; profileList ¬ NIL; profileName ¬ NIL; profileNameList ¬ NIL; << (until something like the Booting interface comes along for PCedar IF Booting.switches[p] THEN RETURN -- return default values for the profile ELSE>> { dot: ROPE ~ "."; machineName: ROPE ~ SystemNames.MachineName[]; machineProfileName: ROPE ~ Rope.Cat[dot, machineName, dot, SystemNames.ReleaseName[], ".machineProfile"]; userProfileName: ROPE ~ Rope.Cat[".cedar.", SystemNames.ReleaseName[], ".profile"]; serverProfileName: ROPE ~ Rope.Cat[".server.", SystemNames.ReleaseName[], ".profile"]; defaultProfileName: ROPE ~ Rope.Cat[".user.", SystemNames.ReleaseName[], ".profile"]; oldMachineProfileName: ROPE ~ dot.Cat[machineName, ".machineProfile"]; oldUserProfileName: ROPE ~ ".cedar.profile"; oldServerProfileName: ROPE ~ ".server.profile"; oldDefaultProfileName: ROPE ~ ".user.profile"; TryFileProfile: INTERNAL PROC [shortName: ROPE, wDir: PFSNames.PATH] RETURNS [found: BOOL ¬ FALSE] ~ { stream: IO.STREAM ¬ NIL; pPath: PFS.PATH; pPath ¬ PFSNames.ExpandName[name: PFS.PathFromRope[shortName], wDir: wDir ! PFS.Error => CONTINUE]; stream ¬ PFS.StreamOpen[pPath ! PFS.Error => CONTINUE]; IF stream#NIL THEN { profileName ¬ PFS.RopeFromPath[pPath]; profileNameList ¬ CONS[profileName, profileNameList]; ParseProfileInternal[stream]; found ¬ TRUE; }; }; TryFiles: INTERNAL PROC [] ~ { IF TryFileProfile[machineProfileName, profileDir].found THEN [] ¬ TryFileProfile[oldMachineProfileName, profileDir]; IF TryFileProfile[serverProfileName, profileDir].found THEN RETURN; IF TryFileProfile[oldServerProfileName, profileDir].found THEN RETURN; IF userSetProfileName # NIL THEN IF TryFileProfile[userSetProfileName, NIL].found THEN RETURN; IF TryFileProfile[userProfileName, profileDir].found THEN RETURN; IF TryFileProfile[oldUserProfileName, profileDir].found THEN RETURN; IF TryFileProfile[defaultProfileName, profileDir].found THEN RETURN; IF TryFileProfile[oldDefaultProfileName, profileDir].found THEN RETURN; }; TryRopeProfile: INTERNAL PROC ~ { stream: IO.STREAM; IF ropeProfile = NIL THEN RETURN; stream ¬ IO.RIS[ropeProfile]; IF stream # NIL THEN { profileName ¬ "RopeProfile"; -- may need for error reporting ParseProfileInternal[stream, FALSE]; }; }; IF startNewErrorLog THEN errorLog ¬ NIL; TryFiles[]; TryRopeProfile[]; -- make any requested additions/changes. }; CheckErrorLogInternal["parsing your profile"]; }; ParseProfileInternal: INTERNAL PROC [stream: IO.STREAM, reportSameKeys: BOOL ¬ TRUE] = { DO ENABLE { RuntimeError.UNCAUGHT => EXIT; UNWIND => IO.Close[stream]; }; SkipWhite: PROC [flushLines: BOOL ¬ FALSE] RETURNS [c: CHAR] = { DO ENABLE IO.Error, IO.EndOfStream => GO TO stop; c ¬ stream.PeekChar[]; SELECT c FROM '\n, '\r, '\l => IF NOT flushLines THEN RETURN; <= 40C => {}; '- => {-- could be a comment [] ¬ stream.GetChar[]; IF stream.PeekChar[] # '- THEN { stream.Backup[c]; RETURN [c]; }; DO c ¬ stream.GetChar[]; SELECT c FROM '\n, '\r, '\l => { stream.Backup[c]; IF flushLines THEN EXIT; RETURN; }; '- => IF stream.PeekChar[] = '- THEN EXIT; ENDCASE; ENDLOOP; }; ENDCASE => RETURN; [] ¬ stream.GetChar[]; ENDLOOP; EXITS stop => {c ¬ 0C}; }; LocalToken: PROC [flushLines: BOOL ¬ FALSE] = { stop: CHAR ¬ SkipWhite[flushLines]; position ¬ stream.GetIndex[]; token ¬ NIL; SELECT stop FROM 0C, '\n, '\r, '\l => RETURN; '" => token ¬ stream.GetRopeLiteral[]; ENDCASE => token ¬ stream.GetTokenRope[tokenProc].token; }; tokenProc: IO.BreakProc = { RETURN[SELECT char FROM IO.SP, IO.TAB, ', => sepr, '\n, '\r, '\l, ': => break, ENDCASE => other ]; }; Cat: PROC [a, b: LIST OF ROPE] RETURNS [LIST OF ROPE] ~ { RETURN [IF a=NIL THEN b ELSE CONS[a.first, Cat[a.rest, b]]]; }; LookupTokens: INTERNAL PROC [key: ROPE] RETURNS [tokens: LIST OF ROPE] ~ { entry: ProfileEntry ~ LookupInternal[key]; RETURN [IF entry=NIL THEN NIL ELSE entry.tokens]; }; token: ROPE ¬ NIL; tokens, tail: LIST OF ROPE ¬ NIL; position: INT; key: ROPE ¬ NIL; additive: {none, prefix, suffix}; LocalToken[TRUE]; IF (key ¬ token) = NIL THEN EXIT; SELECT SkipWhite[] FROM ': => { [] ¬ stream.GetChar[]; -- flush the ': IF (additive ¬ SELECT stream.PeekChar[] FROM '< => prefix, '> => suffix, ENDCASE => none)#none THEN [] ¬ stream.GetChar[]; --flush '< or '> if needed }; ENDCASE => { position: INT ¬ stream.GetIndex[]; DO IF stream.GetChar[ ! IO.EndOfStream => EXIT] = '\n THEN EXIT; ENDLOOP; ReportInternal[msg: IO.PutFR1["\tmissing : at [%d]\n", [integer[position]]] ]; LOOP; }; DO list: LIST OF ROPE ¬ NIL; LocalToken[]; IF token = NIL THEN EXIT; list ¬ LIST[token]; IF tail = NIL THEN {tail ¬ tokens ¬ list} ELSE {tail.rest ¬ list; tail ¬ list}; ENDLOOP; IF key.Equal["Include", FALSE] THEN { --Insert a profile slice position ¬ stream.GetIndex[]; FOR each: LIST OF ROPE ¬ tokens, each.rest UNTIL each=NIL DO profileSlice: IO.STREAM ¬ NIL; profileSlice ¬ PFS.StreamOpen[fileName: PFS.AbsoluteName[short: PFS.PathFromRope[each.first], wDir: profileDir] ! PFS.Error => CONTINUE]; IF profileSlice#NIL THEN ParseProfileInternal[profileSlice] ELSE { ReportInternal[ entry: NEW[ProfileRecord ¬ [each.first, tokens, position]], msg: "\tFile not found." ]; }; ENDLOOP; } ELSE { --Add a profile entry SELECT additive FROM prefix => tokens ¬ Cat[tokens, LookupTokens[key]]; suffix => tokens ¬ Cat[LookupTokens[key], tokens]; ENDCASE => IF reportSameKeys AND LookupInternal[key] # NIL THEN ReportInternal[ entry: LookupInternal[key], msg: IO.PutFR["\t%g also appears at [%d]", [rope[key]], [integer[position]]] ]; profileList ¬ CONS[NEW[ProfileRecord ¬ [key, tokens, stream.GetIndex[]]], profileList]; }; ENDLOOP; IO.Close[stream]; }; Report: ENTRY PROC [entry: ProfileEntry ¬ NIL, msg: ROPE] = { ENABLE UNWIND => NULL; ReportInternal[entry, msg]; }; logPath: PFS.PATH ~ PFS.PathFromRope["UserProfile.log"]; createNewErrorLog: BOOL ¬ TRUE; ReportInternal: INTERNAL PROC [entry: ProfileEntry ¬ NIL, msg: ROPE] = { ENABLE RuntimeError.UNCAUGHT => CONTINUE; IF errorLog = NIL THEN { errorLog ¬ PFS.StreamOpen[fileName: PFSNames.Cat[profileDir, logPath], accessOptions: IF createNewErrorLog THEN $create ELSE $write]; IF NOT createNewErrorLog THEN errorLog.SetIndex[errorLog.GetLength[]]; createNewErrorLog ¬ FALSE; -- once per startup errorLog.PutF["\n\n*** %g - processing %g\n", [time[BasicTime.Now[]]], [rope[profileName]]]; }; errorLog.PutF1["\n %g", [rope[msg]] ]; IF entry # NIL THEN errorLog.PutF[", at %g [%d]", [rope[entry.key]], [integer[entry.position]]]; errorLog.Flush[]; }; GetErrorLog: PUBLIC ENTRY PROC RETURNS [fileName: ROPE ¬ NIL] = { ENABLE UNWIND => { errorLog ¬ NIL; NULL }; RETURN[InternalErrorLogName[]]; }; CheckErrorLog: ENTRY PROC[msg: ROPE] ~ { CheckErrorLogInternal[msg] }; CheckErrorLogInternal: INTERNAL PROC[msg: ROPE] ~ { IF errorLog # NIL THEN SimpleFeedback.Append[$UserProfile, $begin, $info, IO.PutFR["** There were errors %g; see %g for details (%g)\n", [rope[msg]], [rope[InternalErrorLogName[]]], [time[BasicTime.Now[]]] ]]; }; InternalErrorLogName: INTERNAL PROC RETURNS[fileName: ROPE ¬ NIL] = { IF errorLog # NIL THEN { fileName ¬ PFS.RopeFromPath[PFS.GetName[PFS.OpenFileFromStream[errorLog]].fullFName]; errorLog.Close[]; errorLog ¬ NIL; }; }; errorLog: IO.STREAM ¬ NIL; profileChangedList: LIST OF ProfileChangedItem ¬ NIL; profileChangedListLast: LIST OF ProfileChangedItem ¬ NIL; AddToList: ENTRY PROC [item: ProfileChangedItem] ~ { itemL: LIST OF ProfileChangedItem = CONS[item, NIL]; IF profileChangedList = NIL THEN profileChangedList ¬ itemL ELSE profileChangedListLast.rest ¬ itemL; profileChangedListLast ¬ itemL; }; CopyList: ENTRY PROC RETURNS [LIST OF ProfileChangedItem] ~ { head, tail: LIST OF ProfileChangedItem ¬ NIL; FOR list: LIST OF ProfileChangedItem ¬ profileChangedList, list.rest UNTIL list=NIL DO copy: LIST OF ProfileChangedItem ~ CONS[list.first, NIL]; IF head=NIL THEN head ¬ copy ELSE tail.rest ¬ copy; tail ¬ copy; ENDLOOP; RETURN [head]; }; CallWhenProfileChanges: PUBLIC PROC [proc: UserProfile.ProfileChangedProc] = { IF IsGuestProcess[] THEN GuestProcs.CallWhenProfileChanges[proc] ELSE { item: ProfileChangedItem ~ [proc, --clientData--NIL]; AddToList[item]; DoIt[item, firstTime]; CheckErrorLog["during add ProfileChangedProc"]; }; }; ProfileChanged: PUBLIC PROC [reason: UserProfile.ProfileChangeReason] = { ProfileChangedInternal[reason, reason # firstTime]; }; ProfileChangedInternal: PROC [reason: UserProfile.ProfileChangeReason, nillRopeProfile: BOOL ¬ TRUE] = { IF IsGuestProcess[] THEN GuestProcs.ProfileChanged[reason] ELSE { IF nillRopeProfile THEN ropeProfile ¬ NIL; ParseProfile[startNewErrorLog: TRUE ! RuntimeError.UNCAUGHT => CONTINUE]; FOR l: LIST OF ProfileChangedItem ¬ CopyList[], l.rest UNTIL l=NIL DO DoIt[l.first, reason]; ENDLOOP; }; }; DoIt: PROC [item: ProfileChangedItem, reason: UserProfile.ProfileChangeReason] = { item.proc[reason--, item.clientData-- ! RuntimeError.UNCAUGHT => { Report[msg: Rope.Concat["\tUncaught error while executing ProfileChangedProc: ", InstallationBasicComforts.BasicProcName[item.proc]]]; CONTINUE } ]; }; GetRopeProfile: PUBLIC PROC RETURNS [ROPE] = { RETURN[ropeProfile]; }; SetRopeProfile: PUBLIC PROC [rope: ROPE ¬ NIL] = { ropeProfile ¬ rope; ProfileChangedInternal[edit, FALSE]; }; RegisterGuestProcs: PUBLIC PROC [newProcs: REF UserProfileBackdoor.GuestProcsRec] = { GuestProcs ¬ newProcs; IsGuestProcess ¬ newProcs.IsGuestProcess; }; FalseProc: PROC RETURNS [isGuest: BOOL] = { isGuest ¬ FALSE; }; << Until something like the Booting interface comes along for PCedar CheckpointWatcher: ENTRY Booting.CheckpointProc ~ { ENABLE UNWIND => {errorLog ¬ NIL; NULL}; IF errorLog # NIL THEN { errorLog.Close[]; errorLog ¬ NIL; }; };>> << Until threads and filing come along for PCedar ProfileWatcher: PROC ~ { FOR event: REF READONLY FSBackdoor.CreateEvent ¬ FSBackdoor.NextCreateEvent[NIL], FSBackdoor.NextCreateEvent[event] DO fileName: ROPE ~ event.fName; bang: INT ~ Rope.SkipTo[s: fileName, skip: "!"]; FOR list: LIST OF ROPE ¬ profileNameList, list.rest UNTIL list=NIL DO run: INT ~ Rope.Run[s1: fileName, s2: list.first, case: FALSE]; IF run>bang THEN { ProfileChanged[edit]; EXIT }; ENDLOOP; ENDLOOP; }; >> ParseProfileCommand: Commander.CommandProc ~ { ProfileChanged[edit] }; NotePerLogin: Commander.CommandProc ~ { line: ROPE ¬ Line["CommandTool.PerLogin", NIL]; IF line # NIL THEN [] ¬ CommanderOps.DoCommand[line, cmd]; }; NoteNewUser: Commander.CommandProc ~ { line: ROPE ¬ Line["CommandTool.NewUser", NIL]; IF line # NIL THEN [] ¬ CommanderOps.DoCommand[line, cmd]; }; NotePerCommandTool: Commander.CommandProc ~ { line: ROPE ¬ Line["CommandTool.PerCommandTool", NIL]; IF line # NIL THEN [] ¬ CommanderOps.DoCommand[line, cmd]; }; NoteBootCommands: Commander.CommandProc ~ { line: ROPE ¬ Line["CommandTool.BootCommands", NIL]; IF line # NIL THEN [] ¬ CommanderOps.DoCommand[line, cmd]; }; GetProfileEntryAsRope: Commander.CommandProc = { key: ROPE = CommanderOps.NextArgument[cmd]; IF key = NIL THEN RETURN; cmd.out.PutF["ProfileEntryAsRope: key: %g, value: %g\n", [rope[key]], [rope[Line[key, NIL]]] ]; }; SetFileNameForUserProfile: ENTRY Commander.CommandProc ~ { userSetProfileName ¬ CommanderOps.NextArgument[cmd]; }; partialRopeProfile: ROPE ¬ NIL; SetProfileEntry: Commander.CommandProc = { partialRopeProfile ¬ Rope.Concat[partialRopeProfile, cmd.commandLine]; }; UpdateProfileEntries: Commander.CommandProc = { SetRopeProfile[Rope.Cat[GetRopeProfile[], partialRopeProfile, cmd.commandLine]]; partialRopeProfile ¬ NIL; }; UndoProfileChanges: Commander.CommandProc = { SetRopeProfile[partialRopeProfile ¬ NIL]; }; PrintProfileChanges: Commander.CommandProc = { cmd.out.PutRope[ GetRopeProfile[] ]; }; Commander.Register["NoteBootCommands", NoteBootCommands, "Executes CommandTool.NoteBootCommands commands"]; Commander.Register["NoteNewUser", NoteNewUser, "Executes CommandTool.NoteNewUser commands"]; Commander.Register["NotePerCommandTool", NotePerCommandTool, "Executes CommandTool.NotePerCommandTool commands"]; Commander.Register["NotePerLogin", NotePerLogin, "Executes CommandTool.PerLogin commands"]; Commander.Register["GetProfileEntryAsRope", GetProfileEntryAsRope, "Prints the rope value of the asked for profile entry"]; Commander.Register["PrintProfileChanges", PrintProfileChanges, "Print the entries that are currently layered over existing profile entries."]; Commander.Register["ProfileChanged", ParseProfileCommand, "Same as UserProfile.ProfileChanged[edit]"]; Commander.Register["SetFileNameForUserProfile", SetFileNameForUserProfile, "Tells UserProfile a specific file to use - no checking done"]; Commander.Register["SetProfileEntry", SetProfileEntry, "Reads a new value for the indicated profile entry. Syntax is identical to UserProfile, don't forget the ': following the key. NOTE: this will not take effect until the command UpdateProfileEntries is executed."]; Commander.Register["UndoProfileChanges", UndoProfileChanges, "Resets all UserProfile entries to their file specified values."]; Commander.Register["UpdateProfileEntries", UpdateProfileEntries, "Causes SetProfileEntry commands to take effect."]; ProfileChanged[firstTime]; --Booting.RegisterProcs[c: CheckpointWatcher]; --TRUSTED { Process.Detach[FORK ProfileWatcher] }; END. μ UserProfileImpl.mesa Copyright Σ 1984, 1985, 1986, 1987, 1990, 1991 by Xerox Corporation. All rights reserved. Bob Hagmann May 22, 1985 4:50:10 pm PDT Russ Atkinson (RRA) July 3, 1985 8:40:09 am PDT Hal Murray, January 14, 1986 4:22:44 am PST Doug Wyatt, June 10, 1987 5:09:27 pm PDT Eric Nickell, October 29, 1987 1:34:17 pm PST Wes Irish, May 3, 1988 12:15:48 pm PDT Andy Litman August 12, 1988 1:33:08 am PDT JKF October 26, 1988 7:35:02 am PDT Michael Plass, November 22, 1991 5:27 pm PST Willie-s, June 9, 1993 12:29 pm PDT Chauser, November 6, 1991 4:40 pm PST Swen Johnson, May 2, 1992 6:48 pm PDT Christian Jacobi, October 30, 1992 10:20 am PST Types Variables Accessing profile Utillity PROCs Building profileList --Parse machine profile, if any, for machine-dependent stuff --The presence of "Server.profile" overrides a user's profile (for use by servers). --Try explicitely set profile. --Try for user's personal profile. --Use the default profile (if any). Given an open stream, parse the profile and close the stream. Successive calls "overwrite" previous entries. it is not a comment the end of a comment is either a '\n or a double - Only flush the \n if it was requested ': => [] _ stream.GetChar[]; -- flush the ': key was NOT followed by ':, so flush to the end of line and report the error Reporting errors When the profile changes Note that registration order is important; procs must be called in the same order in which they were registered (otherwise deadlocks may occur at rollback time). RopeProfile Interface Exported to UserProfileBackdoor default IsGuestProcess (not really exported) Watching for profile changes Commander commands Notice that this necessarily includes the CR. Also notice that this won't take effect until UpdateProfileEntries. ******** Main program ******** Bob Hagmann May 3, 1985 11:38:36 am PDT changes to: DIRECTORY, UserProfileImpl, Boolean, Number, CallWhenProfileChanges, ProfileChanged, FalseProc Russ Atkinson (RRA) May 13, 1985 4:33:37 pm PDT Eliminated UserCredentials change watcher (InstallerImpl has this responsibility), UserProfileImpl Bob Hagmann May 22, 1985 4:50:11 pm PDT changes to: Token, ListOfTokens, Line, GetProfileName, UserProfileImpl, ProfileList, ProfileEntry, ProfileRecord, ProfileChangedItem, IsGuestProcess, Boolean, Number, Lookup, LookupInternal, GetRope, profileList, profileName, ParseProfile, ParseProfileInternal, SkipWhite (local of ParseProfileInternal), LocalToken (local of ParseProfileInternal), Report, ReportInternal, GetErrorLog, errorLog, profileChangedList, CallWhenProfileChanges, ProfileChanged, DoIt, AcquireList, ReleaseList, RegisterGuestProcs, FalseProc Christian Jacobi, October 23, 1987 11:56:16 am PDT changes: Added checkpoint watcher to close log file. Eric Nickell, October 29, 1987 1:32:19 pm PST changes: Added ability to Include other profiles. Allowed '< and '> placed after ': to mean to prepend and append tokens on the line to tokens previously stored under this key. Wesley Irish, April 29, 1988 4:15:47 pm PDT changes: Added RopeProfile feature. Jim Foote, August 11, 1988 10:55:53 am PDT changes: Ported to PCedar. Removed references to Booting until something like it shows up for PCedar. Removed references to the debugger volume, until we figure out what that means for PCedar. Removed references to threads and FSBackDoor. Swen Johnson, May 2, 1992 6:48:10 pm PDT Bug fix: code to open stream on Include file was unconditionally Cat'ing specified filename to directory name, even when specified filename was absolute. changes to: ParseProfileInternal: Use PFS.AbsoluteName instead of PFSNames.Cat. Christian Jacobi, October 30, 1992 10:12:04 am PST changes: Added release version number to profile file names. For compatibility not yet removed the old way. Κ Φ–(cedarcode) style•NewlineDelimiter ™codešœ™Kšœ ΟeœO™ZKšœ$Οk™'Kšœžœž™/Kšœ(ž™+Kšœ%ž™(Kšœ*ž™-Kšœ&™&K™*K™#K™,K™#K™%K™%K™/—K˜šž ˜ Kšœ žœ˜Kšœžœ˜Kšœ žœ˜(Kšœ žœ˜-K˜Kšžœ˜Kšžœ˜Kšœ ˜ Kšœžœžœ˜&Kšœ žœžœ˜Kšœžœ ˜Kšœ žœ1˜BKšœ žœ+˜K˜K˜—š œžœžœžœ žœžœ žœ˜FK˜Kšžœžœžœ˜>K˜Kšžœ žœžœžœ ˜$š žœžœžœžœžœžœž˜;Kšžœ žœžœž˜(K˜ Kšžœ˜—K˜K˜——™K™š  œžœžœžœžœ˜9Kšžœžœžœ˜Kšžœ˜Kšœ˜K˜—š  œžœžœžœžœ˜DKšžF˜Fšžœ.žœ˜6Kšœžœ!˜)šžœ&žœžœž˜9Kšžœ+žœžœžœ ˜IKšžœ˜—K˜—šžœ&žœžœž˜9Kšžœ,žœžœžœ ˜JKšžœ˜—Kšžœžœ˜ K˜K˜—š  œžœžœ žœžœ˜DKšžœ žœžœžœžœžœžœ˜6K˜Kšžœžœžœ+˜JK˜K˜—š  œžœžœžœžœ˜.šžœ˜Kšžœžœ˜'Kšžœžœžœžœžœžœžœ˜J—Kšœ˜——K™™K˜Kšœ žœžœžœ1˜KKšœžœ˜Kšœ žœžœ˜Kš œžœžœžœžœ˜$Kšœ žœžœ˜šœžœžœ˜K˜—š   œžœžœžœžœ˜>Kšžœžœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜K˜FKšžœžœžœΟc(˜Kšžœ˜ Kšœžœ˜Kšœ žœ˜.K˜KšœžœQ˜iKšœžœ?˜TKšœžœ?˜VKšœžœ=˜UK˜Kšœžœ+˜FKšœžœ˜-Kšœžœ˜/Kšœžœ˜.K˜š œžœžœ žœžœžœ žœžœ˜fKšœžœžœžœ˜Kšœžœžœ˜Kšœ"žœ'žœ žœ˜cKšœ žœžœ žœ˜7šžœžœžœ˜Kšœžœ˜&Kšœžœ˜5Kšœ˜Kšœžœ˜ Kšœ˜—K˜—š œžœžœ˜K™<šžœ6ž˜Kšœ˜š žœžœžœžœžœžœž˜