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: BOOLFALSE] 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: ROPENIL;
ParseProfile: ENTRY PROC [startNewErrorLog: BOOLFALSE] = {
OPEN IO;
name: ROPE;
stream: IO.STREAM;
CheckForFile: PROC [file: ROPE] RETURNS [found: BOOLTRUE] = {
[] ← 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: BOOLFALSE] 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: BOOLFALSE] = {
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: ROPENIL;
tokens, tail: LIST OF ROPENIL;
position: INT;
key: ROPENIL;
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 ROPENIL;
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;
EXITS
Out => NULL;
};
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.STREAMNIL;
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: BOOLFALSE;
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.