(* Copyright (C) 1994, Digital Equipment Corporation           *)
(* All rights reserved.                                        *)
(* See the file COPYRIGHT for a full description.              *)
(*                                                             *)
(* Created by Carsten Weich and Greg Nelson                    *)
(*                                                             *)
(* Last modified on Thu Jan 19 12:56:06 PST 1995 by kalsow     *)
(*      modified on Thu Sep 22 18:25:39 PDT 1994 by weich      *)

(* A "LogManager" provides an object which manages readers and writers
   for the log and checkpoint files used by stable objects.

   The default log manager stores the stable state in the file system.
   Since the default is satisfactory for most applications, 
   most clients of stable objects don't need to use the LogManager 
   interface directly.  
   
   A situation in which you might want to use this interface is 
   if you want to test whether a checkpoint is available before initializing 
   a stable object.  In this case the "recoverable" method of the 
   log manager will be useful to you.  As another example, if you 
   don't want to use the file system for storing the stable state, 
   you will need to study the specifications in this interface and 
   implement your own log manager that stores the stable state in 
   your preferred form of stable storage.  *)

INTERFACE LogManager;

  IMPORT Pathname, Wr, Rd, OSError;

  TYPE T <: Public;

    Public = OBJECT METHODS
        beginCheckpoint(nm: Pathname.T): Wr.T (* new checkpoint *)
            RAISES {OSError.E};
        endCheckpoint(nm: Pathname.T):Wr.T (* new log *)
            RAISES {OSError.E};
        reOpenLog(nm: Pathname.T): Wr.T RAISES {OSError.E};
        recover(nm: Pathname.T; VAR log, checkp: Rd.T)
            RAISES {OSError.E};
        recoverable(nm: Pathname.T): BOOLEAN
            RAISES {OSError.E};
        emptyLog(nm: Pathname.T): BOOLEAN
            RAISES {OSError.E};
        dispose(nm: Pathname.T) 
            RAISES {OSError.E};
      END;

    DefaultPublic = T OBJECT METHODS init(): Default END;
    Default <: DefaultPublic;

    VAR default: Default;

END LogManager.

(* A "LogManager.T" manages a repository of named stable snapshots. 
   A snapshot consists of two sequences of bytes, a <I>checkpoint</I> 
   and a <I>redo log</I>.  The repository must be stable; that is, 
   it must survive program crashes.  Here are specifications for 
   the methods of a "LogManager.T" named "lm": 

   The  "beginCheckpoint" and "endCheckpoint" methods are
   used to write a new stable snapshot.  The call

| lm.beginCheckpoint(nm)

   returns a writer whose target is the checkpoint named "nm"
   in "lm"'s repository.  The call

|  lm.endCheckpoint(nm)

   should be made only after a previous call to "lm.beginCheckpoint(nm)". 
   The "endCheckpoint" method commits the bytes that have been written 
   to the writer that was returned by "beginCheckpoint", making them 
   become the new checkpoint. The "endCheckpoint" method also empties 
   the redo log and returns a writer that can be used to extend 
   the now-empty redo log. 

   Therefore, to make a new checkpoint, you should execute the following 
   steps: 

| wr1 := lm.beginCheckpoint(nm);
| `write a new checkpoint to ` wr1;
| wr2 := lm.endCheckpoint(nm);
| `new checkpoint is made; log updates to ` wr2

   If the application exits or crashes before the call to "endCheckpoint", 
   any bytes written to "wr1" will be discarded, and the previous 
   checkpoint will not be changed.  (Of course these steps are performed
   by the "Checkpoint" procedure in the generic "Stable" interface, so
   there is no reason for the typical client of stable objects to
   recode this procedure.  But if you are implementing your own
   log managers, it is important to know how the methods will
   be used by your clients.)

   An application may close the writer returned by "endCheckpoint" 
   (for example, to free up file descriptors or other resources associated 
   with inactive stable objects).  If this writer has been closed, 
   the call 

| lm.reopenLog(nm)

   will return a writer which will append to the current redo
   log.  

   The call

| lm.recoverable(nm)

   returns "TRUE" if and only if the repository managed by "lm" contains 
   a snapshot named "nm". 

   The call

| lm.emptyLog(nm)

   returns "TRUE" if and only if the repository managed by "lm" contains 
   a snapshot "nm" which has an empty log. The log will be empty if the
    program that created a stable object did a checkpoint right
    before terminating. A non empty log might indicate a crashed program.
    "emptyLog()" will raise the exception if there is no snapshot "nm".

   The call

| lm.recover(nm, cprd, logrd)

   sets "cprd" and "logrd" to readers whose sources are the
   checkpoint and redo log named "nm", respectively, assuming
   a snapshot exists under that name in the repository managed
   by "lm".

   The call

| lm.dispose(nm)

   discards any snapshot named "nm" from "lm"'s repository and
   reclaims any associated stable storage.

So much for the methods of a general "LogManager.T".  Almost all
clients will use a "LogManager.Default", which uses the
ordinary file system as its source of stable storage.
A "LogManager.Default" must be initialized by the client;
to obtain one, call

| VAR lm := NEW(LogManager.Default).init(); ...

The methods of a "LogManager.Default" interpret "nm" as a directory 
in the file system, in which they expects to find files containing a
checkpoint and log.  The "beginCheckpoint" method will create the
directory if necessary.  File renaming is used to make checkpoint
atomic with respect to crashes.

The variable "LogManager.default" is initialized by the
"LogManager" module to a valid "LogManager.Default".

*)

