UserProfileImpl.mesa;
Teitelman on January 13, 1983 1:52 pm
Levin, October 6, 1983 12:03 pm
Russ Atkinson, October 4, 1983 11:17 am
Paul Rovner on December 12, 1983 8:45 pm
DIRECTORY
Booting USING [switches, RollbackProc, RegisterProcs],
Convert USING [IntFromRope],
File USING [IsDebugger, SystemVolume],
FS USING [FileInfo, Error, StreamOpen],
IO
USING [
Backup, BreakProc, Close, CR, EndOfStream, Error, GetChar, GetIndex, GetRopeLiteral, GetTokenRope, int, PeekChar, PutF, PutFR, rope, SP, STREAM, TAB, time],
Process USING [Detach],
Rope USING [Cat, Concat, Equal, ROPE, SkipTo, Substr],
RuntimeError USING [UNCAUGHT],
UserCredentials USING [CredentialsChangeProc, Get, RegisterForChange],
UserProfile USING [ProfileChangeReason, ProfileChangedProc]
;
UserProfileImpl: CEDAR MONITOR
IMPORTS Booting, Convert, File, FS, IO, Process, Rope, RuntimeError, UserCredentials
EXPORTS UserProfile
= 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
];
Accessing profile
Boolean:
PUBLIC
PROC [key:
ROPE, default:
BOOL ←
FALSE]
RETURNS [value:
BOOL] = {
entry: ProfileEntry = Lookup[key];
val: ROPE = 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 = Lookup[key];
val: ROPE = 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 = Lookup[key];
val: ROPE = 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 = Lookup[key];
IF entry = NIL THEN RETURN[default]
ELSE RETURN[entry.tokens];
};
Line:
PUBLIC PROC [key:
ROPE, default:
ROPE]
RETURNS [value:
ROPE] = {
entry: ProfileEntry = 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 File.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] = {
RETURN[profileName];
};
Building profileList
profileList: ProfileList ← NIL;
profileName: ROPE ← NIL;
ParseProfile:
ENTRY
PROC [startNewErrorLog:
BOOL ←
FALSE] = {
OPEN IO;
name: ROPE;
stream: IO.STREAM;
CheckForFile:
PROC [file:
ROPE]
RETURNS [found:
BOOL ←
TRUE] = {
[] ← FS.FileInfo[file ! FS.Error => {found ← FALSE; CONTINUE}];
};
IF startNewErrorLog THEN errorLog ← NIL;
profileList ← NIL;
profileName ← NIL;
IF Booting.switches[p] THEN RETURN; -- return default values for the profile
name ← UserCredentials.Get[].name;
name ← name.Substr[0, name.SkipTo[0, "."]];
profileName ← name.Concat[".profile"];
IF NOT CheckForFile[profileName] THEN profileName ← "User.profile";
{
ENABLE RuntimeError.UNCAUGHT => GOTO Out;
stream ← FS.StreamOpen[fileName: profileName, accessOptions: $read];
DO
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;
};
IF stream # NIL THEN stream.Close[];
};
Reporting errors
Report:
ENTRY
PROC [entry: ProfileEntry ←
NIL, msg:
ROPE] = {
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] = {
IF errorLog #
NIL
THEN {
errorLog.Close[];
errorLog ← NIL;
RETURN["UserProfile.log"];
}
ELSE RETURN[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 wer 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--, clientData: REF ANY ← NIL--] = {
itemL: LIST OF ProfileChangedItem = CONS[[proc, --clientData--NIL], NIL];
AcquireList[];
IF profileChangedList = NIL THEN profileChangedList ← itemL
ELSE profileChangedListLast.rest ← itemL;
profileChangedListLast ← itemL;
ReleaseList[];
DoIt[itemL.first, firstTime];
};
ProfileChanged:
PUBLIC
PROC [reason: UserProfile.ProfileChangeReason] = {
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
}
];
};
NoticeCredentialsChange: UserCredentials.CredentialsChangeProc = {
TRUSTED{Process.Detach[FORK ProfileChanged[rollBack]]};
};
NoticeRollback: Booting.RollbackProc = {ProfileChanged[rollBack]};
AcquireList:
ENTRY
PROC = {
WHILE listInUse DO WAIT listAvailable; ENDLOOP;
listInUse ← TRUE;
};
ReleaseList:
ENTRY
PROC = {
listInUse ← FALSE;
BROADCAST listAvailable;
};
Initialization
UserCredentials.RegisterForChange[proc: NoticeCredentialsChange, clientData: NIL];
TRUSTED {Booting.RegisterProcs[r: NoticeRollback]};
ParseProfile[! RuntimeError.UNCAUGHT => CONTINUE];
END.