UserProfileImpl.mesa;
Copyright © 1984, 1985 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
DIRECTORY
Booting USING [switches],
Convert USING [IntFromRope],
File USING [SystemVolume],
FileBackdoor USING [IsDebugger],
FS USING [Error, GetName, OpenFileFromStream, StreamOpen],
IO USING [Backup, BreakProc, Close, CR, EndOfStream, Error, GetChar, GetIndex, GetRopeLiteral, GetTokenRope, int, PeekChar, PutF, PutFR, rope, SP, STREAM, TAB, time],
Rope USING [Cat, Concat, Equal, ROPE, SkipTo, Substr],
RuntimeError USING [UNCAUGHT],
ThisMachine USING [Name],
UserCredentials USING [Get],
UserProfile USING [ProfileChangeReason, ProfileChangedProc],
UserProfileBackdoor USING [GuestProcsRec];
UserProfileImpl:
CEDAR
MONITOR
IMPORTS Booting, Convert, File, FileBackdoor, FS, IO, Rope, RuntimeError, ThisMachine, UserCredentials
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, val.Concat[" 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, val.Concat[" 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] = {
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, "extra material on line"];
};
GetProfileName:
PUBLIC
PROC
RETURNS [
ROPE] = {
IF IsGuestProcess[]
THEN RETURN GuestProcs.GetProfileName[]
ELSE RETURN[profileName];
};
Building profileList
profileList: ProfileList ← NIL;
profileName: ROPE ← NIL;
ParseProfile:
ENTRY
PROC [startNewErrorLog:
BOOL ←
FALSE] = {
ENABLE UNWIND => NULL;
stream: IO.STREAM ← NIL;
name: ROPE ← UserCredentials.Get[].name;
IF startNewErrorLog THEN errorLog ← NIL;
profileList ← NIL;
profileName ← NIL;
IF Booting.switches[p] THEN RETURN; -- return default values for the profile
name ← Rope.Cat["///", name.Substr[0, name.SkipTo[0, "."]], ".profile"];
stream ←
FS.StreamOpen[fileName: Rope.Cat["///", ThisMachine.Name[], ".machineProfile"]
! FS.Error => CONTINUE
];
IF stream #
NIL
THEN {
There is a machine profile for machine-dependent stuff
ParseProfileInternal[stream];
stream ← NIL;
};
stream ←
FS.StreamOpen[fileName: "///Server.profile"
! FS.Error => CONTINUE
];
IF stream #
NIL
THEN {
The presence of "///Server.profile" overrides a user's profile (for use by servers).
profileName ← "///Server.profile";
ParseProfileInternal[stream];
RETURN;
};
stream ←
FS.StreamOpen[fileName: name
! FS.Error => CONTINUE
];
IF stream #
NIL THEN {
The user has supplied a profile
profileName ← name;
ParseProfileInternal[stream];
RETURN;
};
Use the default profile (if any)
stream ←
FS.StreamOpen[fileName: "///User.profile"
! FS.Error => CONTINUE
];
IF stream #
NIL
THEN {
profileName ← "///User.profile";
ParseProfileInternal[stream];
};
};
ParseProfileInternal:
INTERNAL
PROC [stream:
IO.
STREAM] = {
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 => 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 => {
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 => RETURN;
'" => token ← stream.GetRopeLiteral[];
ENDCASE => token ← stream.GetTokenRope[tokenProc].token;
};
tokenProc:
IO.BreakProc = {
RETURN[
SELECT char
FROM
IO.SP, IO.TAB, ', => sepr,
IO.CR, ': => break,
ENDCASE => other
];
};
token: ROPE ← NIL;
tokens, tail: LIST OF ROPE ← NIL;
position: INT;
key: ROPE ← NIL;
LocalToken[TRUE];
IF (key ← token) = NIL THEN EXIT;
SELECT SkipWhite[]
FROM
': => [] ← stream.GetChar[]; -- flush the ':
ENDCASE => {
key was NOT followed by ':, so flush to the end of line and report the error
DO
IF stream.GetChar[ ! IO.EndOfStream => EXIT] = '\n THEN EXIT;
ENDLOOP;
ReportInternal[msg: IO.PutFR["missing : at [%d]", IO.int[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 LookupInternal[key] #
NIL
THEN
ReportInternal[
entry: LookupInternal[key],
msg: IO.PutFR["%g also appears at [%d]", IO.rope[key], IO.int[position]]
];
profileList ← CONS[NEW[ProfileRecord ← [key, tokens, position]], profileList];
ENDLOOP;
IO.Close[stream];
};
Reporting errors
Report:
ENTRY
PROC [entry: ProfileEntry ←
NIL, msg:
ROPE] = {
ENABLE UNWIND => NULL;
ReportInternal[entry, msg];
};
ReportInternal:
INTERNAL
PROC [entry: ProfileEntry ←
NIL, msg:
ROPE] = {
ENABLE RuntimeError.UNCAUGHT => CONTINUE;
IF errorLog =
NIL
THEN {
errorLog ← FS.StreamOpen[fileName: "///UserProfile.log", accessOptions: $create];
errorLog.PutF["Processing %g at %t", IO.rope[profileName], IO.time[]];
};
errorLog.PutF["\n\n%g", IO.rope[msg]];
IF entry #
NIL
THEN
errorLog.PutF[", at %g [%d]", IO.rope[entry.key], IO.int[entry.position]];
};
GetErrorLog:
PUBLIC
ENTRY
PROC
RETURNS [fileName:
ROPE ←
NIL] = {
ENABLE UNWIND => {errorLog ← NIL; NULL};
IF errorLog #
NIL
THEN {
fileName ← FS.GetName[FS.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;
listInUse: BOOL ← FALSE;
listAvailable: CONDITION ← [timeout: 0];
CallWhenProfileChanges:
PUBLIC
PROC [proc: UserProfile.ProfileChangedProc] = {
itemL: LIST OF ProfileChangedItem = CONS[[proc, --clientData--NIL], NIL];
IF IsGuestProcess[] THEN {GuestProcs.CallWhenProfileChanges[proc]; RETURN};
AcquireList[];
IF profileChangedList = NIL THEN profileChangedList ← itemL
ELSE profileChangedListLast.rest ← itemL;
profileChangedListLast ← itemL;
ReleaseList[];
DoIt[itemL.first, firstTime];
};
ProfileChanged:
PUBLIC
PROC [reason: UserProfile.ProfileChangeReason] = {
IF IsGuestProcess[] THEN {GuestProcs.ProfileChanged[reason]; RETURN};
AcquireList[];
ParseProfile[startNewErrorLog: TRUE ! RuntimeError.UNCAUGHT => CONTINUE];
FOR l:
LIST
OF ProfileChangedItem ← profileChangedList, l.rest
UNTIL l =
NIL
DO
DoIt[l.first, reason];
ENDLOOP;
ReleaseList[];
};
DoIt:
PROC [item: ProfileChangedItem, reason: UserProfile.ProfileChangeReason] = {
item.proc[reason
--, item.clientData--
! RuntimeError.
UNCAUGHT => {
Report[msg: "Problem while executing ProfileChangedProc"];
CONTINUE
}
];
};
AcquireList:
ENTRY
PROC = {
WHILE listInUse DO WAIT listAvailable; ENDLOOP;
listInUse ← TRUE;
};
ReleaseList:
ENTRY
PROC = {
listInUse ← FALSE;
BROADCAST listAvailable;
};
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;
};
NO initialization, since InstallerImpl has that responsibility!
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