The LogManager provides an object which methods take a Pathname.T
   and produce readers and writers for log and checkpoint files.  It is
   used by stable objects.
   The default log mananger uses the nm paramter as the name of a
   directory in the filesystem.  In this directory it will put the log and
   checkpointfiles. 
MODULE\subsection{produce filenames}; IMPORT Pathname, Thread, Wr, FileWr, Rd, FileRd, FS, OSError, Atom, AtomList, FSError; IMPORT Log; FROM Log IMPORT CrashPoint; REVEAL T = Public BRANDED OBJECT END; REVEAL Default = DefaultPublic BRANDED OBJECT METHODS (* produce the complete filenames: *) cpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := Cpfn; newcpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := NewCpfn; oldcpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := OldCpfn; dummycp (nm: Pathname.T): Pathname.T RAISES {OSError.E} := DummyCp; logfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := Logfn; OVERRIDES init := Init; reOpenLog := ReOpenLog; beginCheckpoint := BeginCheckpoint; endCheckpoint := EndCheckpoint; recover := Recover; recoverable := Recoverable; emptyLog := EmptyLog; dispose := Dispose; END; LogManager 
PROCEDURE\subsection{Public Methods}Logfn (<*UNUSED*> lm: Default; nm: Pathname.T): Pathname.T = (* log filename *) BEGIN RETURN Pathname.Join(nm, "log", NIL) END Logfn; PROCEDURECpfn (<*UNUSED*> lm: Default; nm: Pathname.T): Pathname.T = (* checkpoint filename *) BEGIN RETURN Pathname.Join(nm, "checkpoint", NIL) END Cpfn; PROCEDUREOldCpfn (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* old (backup) checkpoint filename *) BEGIN RETURN lm.cpfn(nm) & ".old" END OldCpfn; PROCEDURENewCpfn (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* new checkpoint filename *) BEGIN RETURN lm.cpfn(nm) & ".new" END NewCpfn; PROCEDUREDummyCp (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* dummy checkpoint filename for bootstrapping *) BEGIN RETURN lm.cpfn(nm) & ".dummy" END DummyCp;
PROCEDUREInit (lm: Default): Default =
No dynamic initialization needed:
  BEGIN
    RETURN lm;
  END Init;
PROCEDURE ReOpenLog  (lm: Default; nm: Pathname.T): Wr.T
  RAISES {OSError.E} =
  BEGIN
    RETURN FileWr.OpenAppend(lm.logfn(nm))
  END ReOpenLog;
PROCEDURE BeginCheckpoint  (lm: Default; nm: Pathname.T): Wr.T
  RAISES {OSError.E} =
  BEGIN
    Log.PutText("BeginCheckpoint: ");
    (**)
    CrashPoint(101);
    TestDir(nm);
    IF TestFile(lm.cpfn(nm)) THEN
      FS.Rename(lm.cpfn(nm), lm.oldcpfn(nm));
      Log.PutText("renamed checkpointfile to " & lm.oldcpfn(nm));
      Log.Nl();
    ELSE
      TRY
        <*FATAL Thread.Alerted*>
        BEGIN
          Wr.Close(FileWr.Open(lm.dummycp(nm)));
        END
      EXCEPT
        Wr.Failure =>
          RAISE
            OSError.E(AtomList.List1(
                        Atom.FromText("error creating dummy checkpoint "
                                        & lm.dummycp(nm))));
      END;
      Log.PutText("created dummy checkpoint " & lm.dummycp(nm));
      Log.Nl();
    END;
    Log.PutText("new checkpointfile " & lm.newcpfn(nm));
    Log.Nl();
    RETURN FileWr.Open(lm.newcpfn(nm));
  END BeginCheckpoint;
PROCEDURE EndCheckpoint  (lm: Default; nm: Pathname.T): Wr.T
  RAISES {OSError.E} =
  BEGIN
    Log.PutText("EndCheckpoint: ");
    Log.PutText("renaming checkpoint " & lm.newcpfn(nm));
    Log.Nl();
    (**)
    CrashPoint(103);
    FS.Rename(lm.newcpfn(nm), lm.cpfn(nm));
    (**)
    CrashPoint(104);
    VAR newLog := FileWr.Open(lm.logfn(nm));
    BEGIN
      IF TestFile(lm.dummycp(nm)) THEN
        Log.PutText("deleting dummy checkpoint" & lm.dummycp(nm));
        Log.Nl();
        (**)
        CrashPoint(105);
        FS.DeleteFile(lm.dummycp(nm));
      ELSE
        Log.PutText("deleting old checkpoint" & lm.oldcpfn(nm));
        Log.Nl();
        (**)
        CrashPoint(105);
        FS.DeleteFile(lm.oldcpfn(nm));
      END;
      (**)
      CrashPoint(106);
      RETURN newLog;
    END;
  END EndCheckpoint;
