/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
** DAV extension module for Apache 2.0.*
**  - Database support using DBM-style databases,
**    part of the filesystem repository implementation
*/

/*
** This implementation uses a SDBM database per file and directory to
** record the properties. These databases are kept in a subdirectory (of
** the directory in question or the directory that holds the file in
** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
** database is equivalent to the target filename, and is
** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
*/

#include "apr_strings.h"
#include "apr_file_io.h"

#include "apr_dbm.h"

#include "mod_dav.h"
#include "repos.h"

struct dav_db {
    apr_pool_t *pool;
    apr_dbm_t *file;
};


void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
			    const char **state1, const char **state2)
{
    if (fname == NULL)
	fname = DAV_FS_STATE_FILE_FOR_DIR;

    apr_dbm_get_usednames(p, fname, state1, state2);
}

static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
                                    apr_status_t status)
{
    int save_errno = errno;
    int errcode;
    const char *errstr;
    dav_error *err;
    char errbuf[200];

    if (status == APR_SUCCESS)
        return NULL;

    p = db ? db->pool : p;

    /* There might not be a <db> if we had problems creating it. */
    if (db == NULL) {
        errcode = 1;
        errstr = "Could not open property database.";
    }
    else {
        (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
        errstr = apr_pstrdup(p, errbuf);
    }

    err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr);
    err->save_errno = save_errno;
    return err;
}

/* ensure that our state subdirectory is present */
/* ### does this belong here or in dav_fs_repos.c ?? */
void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
{
    const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);

    /* ### do we need to deal with the umask? */

    /* just try to make it, ignoring any resulting errors */
    (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
}

/* dav_dbm_open_direct:  Opens a *dbm database specified by path.
 *    ro = boolean read-only flag.
 */
dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
				dav_db **pdb)
{
    apr_status_t status;
    apr_dbm_t *file;

    *pdb = NULL;

    if ((status = apr_dbm_open(&file, pathname,
                               ro ? APR_DBM_READONLY : APR_DBM_RWCREATE, 
                               APR_OS_DEFAULT, p))
                != APR_SUCCESS
        && !ro) {
        /* ### do something with 'status' */

	/* we can't continue if we couldn't open the file 
	   and we need to write */
	return dav_fs_dbm_error(NULL, p, status);
    }

    /* may be NULL if we tried to open a non-existent db as read-only */
    if (file != NULL) {
	/* we have an open database... return it */
	*pdb = apr_pcalloc(p, sizeof(**pdb));
	(*pdb)->pool = p;
	(*pdb)->file = file;
    }

    return NULL;
}

static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
                                int ro, dav_db **pdb)
{
    const char *dirpath;
    const char *fname;
    const char *pathname;

    /* Get directory and filename for resource */
    dav_fs_dir_file_name(resource, &dirpath, &fname);

    /* If not opening read-only, ensure the state dir exists */
    if (!ro) {
	/* ### what are the perf implications of always checking this? */
        dav_fs_ensure_state_dir(p, dirpath);
    }

    pathname = apr_pstrcat(p,
			  dirpath,
			  "/" DAV_FS_STATE_DIR "/",
			  fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
			  NULL);

    /* ### readers cannot open while a writer has this open; we should
       ### perform a few retries with random pauses. */

    /* ### do we need to deal with the umask? */

    return dav_dbm_open_direct(p, pathname, ro, pdb);
}

static void dav_dbm_close(dav_db *db)
{
    apr_dbm_close(db->file);
}

static dav_error * dav_dbm_fetch(dav_db *db, dav_datum key, dav_datum *pvalue)
{
    apr_status_t status = apr_dbm_fetch(db->file, key, pvalue);

    return dav_fs_dbm_error(db, NULL, status);
}

static dav_error * dav_dbm_store(dav_db *db, dav_datum key, dav_datum value)
{
    apr_status_t status = apr_dbm_store(db->file, key, value);

    return dav_fs_dbm_error(db, NULL, status);
}

static dav_error * dav_dbm_delete(dav_db *db, dav_datum key)
{
    apr_status_t status = apr_dbm_delete(db->file, key);

    return dav_fs_dbm_error(db, NULL, status);
}

static int dav_dbm_exists(dav_db *db, dav_datum key)
{
    return apr_dbm_exists(db->file, key);
}

static dav_error * dav_dbm_firstkey(dav_db *db, dav_datum *pkey)
{
    apr_status_t status = apr_dbm_firstkey(db->file, pkey);

    return dav_fs_dbm_error(db, NULL, status);
}

static dav_error * dav_dbm_nextkey(dav_db *db, dav_datum *pkey)
{
    apr_status_t status = apr_dbm_nextkey(db->file, pkey);

    return dav_fs_dbm_error(db, NULL, status);
}

static void dav_dbm_freedatum(dav_db *db, dav_datum data)
{
    apr_dbm_freedatum(db->file, data);
}

const dav_hooks_db dav_hooks_db_dbm =
{
    dav_dbm_open,
    dav_dbm_close,
    dav_dbm_fetch,
    dav_dbm_store,
    dav_dbm_delete,
    dav_dbm_exists,
    dav_dbm_firstkey,
    dav_dbm_nextkey,
    dav_dbm_freedatum,
};
