-- Salvager.mesa;
--Maurice Herlihy August 29, 1982
--Mike Schroeder, October 6, 1982 9:17 am
--Roy Levin, November 2, 1982 4:35 pm

DIRECTORY
 CedarInitOps  USING [salvageLSD],
CIFS    USING [Error, ErrorCode],
 ConvertUnsafe USING [ToRope],
 Directory   USING [DeleteFile, Error, GetProperty, PropertyType],
 File    USING [Capability, Delete, nullCapability],
 FileStream  USING [GetLeaderPropertiesForCapability],
 FT    USING [GetBaseFreeSpace],
 FtpMan   USING [GetFileInfo],
 KernelFile  USING [GetNextFile],
LSD    USING [Create, Entry, Lookup, SetDirty, Unlock],
 PupDefs   USING [PupPackageMake],
 Rope    USING [Find, ROPE, Substr],
 Volume   USING [ID, SystemID];

Salvager: MONITOR

IMPORTS
CedarInitOps, CIFS, ConvertUnsafe, Directory, File, FileStream, FT, FtpMan, KernelFile, LSD, PupDefs, Rope, Volume

= {
-- Remote Name Property from Eric's code
RemoteNameProperty: Directory.PropertyType = LOOPHOLE[213B];
ROPE: TYPE = Rope.ROPE;

Salvage
: PROC[] = {
-- Strategy:
-- Delete the LSD.
-- Iterate through each file on the disk,
-- putting remote files in the lsd with create date 0.
-- If duplicates are encountered, keep the most recently written.
-- Build up a list of files to be deleted at the end.

 VictimList: TYPE = REF VictimRec;
 VictimRec: TYPE = RECORD[
   file: File.Capability,
  next: VictimList
  ];

 volume: Volume.ID ← Volume.SystemID[];
 file: File.Capability ← File.nullCapability;
 victim: File.Capability ← File.nullCapability;
 fileName: STRING ← [125];
 name: ROPE;
 entry: LSD.Entry;
 victims: VictimList ← NIL;
 remoteState: {exists, doesNotExist, noResponse};
 localCreate, remoteCreate: LONG CARDINAL;

-- Crank up the pup package.
 PupDefs.PupPackageMake[];

-- Delete existing lsd, if any.
 Directory.DeleteFile["lsd.dir" ! Directory.Error => CONTINUE];

-- Krock to start FT so it will initialize session start time
 [] ← FT.GetBaseFreeSpace[];
  
-- Iterate through the files on the volume
WHILE (file ← KernelFile.GetNextFile[volume, file]) # File.nullCapability DO
  remoteState ← exists;
  remoteCreate ← 0;
  fileName.length ← 0;
  Directory.GetProperty[file: file, property: RemoteNameProperty,
  propertyValue: DESCRIPTOR[fileName, SIZE[StringBody[fileName.maxlength]]]
  -- in case of error, charge!
  ! Directory.Error => LOOP];
-- If the name doesn't start with '/, skip it.
  IF fileName.length = 0 OR fileName[0] # '/ THEN LOOP;
   name ← ConvertUnsafe.ToRope[fileName];
 localCreate ← FileStream.GetLeaderPropertiesForCapability[file].create;
 entry ← LSD.Lookup[name];
IF entry # NIL THEN { -- Found a duplicate.
  IF FileStream.GetLeaderPropertiesForCapability[entry.fc].create >= localCreate
   THEN { -- One in lsd is more current.
   victims ← NEW [VictimRec ← [file: file, next: victims]];
   LOOP; -- nothing more to do.
   }
   -- Mark lsd entry for deletion and insert new entry below.
   ELSE victims ← NEW [VictimRec ← [file: entry.fc, next: victims]];
    };
  [remoteCreate, ] ← APPLY [FtpMan.GetFileInfo, Split[name]
  ! CIFS.Error => CHECKED {remoteState ← IF code = CIFS.ErrorCode[noSuchFile]
   THEN doesNotExist ELSE noResponse; CONTINUE} ];
SELECT remoteState FROM
   noResponse => -- it may be dirty --
    MakeLSDEntry[name, file, 0, dirty];
   doesNotExist =>
    IF localCreate = 0
     THEN -- probably still clean --
      MakeLSDEntry[name, file, 0, clean]
     ELSE -- a new dirty file --
      MakeLSDEntry[name, file, localCreate - 1, dirty];
   exists =>
    SELECT localCreate FROM
     < remoteCreate => -- local file has been superceded --
      MakeLSDEntry[name, file, localCreate, recheck];
     = remoteCreate => -- same as file on remote server --
      MakeLSDEntry[name, file, localCreate, clean];
     > remoteCreate => -- local file is more recent --
      MakeLSDEntry[name, file, remoteCreate, dirty];
     ENDCASE => ERROR;
   ENDCASE => ERROR;
  ENDLOOP;
-- Delete unwanted files.
WHILE victims # NIL DO
 File.Delete[victims.file];
 victims ← victims.next;
ENDLOOP;
};

LSDFull: ERROR = CODE;
MakeLSDEntry: PROC[n: ROPE, f: File.Capability, cr: LONG CARDINAL,
        state: {clean, recheck, dirty} ]
-- make new LSD entry.
={
 e: LSD.Entry = LSD.Create [name: n, fc: f, create: cr];
IF e = NIL THEN ERROR LSDFull;
 SELECT state FROM
  clean => NULL;
  recheck => e.checked ← 0;
  dirty => LSD.SetDirty[e];
  ENDCASE => ERROR;
LSD.Unlock[e];
 };
Split: PROC[file: ROPE] RETURNS [server, name: ROPE]
-- Take in CIFS name, split off server name.
={
 fileNameStart: INT ← Rope.Find[file, "/", 1]; -- second slash in name
 name ← file.Substr[start: fileNameStart+1];
 server ← file.Substr[start: 1, len: fileNameStart - 1 ];
 }; --Split--
-- Start code.
IF CedarInitOps.salvageLSD THEN Salvage[];
}.