/** BEGIN COPYRIGHT BLOCK
 * This Program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; version 2 of the License.
 * 
 * This Program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA.
 * 
 * In addition, as a special exception, Red Hat, Inc. gives You the additional
 * right to link the code of this Program with code not covered under the GNU
 * General Public License ("Non-GPL Code") and to distribute linked combinations
 * including the two, subject to the limitations in this paragraph. Non-GPL Code
 * permitted under this exception must only link to the code of this Program
 * through those well defined interfaces identified in the file named EXCEPTION
 * found in the source code files (the "Approved Interfaces"). The files of
 * Non-GPL Code may instantiate templates or use macros or inline functions from
 * the Approved Interfaces without causing the resulting work to be covered by
 * the GNU General Public License. Only Red Hat, Inc. may make changes or
 * additions to the list of Approved Interfaces. You must obey the GNU General
 * Public License in all respects for all of the Program code and other code used
 * in conjunction with the Program except the Non-GPL Code covered by this
 * exception. If you modify this file, you may extend this exception to your
 * version of the file, but you are not obligated to do so. If you do not wish to
 * provide this exception without modification, you must delete this exception
 * statement from your version and license this file solely under the GPL without
 * exception. 
 * 
 * Copyright (C) 2007 Red Hat, Inc.
 * All rights reserved.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/* dbverify.c - verify database files */

#include "back-ldbm.h"
#include "dblayer.h"

static int 
dbverify_ext( ldbm_instance *inst, int verbose )
{
    char dbdir[MAXPATHLEN];
    char *filep           = NULL;
    PRDir *dirhandle      = NULL;
    PRDirEntry *direntry  = NULL;
    DB *dbp               = NULL;
    int tmplen            = 0;
    int filelen           = 0;
    int rval              = 1;
    int rval_main         = 0;
    struct ldbminfo *li   = inst->inst_li;
    dblayer_private *priv = (dblayer_private*)li->li_dblayer_private;
    struct dblayer_private_env *pEnv = priv->dblayer_env;

    dbdir[sizeof(dbdir)-1] = '\0';
    PR_snprintf(dbdir, sizeof(dbdir), "%s/%s", inst->inst_parent_dir_name,
                                               inst->inst_dir_name);
    if ('\0' != dbdir[sizeof(dbdir)-1]) /* overflown */
    {
        slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                        "db path too long: %s/%s\n",
                         inst->inst_parent_dir_name, inst->inst_dir_name);
        return 1;
    }
    tmplen = strlen(dbdir);
    filep = dbdir + tmplen;
    filelen = sizeof(dbdir) - tmplen;

    /* run dbverify on each each db file */
    dirhandle = PR_OpenDir(dbdir);
    if (! dirhandle)
    {
        slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                        "PR_OpenDir (%s) failed (%d): %s\n", 
                        dbdir, PR_GetError(),slapd_pr_strerror(PR_GetError()));
        return 1;
    }
    while (NULL !=
          (direntry = PR_ReadDir(dirhandle, PR_SKIP_DOT | PR_SKIP_DOT_DOT)))
    {
        /* struct attrinfo *ai = NULL; */
        dbp = NULL;

        if (!direntry->name)
        {
            break;
        }
        if (!strstr(direntry->name, LDBM_FILENAME_SUFFIX)) /* non db file */
        {
            continue;
        }
        if (sizeof(direntry->name) + 2 > filelen)
        {
            slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                                        "db path too long: %s/%s\n",
                                         dbdir, direntry->name);
            continue;
        }
        PR_snprintf(filep, filelen, "/%s", direntry->name);
        rval = db_create(&dbp, pEnv->dblayer_DB_ENV, 0);
        if (0 != rval)
        {
            slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                        "Unable to create id2entry db file %d\n", rval);
            return rval;
        }

