/*
 * 
 * (c) Copyright 1991 OPEN SOFTWARE FOUNDATION, INC.
 * (c) Copyright 1991 HEWLETT-PACKARD COMPANY
 * (c) Copyright 1991 DIGITAL EQUIPMENT CORPORATION
 * To anyone who acknowledges that this file is provided "AS IS"
 * without any express or implied warranty:
 *                 permission to use, copy, modify, and distribute this
 * file for any purpose is hereby granted without fee, provided that
 * the above copyright notices and this notice appears in all source
 * code copies, and that none of the names of Open Software
 * Foundation, Inc., Hewlett-Packard Company, or Digital Equipment
 * Corporation be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  Neither Open Software Foundation, Inc., Hewlett-
 * Packard Company, nor Digital Equipment Corporation makes any
 * representations about the suitability of this software for any
 * purpose.
 * 
 */
/*
 */
/*
**
**  NAME:
**
**      dsm.idl
**
**  FACILITY:
**
**      Global Location Broker (GLB)
**
**  ABSTRACT:
**
**
**
**
*/

[local] interface dsm {

/*  DSM (Data Store Manager) public interface definition

    The Data Store Manager is a heap storage allocation package wherein
    allocated records are strongly associated with storage in a backing
    file, such that they can be stably stored upon modification.  The basic
    paradigm is that the client ALLOCATEs a block of some requested size,
    modifies it in memory, and WRITEs it; successful completion of the
    WRITE implies that the record has been stably stored in the file.

    DSM uses OS page alignment to define an atomic operation (a write of
    or within a page is assumed to either succeed or fail, without any
    intermediate state).  Records are laid out in the file such that the
    DSM header, as well as some reasonable chunk of the start of
    application data (e.g. the first 64 bytes total of each record) are
    contiguous in a page, and so can be written atomically.  A write that
    spans a page boundary occurs in two phases (assuming the record being
    written was previously free and is so marked on disk): the data portion
    is written and synched first, then the DSM header (specifically the
    'inuse/free' mark) to commit the write.

    Updates are not atomically supported.  Changing the contents of a
    record requires conceptually adding a new version and deleting the
    old one.  DSM provides an operation to 'detach' a record (mark it free
    in the file, effectively deleting it if a crash occurs at this point),
    after which it can be written normally.  This is adequate for
    applications like DRM which can recover from crashes by replaying the
    last operation on its propagation queue.  Another approach would be
    to allocate a new record and make a copy, setting a 'new' flag in its
    application header, then freeing the old copy, and finally clearing
    the 'new' flag in the new copy.  Upon crash recovery the application
    might see two versions of the same datum, one flagged 'new', and can
    discard the other one (or it may see only the 'new' version).

    The DSM does not currently itself provide mutual exclusion, although
    it must be used in such a context (the caller is currently assumed
    to do the mutex).
    
    Function summary:

    dsm_create     - create a new, empty data store in a named file
    dsm_open       - open an existing data store in a named file
    dsm_close      - close an open data store

    dsm_allocate   - allocate a block of some specified size
    dsm_write      - write a block to the backing store, stable upon completion
    dsm_free       - free a block

    dsm_read       - successively step through the store, returning each 
                      active record
    dsm_marker_reset - reset the marker used by dsm_read to the beginning

    dsm_get_info
    dsm_set_info   - allow access to application-defined per-file info
    
    dsm_get_stats  - get statistics about file size, free space, etc.

    dsm_reclaim    - reclaim free space in data store

    dsm_detach     - mark a block free on the backing store (allow simple 
                      update paradigm)
    dsm_write_hdr  - write only the beginning of a record (a small header can 
                      be updated atomically)

(see below for details on calling sequences and use)

*/

const long dsm_mod                    = 0x1C0B0000;

const long dsm_err_create_failed      = 0x1C0B0001;
const long dsm_err_file_io_error      = 0x1C0B0002;
const long dsm_err_open_failed        = 0x1C0B0003;
const long dsm_err_version            = 0x1C0B0004;
const long dsm_err_no_memory          = 0x1C0B0005;
const long dsm_err_duplicate_write    = 0x1C0B0006;
const long dsm_err_header_too_long    = 0x1C0B0007;
const long dsm_err_no_more_entries    = 0x1C0B0008;
const long dsm_err_invalid_handle     = 0x1C0B0009;
const long dsm_err_invalid_pointer    = 0x1C0B000A;
const long dsm_err_info_too_long      = 0x1C0B000B;
const long dsm_err_file_busy          = 0x1C0B000C;
const long dsm_err_invalid_marker     = 0x1C0B000D;
 
typedef void          * dsm_handle_t;
typedef unsigned long   dsm_marker_t;

typedef struct dsm_stats_t {        /* returned from dsm_get_stats */
    unsigned long   size;           /* size in bytes */
    unsigned long   free_space;     /* how much of that is free */
    unsigned long   largest_free;   /* size of largest free block */
} dsm_stats_t;

const long dsm_version       = 1;
const long dsm_hdr_size      = 48;           /* amount you can write_hdr */
const long dsm_magic_marker  = 0xF000000F;   /* value of reset marker */

/** dsm_create(fname, &dsh, &st)

    Create a new data store, to live in the (new) file named fname.  Returns
    a data store handle to be used in subsequent dsm calls to refer to that
    data store.
*/

void dsm_create(
    [in, string]  char    * fname,  /* filename */
    [out] dsm_handle_t    * dsh,    /* data store handle */
    [out] error_status_t  * st      /* status */
);

/** dsm_open(fname, &dsh, &st)

    Open the preexisting data store in the file named fname.  Returns
    a data store handle to be used in subsequent dsm calls to refer to that
    data store.
*/

void dsm_open(
    [in, string]  char    * fname,  /* filename */
    [out] dsm_handle_t    * dsh,    /* data store handle */
    [out] error_status_t  * st      /* status */
);    

/** dsm_close(&dsh, &st)

    Close the open data store identified by dsh.
*/

void dsm_close(
    [in, out] dsm_handle_t  * dsh,  /* data store handle */
    [out] error_status_t    * st    /* status */
);

/** dsm_allocate(dsh, len, &p, &st)

    Allocate a block of storage len bytes long, in memory and in the
    backing store identified by dsh; effect is similar to p = malloc(len).
    The caller will fill in the desired contents of the block and then
    use dsm_write to commit those contents stably -- until this is done
    the block is strictly volatile.
*/

void dsm_allocate(
    [in]  dsm_handle_t        dsh,  /* data store handle */
    [in]  unsigned long       len,  /* size of block to allocate */
    [out] void *            * p,    /* pointer to block */
    [out] error_status_t    * st    /* status */
);

/** dsm_write(dsh, p, &st)

    p is a pointer returned by dsm_allocate.  dsm_write writes the block's
    contents to the backing store (in a stable manner); upon return with
    a good status, the caller is assured that the block is backed up.
*/
void dsm_write(
    [in] dsm_handle_t         dsh,  /* data store handle */
    [in] void *               p,    /* pointer to block */
    [out] error_status_t    * st    /* status */
);

/** dsm_write_hdr(dsh, p, len, &st)

    Stably writing arbitrary-length blocks is inherently more expensive than
    certain shorter writes.  DSM provides the feature that the first 48 bytes
    of a record can be written more efficiently than the general case, in
    support of the common case of maintaining more volatile information about
    a record in a header.  dsm_write_hdr is equivalent to dsm_write, except
    that only the first len bytes are written (len being <= 48).  (It is 
    expected that len will generally be expressed as a sizeof expression).
*/

void dsm_write_hdr(
    [in] dsm_handle_t       dsh,    /* data store handle */
    [in] void *             p,      /* pointer to block */
    [in] unsigned long      len,    /* length of header */
    [out] error_status_t  * st      /* status */
);

/** dsm_free(dsh, p, &st)

    p is a pointer to a block in the data store identified by dsh; dsm_free
    returns it to the free pool (future references through p are undefined).
*/
void dsm_free(
    [in] dsm_handle_t       dsh,    /* data store handle */
    [in] void *             p,      /* pointer to block */
    [out] error_status_t  * st      /* status */
);

/** dsm_detach(dsh, p, &st)

    DSM does not support the concept of updating records, that is,
    dsm_write may not be called with a pointer to a block that has been
    written by dsm_write in the past.  Generally update operations must
    be considered as a delete and an add, with the calling application
    providing semantics for ensuring stability if only one succeeds.  For
    instance, DAM uses a sequence of: allocating a new copy flagged "New"
    in its header, writing that, then freeing the original, and finally
    using dsm_write_hdr to clear the "New" mark from the replacement.
    Its database initialization code deals appropriately with finding a
    record marked "New".  However, some applications have inherent
    mechanisms for recovering from partial success of delete/add pairs
    (such as an activity log, or propagation list).  For these applications
    the dsm_detach operation is provided.  It is similar to dsm_free,
    except that the in-memory copy of the record is not freed, and becomes
    a candidate for dsm_write.  Thus a sequence of dsm_detach/dsm_write
    provides an update operation, with the proviso that a failure between
    the detach and write could result in the record being lost if no
    preventative action is taken.  dsm_detach of an already free/detached
    record is a no-op.
*/

void dsm_detach(
    [in] dsm_handle_t       dsh,    /* data store handle */
    [in] void *             p,      /* pointer to block */
    [out] error_status_t  * st      /* status */
);

/** dsm_get_info(dsh, &info, len, &st)
    dsm_set_info(dsh, &info, len, &st)

    DSM provides a mechanism for an application to maintain a small amount
    (up to 256 bytes) of its own information pertaining to an entire data
    store, such as version information.  dsm_set_info copies len bytes of
    application information from info into the data store header.  
    dsm_get_info retrieves len bytes from the data store into info.  len
    is presumed to be constant, typically a sizeof expression, and is not
    stored in the data store.
*/

void dsm_get_info(
    [in] dsm_handle_t       dsh,    /* data store handle */
    [in] void *             info,   /* information buffer */
    [in] unsigned long      len,    /* length of header */
    [out] error_status_t  * st      /* status */
);

void dsm_set_info(
    [in] dsm_handle_t       dsh,    /* data store handle */
    [in] void *             info,   /* information buffer */
    [in] unsigned long      len,    /* length of header */
    [out] error_status_t  * st      /* status */
);

/** dsm_read(dsh, &marker, &p, &st)
    dsm_marker_reset(&marker)

    dsm_read is used to successively examine every allocated record in
    the data store identified by dsh.  marker is used by dsm to hold
    information required to find successive records; it should be
    initialized by calling dsm_marker_reset to get the first record.
    dsm_read will return with status_ok for every valid stably-stored
    record, and with dsm_err_no_more_entries when there are no more valid
    entries.  Note that if the data store is changed between dsm_read
    calls (e.g. by another task, or if a marker is stored over time) it
    is not guaranteed that all records will be seen.  However, the 
    behavior of dsm_read is well-defined in such circumstances and should
    see as many records as possible consistent with a linear pass through
    the data store, without abnormal behavior.

*/

void dsm_read(
    [in] dsm_handle_t           dsh,    /* data store handle */
    [in, out] dsm_marker_t    * marker, /* marker  */
    [out] void *              * p,      /* record */
    [out] error_status_t      * st      /* status */
);

void dsm_marker_reset(
    [in, out] dsm_marker_t    * marker  /* marker  */
);

/** dsm_locate(dsh, marker, &p, &st)

    Returns a pointer to a dsm block, given a marker identifying that block, as
    returned by dsm_read (the marker returned by a dsm_read call corresponds 
    to the record returned by that call), or by dsm_inq_marker.  A marker,
    unlike a pointer, is valid across datastore closes.  An application could
    store a marker in a file before closing a datastore, and later relocate
    a pointer to the storage (after reopening the datastore) with dsm_locate.
    Freeing a block clearly invalidates any markers to it.  A marker clearly
    only applies to the datastore it originally pertained to.  It is not 
    meaningful to locate a reset marker.
*/

void dsm_locate(
    [in] dsm_handle_t           dsh,    /* data store handle */
    [in] dsm_marker_t           marker, /* marker  */
    [out] void *              * p,      /* record */
    [out] error_status_t      * st      /* status */
);

/** dsm_inq_marker(dsh, p, &marker, &st)

    Returns a marker to the given dsm block.
*/
void dsm_inq_marker(
    [in] dsm_handle_t           dsh,    /* data store handle */
    [in] void *                 p,      /* record */
    [out] dsm_marker_t        * marker, /* marker  */
    [out] error_status_t      * st      /* status */
);
/** dsm_inq_size(dsh, p, &size, &st)

    Returns (in the "size" parameter) the size in bytes allocated to the 
    given dsm block.  This may be larger than the amount requested when
    the block was allocated.
*/
void dsm_inq_size(
    [in] dsm_handle_t           dsh,    /* data store handle */
    [in] void *                 p,      /* record */
    [out] unsigned32          * size,   /* its size */
    [out] error_status_t      * st      /* status */
);

/** dsm_get_stats(dsh, &stats, &st)

    Fills in the given DSM statistics record (see definition above) with
    information about the open data store (such as total size, free space,
    largest free block).  Immediately after opening a data store the largest
    free block represents the largest record that could be allocated without
    growing the file.
*/

void dsm_get_stats(
    [in] dsm_handle_t           dsh,    /* data store handle */
    [out] dsm_stats_t         * stats,  /* statistics */
    [out] error_status_t      * st      /* status */
);

/** dsm_reclaim(dsh, name, tempname, oldname, pct, &st): boolean

    DSM datastores grow as required, without limit, but will not shrink;
    that is, if you allocate (and write) a large amount of data, and 
    subsequently free it, the space occupied on the disk and in memory
    becomes eligible for reuse by new DSM records, but is not returned
    to the operating system.  dsm_reclaim is used to reclaim this "high
    water mark" free storage.  It makes a new copy of a datastore
    without the excess storage (DSM stable memory requirements make it
    impossible to reclaim space "in place"), if more than a specified
    percentage of the file is free space.  

    dsm_reclaim takes a handle to a DSM datastore.  If the handle contains
    NULL, dsm_reclaim opens the data store, does the reclaim (if appropriate), 
    and then closes the data store.  If the handle points to an open data 
    store, DSM returns a handle to the original data store (if no reclaim 
    was done) or to the reclaimed data store.  In some error cases, DSM 
    returns a NULL handle even though the input handle pointed to an open 
    data store.

    dsm_reclaim also takes 3 filenames: the datastore name, the 'old' name, 
    and the 'new' name; typical examples might be "mydb", "mydb.bak", and 
    "mydb.new".  The following sequence is applied: 

            if handle is NULL, open mydb
            check mydb stats
            if freespace doesn't exceed specified percent
                if handle is NULL, close mydb 
                return 

            create "mydb.new"
            for every record in mydb, make a copy in mydb.new
            close both datastores
            rename "mydb" to "mydb.bak"
            rename "mydb.new" to "mydb"
            if handle is non-NULL, open mydb

    dsm_reclaim returns true if the file was reclaimed, false otherwise (in case 
    of error or under free_pct).  If the input handle pointed to an open data 
    store then the returned handle points to an open data store (except in certain
    error cases when it contains NULL).

    NB: Since data is moved around in the datastore during dsm_reclaim, 
    markers and pointers into the DSM datastore are invalid after 
    dsm_reclaim and their use will yield indeterminate results.  It
    is best to invoke dsm_reclaim on a closed data store or immediately 
    after opening the data store.

    Observe that this is really at a higher level than DSM per se; dsm_reclaim
    is provided as a common utility as most DSM applications will want this
    sort of functionality.  Individual applications may choose to provide
    variations on this basic algorithm.  This particular sequence is safe in
    the face of crashes.  There should be two files in any case: if "mydb"
    exists, it should be used; if it doesn't then "mydb.bak" contains the old
    version and "mydb.new" the new (reclaimed) one.  (if "mydb.new" and "mydb" 
    both exist, "mydb.new" probably contains an incompletely reclaimed copy, and 
    should be discarded).  

*/

boolean dsm_reclaim(
    [in, out] dsm_handle_t  * dsh,      /* data store handle */
    [in, string]  char      * fname,    /* datastore filename */
    [in, string]  char      * tempname, /* temp file to build new datastore in */
    [in, string]  char      * oldname,  /* what to rename the pre-reclamation store */
    [in]  unsigned32          free_pct, /* integer %age of free space to tolerate */
    [out] error_status_t    * st        /* status */
);

}
