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
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
Types
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
];
Variables
IsGuestProcess: PROC [] RETURNS [isGuest: BOOL] ¬ FalseProc;
GuestProcs: REF UserProfileBackdoor.GuestProcsRec ¬ NEW[UserProfileBackdoor.GuestProcsRec ¬ [FalseProc]] ;
Accessing profile
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;
};
Utillity PROCs
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];
};
Building profileList
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] = {
Given an open stream, parse the profile and close the stream. Successive calls "overwrite" previous entries.
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 {
it is not a comment
stream.Backup[c];
RETURN [c];
};
DO
the end of a comment is either a '\n or a double -
c ¬ stream.GetChar[];
SELECT c FROM
'\n, '\r, '\l => {
Only flush the \n if it was requested
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 => {
key was NOT followed by ':, so flush to the end of line and report the error
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];
};
Reporting errors
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;
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).
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
}
];
};
RopeProfile Interface
GetRopeProfile: PUBLIC PROC RETURNS [ROPE] = {
RETURN[ropeProfile];
};
SetRopeProfile: PUBLIC PROC [rope: ROPE ¬ NIL] = {
ropeProfile ¬ rope;
ProfileChangedInternal[edit, FALSE];
};
Exported to UserProfileBackdoor
RegisterGuestProcs: PUBLIC PROC [newProcs: REF UserProfileBackdoor.GuestProcsRec] = {
GuestProcs ¬ newProcs;
IsGuestProcess ¬ newProcs.IsGuestProcess;
};
default IsGuestProcess (not really exported)
FalseProc: PROC RETURNS [isGuest: BOOL] = {
isGuest ¬ FALSE;
};
Watching for profile changes
<< 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;
}; >>
Commander commands
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];
Notice that this necessarily includes the CR. Also notice that this won't take effect until UpdateProfileEntries.
};
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.
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.