DIRECTORY
Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, Digit, Letter, SP],
BasicTime USING [GMT, nullGMT, Period],
Booting USING [RegisterProcs, RollbackProc, switches],
DefaultRemoteNames USING [DefaultNames, Get],
DFOperations
USING [
BringOver, ChoiceInteraction, ChoiceResponse, Choices, DFInfoInteraction, FileInteraction, InfoInteraction, InteractionProc, YesNoInteraction, YesNoResponse],
DFUtilities USING [DateToRope],
FS USING [ComponentPositions, Copy, Error, ExpandName, FileInfo, StreamOpen],
GermSwap USING [Switch],
IdleExtras USING [IdleHandler, RegisterIdleHandler],
IO USING [
Close, EndOfStream, Error, GetChar, GetTime, GetTokenRope, EraseChar, IDProc, PutChar, PutF, PutFR, PutRope, rope, STREAM, time],
Process USING [SecondsToTicks],
Rope USING [Cat, Concat, Equal, Fetch, FromChar, Length, ROPE, Run, SkipTo, Substr],
SimpleTerminal USING [InputTimeout, SetInputTimeout, TurnOff, TurnOn],
SystemVersion USING [release],
Terminal USING [BlinkBWDisplay, Current],
UserCredentials USING [Get],
UserProfile USING [ListOfTokens, ProfileChanged];
FileDesc: TYPE = RECORD [name: ROPE ← NIL, date: BasicTime.GMT ← BasicTime.nullGMT];
DFList: TYPE = REF DFListObject;
DFListObject: TYPE = RECORD [head: LIST OF FileDesc ← NIL, tail: LIST OF FileDesc ← NIL];
systemID:
ROPE =
IO.PutFR["%g.%g.%g",
[cardinal[SystemVersion.release.major]],
[cardinal[SystemVersion.release.minor]],
[cardinal[SystemVersion.release.patch]]
];
systemID2:
ROPE =
IO.PutFR["%g.%g",
[cardinal[SystemVersion.release.major]],
[cardinal[SystemVersion.release.minor]]
];
stamp: FileDesc ← [];
longDialogue: BOOL ← Booting.switches[l];
development:
BOOL;
Initialized by InitUserRelatedInfo. If TRUE, forces retrieval of BootEssentials every time we are fully booted.
haveEssentials: BOOL ← FALSE;
haveProfile: BOOL ← FALSE;
essentials: FileDesc ← [];
profile: FileDesc ← [];
RRA: the profile is both read from and written to the installation stamp, but our current algorithm for determining the validity of the loacl profile does NOT use this information. This makes it primarily useful for debugging.
profileDFList: DFList = NEW[DFListObject ← []];
newProfileDFList: DFList = NEW[DFListObject ← []];
ParseInstallationStamp:
PROC = {
ForgetStamp[];
stamp ← TryFile[Rope.Concat["InstallationStamp.", systemID]].desc;
IF stamp.date ~= BasicTime.nullGMT
THEN {
in: IO.STREAM = FS.StreamOpen[stamp.name ! FS.Error => GO TO noStamp];
GetFileDesc:
PROC RETURNS [desc: FileDesc] = {
desc.name ← StripVersion[in.GetTokenRope[IO.IDProc].token];
desc.date ← in.GetTime[];
};
DO
ENABLE {
IO.EndOfStream, IO.Error => GO TO parsingProblem;
FS.Error => IF error.group = $user THEN GO TO parsingProblem ELSE REJECT;
};
itemName: ROPE = in.GetTokenRope[IO.IDProc ! IO.EndOfStream => EXIT].token;
SELECT TRUE FROM
itemName.Equal["Essentials"] =>
IF essentials.name = NIL THEN essentials ← GetFileDesc[]
ELSE GO TO parsingProblem;
itemName.Equal["Profile"] =>
IF profile.name = NIL THEN profile ← GetFileDesc[]
ELSE GO TO parsingProblem;
itemName.Equal["UserDF"] => AddToDFList[profileDFList, GetFileDesc[]];
itemName.Equal["User"] => {
This item is the LAST in the installation stamp
userInProfile: ROPE = in.GetTokenRope[IO.IDProc].token;
IF NOT Rope.Equal[user, userInProfile,
FALSE]
THEN {
The profile and profileDFList are both bogus, but the essentials may be OK.
profile ← [];
profileDFList^ ← [];
};
EXIT;
};
ENDCASE => GO TO parsingProblem;
REPEAT
parsingProblem => {in.Close[]; GO TO noStamp};
ENDLOOP;
in.Close[];
};
EXITS
noStamp => ForgetStamp[];
};
WriteStamp:
PROC = {
out:
IO.
STREAM =
FS.StreamOpen[StripVersion[stamp.name], $create
! FS.Error => GO TO noStamp];
PutFileDesc:
PROC [head:
ROPE, desc: FileDesc] = {
out.PutF["%g: %g %g\N",
IO.rope[head],
IO.rope[desc.name],
IO.rope[DFUtils.DateToRope[[$explicit, desc.date]]]
];
};
Note: If the stamp was originally missing or invalid and the user explicitly prohibited retrieval of boot essentials, 'essentials' will be garbage. In this case, we don't write a new "Essentials" entry, and the user will be hassled the next time he boots.
IF essentials.name ~=
NIL AND essentials.date ~= BasicTime.nullGMT
THEN
PutFileDesc["Essentials", essentials];
IF profile.name ~=
NIL AND profile.date ~= BasicTime.nullGMT
THEN
PutFileDesc["Profile", profile];
FOR l:
LIST OF FileDesc ← newProfileDFList.head, l.rest
UNTIL l =
NIL DO
IF l.first.date ~= BasicTime.nullGMT THEN PutFileDesc["UserDF", l.first];
ENDLOOP;
out.PutF["User: %g\n", [rope[user]]];
We depend on the User item being written last!
out.Close[];
stamp ← [];
};
ForgetStamp:
PROC = {
essentials ← profile ← [];
profileDFList^ ← [];
};
FindBootEssentials:
PROC = {
Upon return, if 'haveEssentials' is TRUE, the contents of 'essentials' are unpredictable. If 'haveEssentials' is FALSE, however, 'essentials' describes an extant file to which BringOver is to be applied.
IF longDialogue
THEN
haveEssentials ←
Confirm["Shall I assume the local files essential for booting are current?", FALSE];
IF ~haveEssentials
THEN {
name: ROPE = "BootEssentials.df";
desc: FileDesc ← TryFile[name, topPath].desc;
SELECT TRUE FROM
desc.date ~= BasicTime.nullGMT =>
haveEssentials ←
~ development AND
essentials.date = desc.date AND
essentials.name.Equal[StripVersion[desc.name], FALSE] AND
(~longDialogue
OR ~Confirm[
desc.name.Concat[
" was brought over the last time; shall I do it again anyway?"],
TRUE
]);
ENDCASE =>
WHILE Confirm["Do you want to specify a DF file for booting essentials?"]
DO
fileName: ROPE = FS.ExpandName[name, topPath].fullFName;
out.PutRope["DF file for booting essentials: "];
desc ← TryFile[GetID[fileName ! Rubout => LOOP]].desc;
IF desc.date = BasicTime.nullGMT
THEN out.PutRope["...not found\N"]
ELSE {out.PutChar[Ascii.CR]; EXIT};
REPEAT
FINISHED => {
out.PutRope["The files essential for booting can't be found. I give up.\N"];
Die[];
};
ENDLOOP;
essentials ← desc;
};
};
FindUserProfile:
PROC = {
Upon return, if 'haveProfile' is TRUE, no profile change notification is required. If 'haveProfile' is FALSE, however, any necessary file retrieval has occurred and, if the fields of 'profile' do not have their default values, they describe a remote file to be included in the stamp. Profile change notification should occur after the BringOver of the boot essentials is completed, so that the system default profile can be mentioned therein and noticed at the proper time.
localDesc, remoteDesc: FileDesc;
attachedTo: ROPE;
[localDesc, attachedTo] ← TryFile[localProfile, homeDir];
remoteDesc ← TryFile[localProfile, userPath].desc;
profile ← [];
SELECT TRUE FROM
localDesc.date = BasicTime.nullGMT
OR localDesc.date # remoteDesc.date => {
There is no personal profile on the local disk OR it has a different date from the default remote profile. The user gets the opportunity to specify one; if he chooses not to, the system default will be used (we assume the files essential for booting include a default profile).
installIfTimeout: BOOL ← TRUE;
IF localDesc.date#BasicTime.nullGMT
AND remoteDesc.date#BasicTime.nullGMT
AND
BasicTime.Period[remoteDesc.date, localDesc.date] > 0
THEN
{ OpenTerminal[];
out.PutF["Local profile is newer (%g) than remote version (%g)\n",
IO.time[localDesc.date], IO.time[remoteDesc.date]];
};
WHILE Confirm["Do you wish to install a personal profile?", installIfTimeout]
DO
Loops until remote file is located or user decides against using one.
profileName: ROPE ← FS.ExpandName[localProfile, userPath].fullFName;
remote: FileDesc ← [];
out.PutRope[" Personal profile name: "];
remote ← TryFile[GetID[profileName ! Rubout => LOOP]].desc;
IF remote.date = BasicTime.nullGMT
THEN {
out.PutRope["...not found\N"];
installIfTimeout ← ~remote.name.Equal[profileName];
}
ELSE {
Copy the remote file to both local places for luck.
out.PutChar[Ascii.CR];
profile ← remote;
FS.Copy[
from: remote.name,
to: Rope.Concat[homeDir, localProfile],
keep: 2,
attach: TRUE];
EXIT
};
ENDLOOP;
};
ENDCASE => {
There is a local profile on the disk, and it matches the remote profile. So we trust the local profile.
haveProfile ← TRUE;
IF attachedTo ~= NIL THEN profile ← [attachedTo, localDesc.date];
};
};
BringOverUserDFs:
PROC = {
dfNames: LIST OF ROPE ← UserProfile.ListOfTokens["Installation.BringOver"];
remoteNames: DefaultRemoteNames.DefaultNames = DefaultRemoteNames.Get[];
prevRelease: ROPE = remoteNames.previous;
prevReleaseLen: INT = Rope.Length[prevRelease];
IF longDialogue
THEN
SELECT TRUE FROM
dfNames ~= NIL AND
Confirm["Shall I ignore the profile's list of DF files to bring over?",
FALSE] =>
dfNames ← NIL;
profileDFList.head ~= NIL AND
Confirm["Shall I ignore DF files previously brought over?",
FALSE] =>
profileDFList^ ← [];
ENDCASE;
newProfileDFList^ ← [];
FOR dfList:
LIST OF ROPE ← dfNames, dfList.rest
UNTIL dfList =
NIL DO
desc: FileDesc = TryFile[dfList.first].desc;
IF Rope.Run[prevRelease, 0, desc.name, 0,
FALSE] = prevReleaseLen
THEN {
We should not bring over anything from the previous release, since that can screw up the local disk. This is a simple, conservative test, but who knows how well it will work?
out.PutF["%g {%g} was from the previous release and was ignored.\N",
IO.rope[desc.name],
IO.rope[DFUtils.DateToRope[[$explicit, desc.date]]]
];
LOOP;
};
IF InDFList[profileDFList, desc]
THEN {
IF out ~=
NIL THEN
out.PutF["%g {%g} previously brought over.\N",
IO.rope[desc.name],
IO.rope[DFUtils.DateToRope[[$explicit, desc.date]]]
];
AddToDFList[newProfileDFList, desc];
}
ELSE {
e, w: INT;
CheckAutoConfirm[desc.name];
[errors: e, warnings: w] ←
DFOps.BringOver[dfFile: desc.name, filter: [filterB: $public], interact: Interact];
IF e + w = 0 THEN AddToDFList[newProfileDFList, desc];
};
ENDLOOP;
};
InDFList:
PROC [list: DFList, desc: FileDesc]
RETURNS [
BOOL ←
FALSE] = {
FOR l:
LIST OF FileDesc ← list.head, l.rest
UNTIL l =
NIL DO
IF l.first.date = desc.date
AND
StripVersion[desc.name].Equal[StripVersion[l.first.name], FALSE] THEN RETURN[TRUE];
ENDLOOP;
};
AddToDFList:
PROC [list: DFList, desc: FileDesc] = {
descL: LIST OF FileDesc = CONS[desc, NIL];
IF list.head = NIL THEN list.head ← descL ELSE list.tail.rest ← descL;
list.tail ← descL;
};
installedUser: ROPE ← NIL;
user: ROPE ← NIL;
localProfile: ROPE ← NIL;
userPath: ROPE ← NIL;
topPath: ROPE ← NIL;
homeDir: ROPE ← "///";
InitUserRelatedInfo:
PROC = {
Strictly speaking we initialize more here than just the user related info, but it is a good place to initialize various flags and paths.
remoteNames: DefaultRemoteNames.DefaultNames = DefaultRemoteNames.Get[];
installedUser ← UserCredentials.Get[].name;
user ← installedUser.Substr[len: installedUser.SkipTo[skip: "."]];
localProfile ← user.Concat[".profile"];
userPath ← Rope.Cat[remoteNames.userHost, "<", user, ">", systemID2, ">"];
topPath ← Rope.Concat[remoteNames.current, "Top>"];
development ←
Booting.switches[d] OR
TryFile["Released.switch", topPath].desc.date = BasicTime.nullGMT;
};
OpenTerminal:
PROC = {
IF in ~= NIL THEN RETURN;
[in, out] ← SimpleTerminal.TurnOn[];
out.PutChar[Ascii.CR];
};
CloseTerminal:
PROC = {
IF in = NIL THEN RETURN;
SimpleTerminal.TurnOff[];
in ← out ← NIL;
};
TryFile:
PROC [shortName, prefix:
ROPE ←
NIL]
RETURNS [desc: FileDesc ← [], attachedTo: ROPE ← NIL] = {
desc.name ← FS.ExpandName[name: shortName, wDir: prefix].fullFName;
[fullFName: desc.name, attachedTo: attachedTo, created: desc.date] ←
FS.FileInfo[name: desc.name ! FS.Error => CONTINUE];
};
StripVersion:
PROC [old:
ROPE]
RETURNS [new:
ROPE] = {
cp: FS.ComponentPositions;
[fullFName: new, cp: cp] ← FS.ExpandName[old];
new ← new.Substr[len: cp.ext.start+cp.ext.length];
};
CheckAutoConfirm:
PROC [df:
ROPE] = {
IF longDialogue
AND autoConfirm
THEN {
msg:
ROPE =
Rope.Cat["Do you wish to confirm retrieval of each file from ", df, " individually?"];
autoConfirm ← ~Confirm[msg, FALSE];
};
};
Die: PROC = {DO ENDLOOP};