PROCEDURE Recover  (lm: Default; nm: Pathname.T; VAR log, checkp: Rd.T)
  RAISES {OSError.E} =
  (* Check possible leftovers from checkpoint-crashes: This procedure
     tolerates absence of logfiles since itself deletes logfiles when
     invalid.  Usually there are always logfiles (with possible size zero)
     since there is no way of deleting logfiles using the default
     logmanager. *)
  BEGIN
    (* (1) renaming to final name is the commit for writing a new
       checkpoint.  If there is a checkpoint with temporary name then it is
       invalid and the log file belongs to the old version. *)
    IF TestFile(lm.newcpfn(nm)) OR NOT TestFile(lm.cpfn(nm)) THEN
      Log.PutText("found valid backup");
      Log.Nl();
      FS.Rename(lm.oldcpfn(nm), lm.cpfn(nm)); (* must exist or fatal
                                                 error *)
      IF TestFile(lm.newcpfn(nm)) THEN
        Log.PutText("removing new checkpoint");
        Log.Nl();
        FS.DeleteFile(lm.newcpfn(nm))
      END;
      IF NOT TestFile(lm.logfn(nm)) THEN
        log := NIL
      ELSE
        log := FileRd.Open(lm.logfn(nm));
      END;
      (* (2) there is a checkpoint with final name.  Thus it is valid.  If
         there is a backup checkpoint than the log is invalid. *)
    ELSIF TestFile(lm.cpfn(nm)) AND TestFile(lm.oldcpfn(nm)) THEN
      Log.PutText("found valid new checkpoint");
      Log.Nl();
      Log.PutText("deleting backup checkpoint " & lm.oldcpfn(nm));
      FS.DeleteFile(lm.oldcpfn(nm)); (* must exist of fatal error *)
      IF TestFile(lm.logfn(nm)) THEN (* may or may not exist depending on
                                        the crash *)
        Log.PutText("removing log");
        Log.Nl();
        FS.DeleteFile(lm.logfn(nm));
      END;
      log := NIL;
      (* (3) Finally if there is no temporary checkpoint there than the
         checkpoint and the log is valid. *)
    ELSE
      IF NOT TestFile(lm.logfn(nm)) THEN
        log := NIL
      ELSE
        log := FileRd.Open(lm.logfn(nm));
      END;
    END;
    checkp := FileRd.Open(lm.cpfn(nm));
  END Recover;
PROCEDURE Recoverable  (lm: Default; nm: Pathname.T): BOOLEAN =
  BEGIN
    TRY
      (* is there such a directory? *)
      IF FS.Status(nm).type # FS.DirectoryFileType THEN RETURN FALSE END;
      (* Do not care about to logfile but there must be a version of the
         checkpointfile: *)
      RETURN TestFile(lm.cpfn(nm)) OR TestFile(lm.oldcpfn(nm));
    EXCEPT
      OSError.E => RETURN FALSE  (* nothing found *)
    END;
  END Recoverable;
PROCEDURE EmptyLog  (lm: Default; nm: Pathname.T): BOOLEAN
  RAISES {OSError.E} =
  VAR log: TEXT;
  BEGIN
    IF NOT lm.recoverable(nm) THEN
      RAISE OSError.E(
              AtomList.List1(
                Atom.FromText(
                  "no checkpointfile for log in " & nm)));
    END;
    log := lm.logfn(nm);
    RETURN (NOT TestFile(log)) OR (FS.Status(log).size = 0L);
  END EmptyLog;
PROCEDURE Dispose  (lm: Default; nm: Pathname.T) RAISES {OSError.E} =
  BEGIN
    IF TestFile(nm) THEN
      Log.PutText("Erasing directory " & nm);
      Log.Nl();
      (**)
      CrashPoint(501);
      IF TestFile(lm.cpfn(nm)) THEN FS.DeleteFile(lm.cpfn(nm)) END;
      IF TestFile(lm.oldcpfn(nm)) THEN FS.DeleteFile(lm.oldcpfn(nm)) END;
      IF TestFile(lm.newcpfn(nm)) THEN FS.DeleteFile(lm.newcpfn(nm)) END;
      (**)
      CrashPoint(502);
      IF TestFile(lm.logfn(nm)) THEN FS.DeleteFile(lm.logfn(nm)) END;
      (**)
      CrashPoint(503);
      IF TestFile(lm.dummycp(nm)) THEN FS.DeleteFile(lm.dummycp(nm)) END;
      FS.DeleteDirectory(nm);
    END
  END Dispose;
 \subsection{Utilities}
 
PROCEDURETestFile (fname: Pathname.T): BOOLEAN RAISES {OSError.E} = (* Test for existence of a file named 'fname'. Returns TRUE iff it exists and is readable. Exceptions other than "file not found" are reraised. *) BEGIN TRY EVAL FS.Status(fname); RETURN TRUE; (* ok, passed the test *) EXCEPT OSError.E (err) => IF FSError.FileNotFound (err) THEN RETURN FALSE ELSE RAISE OSError.E(err) END END END TestFile; PROCEDURETestDir (dir: Pathname.T) RAISES {OSError.E} = (* This procedure assures that "dir" is a valid directory. If it does not exist it creates it. *) BEGIN TRY IF FS.Status(dir).type # FS.DirectoryFileType THEN RAISE OSError.E(AtomList.List1( Atom.FromText( "name of stable obj is not a directory: " & dir))); END EXCEPT OSError.E => Log.PutText("creating dir " & dir); Log.Nl(); FS.CreateDirectory(dir) END END TestDir; BEGIN default:= NEW(Default).init(); END LogManager.