<<>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> 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 [] ~ { <<--Parse machine profile, if any, for machine-dependent stuff>> IF TryFileProfile[machineProfileName, profileDir].found THEN [] ¬ TryFileProfile[oldMachineProfileName, profileDir]; <<--The presence of "Server.profile" overrides a user's profile (for use by servers).>> IF TryFileProfile[serverProfileName, profileDir].found THEN RETURN; IF TryFileProfile[oldServerProfileName, profileDir].found THEN RETURN; <<--Try explicitely set profile.>> IF userSetProfileName # NIL THEN IF TryFileProfile[userSetProfileName, NIL].found THEN RETURN; <<--Try for user's personal profile.>> IF TryFileProfile[userProfileName, profileDir].found THEN RETURN; IF TryFileProfile[oldUserProfileName, profileDir].found THEN RETURN; <<--Use the default profile (if any).>> 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 }; <<': => [] _ stream.GetChar[]; -- flush the ':>> 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[] ]; }; <<******** Main program ********>> <<>> 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. <> <> <<>> <> <> <<>> <> <> <<>> <> <> <<>> <> < placed after ': to mean to prepend and append tokens on the line to tokens previously stored under this key.>> <<>> <> <> <<>> <> <> << >> <> <> <> <<>> <> <> <<>> <<>>