#define VLVPREFIX "vlv#"
        if (0 != strncmp(direntry->name, ID2ENTRY, strlen(ID2ENTRY)))
        {
            struct attrinfo *ai = NULL;
            char *p = NULL;
            p = strstr(filep, LDBM_FILENAME_SUFFIX); /* since already checked,
                                                        it must have it */
            *p = '\0';
            ainfo_get( inst->inst_be, filep+1, &ai );
            *p = '.';
            if (ai->ai_key_cmp_fn) {
                dbp->app_private = (void *)ai->ai_key_cmp_fn;
                dbp->set_bt_compare(dbp, dblayer_bt_compare);
            }
            if (idl_get_idl_new())
            {
                rval = dbp->set_pagesize(dbp,
                        (priv->dblayer_index_page_size == 0) ?
                        DBLAYER_INDEX_PAGESIZE : priv->dblayer_index_page_size);
            }
            else
            {
                rval = dbp->set_pagesize(dbp,
                        (priv->dblayer_page_size == 0) ?
                        DBLAYER_PAGESIZE : priv->dblayer_page_size);
            }
            if (0 != rval)
            {
                slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                         "Unable to set pagesize flags to db (%d)\n", rval);
                return rval;
            }
            if (0 == strncmp(direntry->name, VLVPREFIX, strlen(VLVPREFIX)))
            {
                rval = dbp->set_flags(dbp, DB_RECNUM);
                if (0 != rval)
                {
                    slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                         "Unable to set RECNUM flag to vlv index (%d)\n", rval);
                    return rval;
                }
            }
            else if (idl_get_idl_new())
            {
                rval = dbp->set_flags(dbp, DB_DUP | DB_DUPSORT);
                if (0 != rval)
                {
                    slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                           "Unable to set DUP flags to db (%d)\n", rval);
                    return rval;
                }
    
				if (ai->ai_dup_cmp_fn) {
					/* If set, use the special dup compare callback */
					rval = dbp->set_dup_compare(dbp, ai->ai_dup_cmp_fn);
				} else {
					rval = dbp->set_dup_compare(dbp, idl_new_compare_dups);
				}

                if (0 != rval)
                {
                    slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                           "Unable to set dup_compare to db (%d)\n", rval);
                    return rval;
                }
            }
        }
#undef VLVPREFIX
        rval = dbp->verify(dbp, dbdir, NULL, NULL, 0);
        if (0 == rval)
        {
            if (verbose)
            {
                slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                                                 "%s: ok\n", dbdir);
            }
        }
        else
        {
            slapi_log_error(SLAPI_LOG_FATAL, "DB verify",
                            "verify failed(%d): %s\n", rval, dbdir);
        }
        rval_main |= rval;
        *filep = '\0';
    }
    PR_CloseDir(dirhandle);

    return rval_main;
}

int 
ldbm_back_dbverify( Slapi_PBlock *pb )
{
    struct ldbminfo *li   = NULL;
    Object *inst_obj      = NULL;
    ldbm_instance *inst   = NULL;
    int verbose           = 0;
    int rval              = 1;
    int rval_main         = 0;
    char **instance_names = NULL;

    slapi_log_error(SLAPI_LOG_TRACE, "verify DB", "Verifying db files...\n");
    slapi_pblock_get(pb, SLAPI_BACKEND_INSTANCE_NAME, &instance_names);
    slapi_pblock_get(pb, SLAPI_SEQ_TYPE, &verbose);
    slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &li);
    ldbm_config_load_dse_info(li);
    ldbm_config_internal_set(li, CONFIG_DB_TRANSACTION_LOGGING, "off");
    /* no write needed; choose EXPORT MODE */
    if (0 != dblayer_start(li, DBLAYER_EXPORT_MODE))
    {
        slapi_log_error(SLAPI_LOG_FATAL, "verify DB",
                                         "dbverify: Failed to init database\n");
        return rval;
    }

    /* server is up */
    slapi_log_error(SLAPI_LOG_TRACE, "verify DB", "server is up\n");
    if (instance_names) /* instance is specified */
    {
        char **inp = NULL;
        for (inp = instance_names; inp && *inp; inp++)
        {
            inst = ldbm_instance_find_by_name(li, *inp);
            if (inst)
            {
                rval_main |= dbverify_ext(inst, verbose);
            }
            else
            {
                rval_main |= 1;    /* no such instance */
            }
        }
    }
    else /* all instances */
    {
        for (inst_obj = objset_first_obj(li->li_instance_set); inst_obj;
              inst_obj = objset_next_obj(li->li_instance_set, inst_obj))
        {
            inst = (ldbm_instance *)object_get_data(inst_obj);
            /* check if an import/restore is already ongoing... */
            if (instance_set_busy(inst) != 0)
            {
                /* standalone, only.  never happens */
                slapi_log_error(SLAPI_LOG_FATAL, "upgrade DB",
                            "ldbm: '%s' is already in the middle of "
                            "another task and cannot be disturbed.\n",
                            inst->inst_name);
                continue; /* skip this instance and go to the next*/
            }
            rval_main |= dbverify_ext(inst, verbose);
        }
    }

    /* close the database down again */
    rval = dblayer_post_close(li, DBLAYER_EXPORT_MODE);
    if (0 != rval)
    {
        slapi_log_error(SLAPI_LOG_FATAL,
                        "verify DB", "Failed to close database\n");
    }

    return rval_main;
}
