Ferret.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Hal Murray June 23, 1985 5:32:21 pm PDT
DIRECTORY
BasicTime USING [GMT, nullGMT, Period],
CommandTool USING [ArgumentVector, Failed, Parse],
Commander USING [CommandProc, Handle, Register],
FS USING [Delete, Error, ErrorDesc, FileInfo],
FSBackdoor USING [EntryPtr, Enumerate, Flush, highestVersion, MakeFName, noVersion, TextFromTextRep, Version],
FSName USING [BangVersionFile, ServerAndFileRopes, VersionFromRope],
FSRemoteFile USING [Info],
IO USING [PutF, STREAM],
Process USING [CheckForAbort],
Rope USING [Fetch, Find, Flatten, Length, ROPE, Substr];
Ferret: CEDAR PROGRAM
IMPORTS BasicTime, Commander, CommandTool, FS, FSBackdoor, FSName, FSRemoteFile, IO, Process, Rope
= {
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Ferret: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
out: STREAM ← cmd.out;
attachedSwitch: BOOLTRUE;
cachedSwitch: BOOLTRUE;
deleteSwitch: BOOLFALSE;
flushSwitch: BOOLFALSE;
rootName: ROPE;
localName: ROPE; -- NIL => Checking cached file
remoteName: ROPE;
thisVersion: FSBackdoor.Version;
createWanted: BasicTime.GMT;
CheckFile: PROC RETURNS [stop: BOOL] = {
versionFound: FSBackdoor.Version;
bytesFound: INT;
createFound: BasicTime.GMT ← BasicTime.nullGMT;
server, fileName: ROPE;
fsError: FS.ErrorDesc ← [ok, NIL, NIL];
stop ← AbortRequested[cmd];
[server: server, file: fileName] ← FSName.ServerAndFileRopes[remoteName];
[versionFound, bytesFound, createFound] ← FSRemoteFile.Info[
server, fileName, createWanted !
FS.Error => { fsError ← error; CONTINUE; } ];
IF fsError.group # ok THEN {
IO.PutF[out, "\n"];
IF localName # NIL THEN { -- Attached file
SELECT fsError.code FROM
$illegalName, $unknownFile, -- Remote file doesn't exist at all (no versions)
$illegalName is probably no directory.
$unknownCreatedTime => { -- Remote file exists, but not the desired versionStamp
fullFName: ROPENIL;
created: BasicTime.GMT;
IO.PutF[out, "Attached file missing from server.\n %G => %G\n", [rope[localName]], [rope[remoteName]]];
[fullFName: fullFName, created: created] ← FS.FileInfo[
name: rootName, remoteCheck: TRUE, wDir: "///" ! FS.Error => CONTINUE];
IF fullFName # NIL THEN {
SELECT BasicTime.Period[to: created, from: createWanted] FROM
> 0 => {
IO.PutF[out, " Newer local version is %G\n", [rope[fullFName]]];
deleted ← deleted.SUCC;
IF deleteSwitch THEN {
IO.PutF[out, " Deleting %G ...", [rope[localName]]];
FS.Delete[localName];
IO.PutF[out, " ok.\n"]; }
ELSE IO.PutF[out, " Would have deleted %G.\n", [rope[localName]]];
RETURN; };
= 0 => {
Groan. The highest local version of this file has the desired stamp and it is in our cache. The server has some version of this file, but not the one we want. This is dull if a better file is on the server, but it's VERY interesting if our copy is better than what's on the server.
targetVersion: FSBackdoor.Version ← ExtractVersion[remoteName];
remoteVersion: FSBackdoor.Version ← FSBackdoor.highestVersion;
remoteCreate: BasicTime.GMT;
[server: server, file: fileName] ← FSName.ServerAndFileRopes[remoteName];
fileName ← FSName.BangVersionFile[fileName, FSBackdoor.highestVersion];
[remoteVersion, , remoteCreate] ← FSRemoteFile.Info[server, fileName, BasicTime.nullGMT ! FS.Error => CONTINUE];
IF remoteVersion = FSBackdoor.highestVersion THEN {
No remote version, but directory and such are reasonable
Another opportunity for more housecleaning, but again, this path doesn't happen very often.
IO.PutF[out, " No remote version at all.\n"];
RETURN; };
fileName ← FSName.BangVersionFile[remoteName, remoteVersion];
SELECT BasicTime.Period[to: remoteCreate, from: createWanted] FROM
> 0 => {
IO.PutF[out, " Newer remote version is %G\n", [rope[fileName]]];
deleted ← deleted.SUCC;
IF deleteSwitch THEN {
IO.PutF[out, "Deleting %G ...", [rope[localName]]];
FS.Delete[localName];
IO.PutF[out, " ok.\n"]; }
ELSE IO.PutF[out, " Would have deleted %G.\n", [rope[localName]]];
RETURN; };
= 0 => ERROR;
< 0 => {
rescueMe ← rescueMe.SUCC;
IO.PutF[out, " *** Beware: Missing remote version was NEWER.\n %G >> %G\n", [rope[remoteName]], [rope[fileName]]];
IO.PutF[out, " *** There is hope. The file is in your cache!\n"]; };
ENDCASE => ERROR; };
< 0 => IO.PutF[out, " *** Beware: Missing remote version was attached to a NEWER local version %G!h\n", [rope[rootName]]];
ENDCASE => ERROR; }; };
ENDCASE => {
rescueMe ← rescueMe.SUCC;
IO.PutF[out, "*** %G\n", [rope[fsError.explanation]]]; };
RETURN; };
IF localName = NIL THEN { -- Cached file
SELECT fsError.code FROM
$illegalName, $unknownFile => { -- Remote file doesn't exist
targetVersion: FSBackdoor.Version ← ExtractVersion[remoteName];
remoteVersion: FSBackdoor.Version ← FSBackdoor.highestVersion;
remoteCreate: BasicTime.GMT;
IO.PutF[out, "Cached file missing from server.\n %G\n", [rope[remoteName]]];
[server: server, file: fileName] ← FSName.ServerAndFileRopes[rootName];
fileName ← FSName.BangVersionFile[rootName, FSBackdoor.highestVersion];
[remoteVersion, , remoteCreate] ← FSRemoteFile.Info[server, fileName, createWanted ! FS.Error => CONTINUE ];
IF remoteVersion = FSBackdoor.highestVersion THEN { -- No remote versions
IO.PutF[out, " No remote versions exist.\n"];
flushed ← flushed.SUCC;
IF flushSwitch THEN {
IO.PutF[out, " Flushing %G ...", [rope[remoteName]]];
FSBackdoor.Flush[remoteName];
IO.PutF[out, " ok.\n"]; }
ELSE IO.PutF[out, " Would have Flushed %G.\n", [rope[remoteName]]];
RETURN; }
ELSE { -- Other versions exist
fileName ← FSName.BangVersionFile[rootName, remoteVersion];
SELECT remoteVersion FROM
> targetVersion => {
IO.PutF[out, " Newer remote version is %G\n", [rope[fileName]]];
flushed ← flushed.SUCC;
IF flushSwitch THEN {
IO.PutF[out, " Flushing %G ...", [rope[remoteName]]];
FSBackdoor.Flush[remoteName];
IO.PutF[out, " ok.\n"]; }
ELSE IO.PutF[out, " Would have Flushed %G.\n", [rope[remoteName]]];
RETURN; };
= targetVersion => IO.PutF[out, " *** Beware: Remote version exists, but it has a DIFFERENT timestamp.\n\n"];
< targetVersion => {
rescueMe ← rescueMe.SUCC;
IO.PutF[out, " *** Beware: Missing remote version was NEWER.\n %G >> %G\n", [rope[remoteName]], [rope[fileName]]];
IO.PutF[out, " *** There is hope. The file is in your cache!\n"]; };
ENDCASE => ERROR; }; };
ENDCASE => {
rescueMe ← rescueMe.SUCC;
IO.PutF[out, "*** %G\n", [rope[fsError.explanation]]]; };
RETURN; }; };
IF createWanted = BasicTime.nullGMT THEN RETURN;
IF createWanted = createFound THEN RETURN;
IO.PutF[out, "%G => %G\n", [rope[localName]], [rope[remoteName]]];
RETURN; };
GrabInfo: UNSAFE PROC
[entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOLFALSE] = UNCHECKED {
WITH e: entry^ SELECT FROM
local => accept ← FALSE;
attached => {
attached ← attached.SUCC;
IF attachedSwitch THEN {
rootName ← FSBackdoor.TextFromTextRep[@entry[e.nameBody]];
localName ← FSBackdoor.MakeFName[rootName, e.version];
thisVersion ← e.version;
remoteName ← FSBackdoor.TextFromTextRep[@entry[e.attachedTo]];
createWanted ← e.created;
accept ← TRUE; }; };
cached => {
cached ← cached.SUCC;
IF cachedSwitch THEN {
rootName ← FSBackdoor.TextFromTextRep[@entry[e.nameBody]];
localName ← NIL;
remoteName ← FSBackdoor.MakeFName[rootName, e.version];
thisVersion ← FSBackdoor.noVersion;
createWanted ← BasicTime.nullGMT;
accept ← TRUE; }; };
ENDCASE => ERROR; };
passes: INT ← 0;
attached, cached, deleted, flushed, rescueMe: INT ← 0;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Failed}];
FOR i: NAT IN [1..argv.argc) DO
arg: ROPE = argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
IF Rope.Fetch[arg, 0] = '- THEN {
sense: BOOLTRUE;
FOR i: INT IN [1..Rope.Length[arg]) DO
SELECT Rope.Fetch[arg, i] FROM
'~ => {sense ← NOT sense; LOOP};
'd, 'D => deleteSwitch ← sense;
'f, 'F => flushSwitch ← sense;
ENDCASE;
sense ← TRUE;
ENDLOOP;
LOOP; };
FSBackdoor.Enumerate[
volName: NIL, nameBodyPattern: Rope.Flatten[arg], localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: GrabInfo, acceptProc: CheckFile];
passes ← passes.SUCC;
ENDLOOP;
IF passes = 0 THEN FSBackdoor.Enumerate[
volName: NIL, nameBodyPattern: NIL, localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: GrabInfo, acceptProc: CheckFile];
IO.PutF[out, "\nProcessed %G attachments and %G cached files.\n", [integer[attached]], [integer[cached]]];
IF deleted # 0 THEN {
IF deleteSwitch THEN IO.PutF[out, "%G files were deleted.\n", [integer[deleted]]]
ELSE IO.PutF[out, "%G files would have been deleted.\n", [integer[deleted]]]; };
IF flushed # 0 THEN {
IF flushSwitch THEN IO.PutF[out, "%G files were flushed.\n", [integer[flushed]]]
ELSE IO.PutF[out, "%G files would have been flushed.\n", [integer[flushed]]]; };
IF rescueMe # 0 THEN IO.PutF[out, "***** %G files need rescuing by hand. *****\n", [integer[rescueMe]]];
EXITS Failed => result ← $Failure; };
ExtractVersion: PROC [fileName: ROPE] RETURNS [version: FSBackdoor.Version] = {
bang: INT ← Rope.Find[fileName, "!", 0];
IF bang < 0 THEN RETURN[FSBackdoor.noVersion];
version ← FSName.VersionFromRope[Rope.Substr[fileName, bang+1]]; };
AbortRequested: PROC [cmd: Commander.Handle] RETURNS [BOOL] = {
Process.CheckForAbort[];
RETURN [FALSE]; };
Init: PROCEDURE = {
Commander.Register[
"///Commands/Ferret", Ferret,
"Ferret -- Find files in cache that are NOT on the server. (Finds lots of junk too.)"]; };
Initialization
Init[];
}.