2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License, Version 1.0 only
2N/A * (the "License"). You may not use this file except in compliance
2N/A * with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * db.cc
2N/A *
2N/A * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A#include <stdio.h>
2N/A#include <string.h>
2N/A#ifdef TDRPC
2N/A#include <sysent.h>
2N/A#else
2N/A#include <unistd.h>
2N/A#endif
2N/A
2N/A#include "nisdb_mt.h"
2N/A#include "db_headers.h"
2N/A#include "db.h"
2N/A
2N/Aextern db_result *empty_result(db_status);
2N/Aextern int add_to_standby_list(db*);
2N/Aextern int remove_from_standby_list(db*);
2N/A
2N/A/* for db_next_desc */
2N/A
2N/A#define LINEAR 1
2N/A#define CHAINED 2
2N/A
2N/Astruct db_next_info {
2N/A int next_type; /* linear or chained */
2N/A void* next_value; /* linear: entryp; */
2N/A /* chained: db_next_index_desc* */
2N/A};
2N/A
2N/A
2N/A/* Constructor: Create a database using the given name, 'dbname.'
2N/A The database is stored in a file named 'dbname'.
2N/A The log file is stored in a file named 'dbname'.log.
2N/A A temporary file 'dbname'.tmp is also used. */
2N/Adb::db(char* dbname)
2N/A{
2N/A int len = strlen(dbname);
2N/A dbfilename = new char[len+1];
2N/A if (dbfilename == NULL)
2N/A FATAL("db::db: cannot allocate space", DB_MEMORY_LIMIT);
2N/A logfilename = new char[len+5];
2N/A if (logfilename == NULL) {
2N/A delete dbfilename;
2N/A FATAL("db::db: cannot allocate space", DB_MEMORY_LIMIT);
2N/A }
2N/A tmpfilename = new char[len+5];
2N/A if (tmpfilename == NULL) {
2N/A delete dbfilename;
2N/A delete logfilename;
2N/A FATAL("db::db: cannot allocate space", DB_MEMORY_LIMIT);
2N/A }
2N/A sprintf(dbfilename, "%s", dbname);
2N/A sprintf(logfilename, "%s.log", dbname);
2N/A sprintf(tmpfilename, "%s.tmp", dbname);
2N/A logfile = NULL;
2N/A logfile_opened = FALSE;
2N/A changed = FALSE;
2N/A INITRW(db);
2N/A READLOCKOK(db);
2N/A
2N/A internal_db.setDbPtr(this);
2N/A (void) internal_db.configure(dbname);
2N/A}
2N/A
2N/A/* destructor: note that associated files should be removed separated */
2N/Adb::~db()
2N/A{
2N/A (void)acqexcl();
2N/A internal_db.reset(); /* clear any associated data structures */
2N/A delete dbfilename;
2N/A delete logfilename;
2N/A delete tmpfilename;
2N/A close_log();
2N/A delete logfile;
2N/A (void)destroylock();
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Aassign_next_desc(db_next_desc* desc, entryp value)
2N/A{
2N/A db_next_info * store = new db_next_info;
2N/A if (store == NULL) {
2N/A desc->db_next_desc_val = NULL;
2N/A desc->db_next_desc_len = 0;
2N/A FATAL("db::assign_next_desc: cannot allocate space",
2N/A DB_MEMORY_LIMIT);
2N/A }
2N/A
2N/A store->next_type = LINEAR;
2N/A store->next_value = (void*)value;
2N/A desc->db_next_desc_val = (char*) store;
2N/A desc->db_next_desc_len = sizeof (db_next_info);
2N/A}
2N/A
2N/Astatic void
2N/Aassign_next_desc(db_next_desc* desc, db_next_index_desc * value)
2N/A{
2N/A db_next_info * store = new db_next_info;
2N/A if (store == NULL) {
2N/A desc->db_next_desc_val = NULL;
2N/A desc->db_next_desc_len = 0;
2N/A FATAL("db::assign_next_desc: cannot allocate space (2)",
2N/A DB_MEMORY_LIMIT);
2N/A }
2N/A store->next_type = CHAINED;
2N/A store->next_value = (void*)value;
2N/A desc->db_next_desc_val = (char*) store;
2N/A desc->db_next_desc_len = sizeof (db_next_info);
2N/A}
2N/A
2N/Astatic entryp
2N/Aextract_next_desc(db_next_desc* desc, int *next_type,
2N/A db_next_index_desc** place2)
2N/A{
2N/A entryp place;
2N/A
2N/A if (desc == NULL || desc->db_next_desc_len != sizeof (db_next_info)) {
2N/A *next_type = 0;
2N/A return (0);
2N/A }
2N/A *next_type = ((db_next_info*) desc->db_next_desc_val)->next_type;
2N/A switch (*next_type) {
2N/A case LINEAR:
2N/A place = (entryp)
2N/A ((db_next_info*) desc->db_next_desc_val)->next_value;
2N/A return (place);
2N/A
2N/A case CHAINED:
2N/A *place2 = (db_next_index_desc*)
2N/A ((db_next_info*) desc->db_next_desc_val) ->next_value;
2N/A return (0);
2N/A default:
2N/A *next_type = 0; // invalid type
2N/A return (0);
2N/A }
2N/A}
2N/A
2N/A/* Execute the specified action using the rest of the arguments as input.
2N/A Return a structure db_result containing the result. */
2N/Adb_result *
2N/Adb::exec_action(db_action action, db_query *query,
2N/A entry_object *content, db_next_desc* previous)
2N/A{
2N/A entryp where, prev;
2N/A db_result *res = new db_result;
2N/A long num_answers;
2N/A entry_object_p * ans;
2N/A entry_object * single;
2N/A db_next_index_desc *index_desc;
2N/A int next_type;
2N/A db_next_index_desc *prev_desc;
2N/A
2N/A if (res == NULL)
2N/A FATAL3("db::exec_action: cannot allocate space for result",
2N/A DB_MEMORY_LIMIT, NULL);
2N/A
2N/A res->objects.objects_len = 0; /* default */
2N/A res->objects.objects_val = NULL; /* default */
2N/A
2N/A switch (action) {
2N/A case DB_LOOKUP:
2N/A res->status = internal_db.lookup(query, &num_answers, &ans);
2N/A res->objects.objects_len = (int) num_answers;
2N/A res->objects.objects_val = ans;
2N/A break;
2N/A
2N/A case DB_ADD:
2N/A res->status = internal_db.add(query, content);
2N/A break;
2N/A
2N/A case DB_REMOVE:
2N/A res->status = internal_db.remove(query);
2N/A break;
2N/A
2N/A case DB_FIRST:
2N/A if (query == NULL) {
2N/A res->status = internal_db.first(&where, &single);
2N/A if (res->status == DB_SUCCESS)
2N/A assign_next_desc(&(res->nextinfo), where);
2N/A } else {
2N/A res->status = internal_db.first(query,
2N/A &index_desc,
2N/A &single);
2N/A if (res->status == DB_SUCCESS)
2N/A assign_next_desc(&(res->nextinfo), index_desc);
2N/A }
2N/A if (res->status == DB_SUCCESS) {
2N/A res->objects.objects_val = new entry_object_p;
2N/A if (res->objects.objects_val == NULL) {
2N/A res->objects.objects_len = 0;
2N/A delete res;
2N/A FATAL3(
2N/A "db::exec_action: cannot allocate space for DB_FIRST result",
2N/A DB_MEMORY_LIMIT, NULL);
2N/A }
2N/A res->objects.objects_len = 1;
2N/A res->objects.objects_val[0] = single;
2N/A }
2N/A break;
2N/A
2N/A case DB_NEXT:
2N/A prev = extract_next_desc(previous, &next_type, &prev_desc);
2N/A switch (next_type) {
2N/A case LINEAR:
2N/A if (prev != 0) {
2N/A res->status = internal_db.next(prev, &where,
2N/A &single);
2N/A if (res->status == DB_SUCCESS)
2N/A assign_next_desc(&(res->nextinfo),
2N/A where);
2N/A } else
2N/A // invalid previous indicator
2N/A res->status = DB_NOTFOUND;
2N/A break;
2N/A case CHAINED:
2N/A if (prev_desc != NULL) {
2N/A res->status = internal_db.next(prev_desc,
2N/A &index_desc, &single);
2N/A if (res->status == DB_SUCCESS)
2N/A assign_next_desc(&(res->nextinfo),
2N/A index_desc);
2N/A } else
2N/A // invalid previous indicator
2N/A res->status = DB_NOTFOUND;
2N/A break;
2N/A default:
2N/A WARNING("db::exec_action: invalid previous indicator");
2N/A res->status = DB_BADQUERY;
2N/A }
2N/A if (previous && previous->db_next_desc_val) {
2N/A delete previous->db_next_desc_val;
2N/A previous->db_next_desc_len = 0;
2N/A previous->db_next_desc_val = NULL;
2N/A }
2N/A if (res->status == DB_SUCCESS) {
2N/A res->objects.objects_len = 1;
2N/A res->objects.objects_val = new entry_object_p;
2N/A if (res->objects.objects_val == NULL) {
2N/A res->objects.objects_len = 0;
2N/A delete res;
2N/A FATAL3(
2N/A "db::exec_action: cannot allocate space for DB_NEXT result",
2N/A DB_MEMORY_LIMIT, NULL);
2N/A }
2N/A res->objects.objects_val[0] = single;
2N/A }
2N/A break;
2N/A
2N/A case DB_RESET_NEXT:
2N/A prev = extract_next_desc(previous, &next_type, &prev_desc);
2N/A switch (next_type) {
2N/A case LINEAR:
2N/A res->status = DB_SUCCESS;
2N/A if (previous->db_next_desc_val) {
2N/A delete previous->db_next_desc_val;
2N/A previous->db_next_desc_len = 0;
2N/A previous->db_next_desc_val = NULL;
2N/A }
2N/A break; // do nothing
2N/A case CHAINED:
2N/A res->status = internal_db.reset_next(prev_desc);
2N/A if (previous->db_next_desc_val) {
2N/A delete previous->db_next_desc_val;
2N/A previous->db_next_desc_len = 0;
2N/A previous->db_next_desc_val = NULL;
2N/A }
2N/A break;
2N/A default:
2N/A WARNING("db::exec_action: invalid previous indicator");
2N/A res->status = DB_BADQUERY;
2N/A }
2N/A break;
2N/A
2N/A case DB_ALL:
2N/A res->status = internal_db.all(&num_answers, &ans);
2N/A res->objects.objects_len = (int) num_answers;
2N/A res->objects.objects_val = ans;
2N/A break;
2N/A
2N/A default:
2N/A WARNING("unknown request");
2N/A res->status = DB_BADQUERY;
2N/A return (res);
2N/A }
2N/A return (res);
2N/A}
2N/A
2N/A/*
2N/A * Log the given action and execute it.
2N/A * The minor version of the database is updated after the action has
2N/A * been executed and the database is flagged as being changed.
2N/A * Return the structure db_result, or NULL if the logging failed or the
2N/A * action is unknown.
2N/A*/
2N/Adb_result *
2N/Adb::log_action(db_action action, db_query *query, entry_object *content)
2N/A{
2N/A vers *v = internal_db.get_version()->nextminor();
2N/A db_result * res;
2N/A db_log_entry le(action, v, query, content);
2N/A bool_t copylog = FALSE;
2N/A
2N/A WRITELOCK(this, empty_result(DB_LOCK_ERROR), "w db::log_action");
2N/A /*
2N/A * If this is a synchronous operation on the master we should
2N/A * not copy the log for each operation. Doing so causes
2N/A * massive disk IO that hampers the performance of these operations.
2N/A * Where as on the replica these operations are not synchronous
2N/A * (batched) and don't affect the performance as much.
2N/A */
2N/A
2N/A if ((action == DB_ADD_NOSYNC) || (action == DB_REMOVE_NOSYNC))
2N/A copylog = TRUE;
2N/A
2N/A if (open_log(copylog) < 0) {
2N/A delete v;
2N/A WRITEUNLOCK(this, empty_result(DB_LOCK_ERROR),
2N/A "wu db::log_action DB_STORAGE_LIMIT");
2N/A return (empty_result(DB_STORAGE_LIMIT));
2N/A }
2N/A
2N/A if (logfile->append(&le) < 0) {
2N/A close_log();
2N/A WARNING_M("db::log_action: could not add log entry: ");
2N/A delete v;
2N/A WRITEUNLOCK(this, empty_result(DB_LOCK_ERROR),
2N/A "wu db::log_action DB_STORAGE_LIMIT");
2N/A return (empty_result(DB_STORAGE_LIMIT));
2N/A }
2N/A
2N/A switch (action) {
2N/A case DB_ADD_NOSYNC:
2N/A action = DB_ADD;
2N/A break;
2N/A case DB_REMOVE_NOSYNC:
2N/A action = DB_REMOVE;
2N/A break;
2N/A default:
2N/A if (logfile->sync_log() < 0) {
2N/A close_log();
2N/A WARNING_M("db::log_action: could not add log entry: ");
2N/A delete v;
2N/A WRITEUNLOCK(this, empty_result(DB_LOCK_ERROR),
2N/A "wu db::log_action DB_STORAGE_LIMIT");
2N/A return (empty_result(DB_STORAGE_LIMIT));
2N/A }
2N/A break;
2N/A }
2N/A res = exec_action(action, query, content, NULL);
2N/A internal_db.change_version(v);
2N/A delete v;
2N/A changed = TRUE;
2N/A WRITEUNLOCK(this, empty_result(DB_LOCK_ERROR), "wu db::log_action");
2N/A
2N/A return (res);
2N/A}
2N/A
2N/A/*
2N/A * Execute 'action' using the rest of the arguments as input.
2N/A * Return the result of the operation in a db_result structure;
2N/A * Return NULL if the request is unknown.
2N/A * If the action involves updates (ADD and REMOVE), it is logged first.
2N/A */
2N/Adb_result *
2N/Adb::execute(db_action action, db_query *query,
2N/A entry_object *content, db_next_desc* previous)
2N/A{
2N/A db_result *res;
2N/A
2N/A switch (action) {
2N/A case DB_LOOKUP:
2N/A case DB_FIRST:
2N/A case DB_NEXT:
2N/A case DB_ALL:
2N/A case DB_RESET_NEXT:
2N/A READLOCK(this, empty_result(DB_LOCK_ERROR), "r db::execute");
2N/A res = exec_action(action, query, content, previous);
2N/A READUNLOCK(this, empty_result(DB_LOCK_ERROR),
2N/A "ru db::execute");
2N/A return (res);
2N/A
2N/A case DB_ADD_NOLOG:
2N/A WRITELOCK(this, empty_result(DB_LOCK_ERROR), "w db::execute");
2N/A changed = TRUE;
2N/A res = exec_action(DB_ADD, query, content, previous);
2N/A WRITEUNLOCK(this, empty_result(DB_LOCK_ERROR),
2N/A "wu db::execute");
2N/A return (res);
2N/A
2N/A case DB_ADD:
2N/A case DB_REMOVE:
2N/A case DB_ADD_NOSYNC:
2N/A case DB_REMOVE_NOSYNC:
2N/A /* log_action() will do the locking */
2N/A return (log_action(action, query, content));
2N/A
2N/A default:
2N/A WARNING("db::execute: unknown request");
2N/A return (empty_result(DB_INTERNAL_ERROR));
2N/A }
2N/A}
2N/A
2N/A/* close existing logfile and delete its structure */
2N/Aint
2N/Adb::reset_log()
2N/A{
2N/A WRITELOCK(this, -1, "w db::reset_log");
2N/A /* try to close old log file */
2N/A /* doesnot matter since we do synchronous writes only */
2N/A if (logfile != NULL) {
2N/A if (logfile_opened == TRUE) {
2N/A logfile->sync_log();
2N/A if (logfile->close() < 0) {
2N/A WARNING_M("db::reset_log: could not close log file: ");
2N/A }
2N/A remove_from_standby_list(this);
2N/A }
2N/A delete logfile;
2N/A logfile = NULL;
2N/A }
2N/A logfile_opened = FALSE;
2N/A WRITEUNLOCK(this, -1, "wu db::reset_log");
2N/A return (0);
2N/A}
2N/A
2N/A/* close existing logfile, but leave its structure if exists */
2N/Aint
2N/Adb::close_log(int bypass_standby)
2N/A{
2N/A WRITELOCK(this, -1, "w db::close_log");
2N/A if (logfile != NULL && logfile_opened == TRUE) {
2N/A logfile->sync_log();
2N/A logfile->close();
2N/A if (!bypass_standby)
2N/A remove_from_standby_list(this);
2N/A }
2N/A logfile_opened = FALSE;
2N/A WRITEUNLOCK(this, -1, "wu db::close_log");
2N/A return (0);
2N/A}
2N/A
2N/A/* open logfile, creating its structure if it does not exist */
2N/Aint
2N/Adb::open_log(bool_t copylog)
2N/A{
2N/A WRITELOCK(this, -1, "w db::open_log");
2N/A if (logfile == NULL) {
2N/A if ((logfile = new db_log(logfilename, PICKLE_APPEND))
2N/A == NULL)
2N/A FATAL3("db::reset_log: cannot allocate space",
2N/A DB_MEMORY_LIMIT, -1);
2N/A }
2N/A
2N/A if (logfile_opened == TRUE) {
2N/A WRITEUNLOCK(this, -1, "wu db::open_log");
2N/A return (0);
2N/A }
2N/A
2N/A logfile->copylog = copylog;
2N/A
2N/A if ((logfile->open()) == NULL){
2N/A WARNING_M("db::open_log: could not open log file: ");
2N/A delete logfile;
2N/A logfile = NULL;
2N/A WRITEUNLOCK(this, -1, "wu db::open_log");
2N/A return (-1);
2N/A }
2N/A add_to_standby_list(this);
2N/A logfile_opened = TRUE;
2N/A WRITEUNLOCK(this, -1, "wu db::open_log");
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Execute log entry 'j' on the database identified by 'dbchar' if the
2N/A * version of j is later than that of the database. If 'j' is executed,
2N/A * 'count' is incremented and the database's verison is updated to that of 'j'.
2N/A * Returns TRUE always for valid log entries; FALSE otherwise.
2N/A */
2N/Astatic bool_t
2N/Aapply_log_entry(db_log_entry * j, char * dbchar, int *count)
2N/A{
2N/A db_mindex * db = (db_mindex *) dbchar;
2N/A bool_t status = TRUE;
2N/A
2N/A WRITELOCK(db, FALSE, "db::apply_log_entry");
2N/A
2N/A if (db->get_version()->earlier_than(j->get_version())) {
2N/A ++ *count;
2N/A#ifdef DEBUG
2N/A j->print();
2N/A#endif /* DEBUG */
2N/A switch (j->get_action()) {
2N/A case DB_ADD:
2N/A case DB_ADD_NOSYNC:
2N/A db->add(j->get_query(), j->get_object());
2N/A break;
2N/A
2N/A case DB_REMOVE:
2N/A case DB_REMOVE_NOSYNC:
2N/A db->remove(j->get_query());
2N/A break;
2N/A
2N/A default:
2N/A WARNING("db::apply_log_entry: unknown action_type");
2N/A WRITEUNLOCK(db, FALSE, "db::apply_log_entry");
2N/A return (FALSE);
2N/A }
2N/A db->change_version(j->get_version());
2N/A }
2N/A
2N/A WRITEUNLOCK(db, FALSE, "db::apply_log_entry");
2N/A
2N/A return (TRUE); /* always want to TRUE if action valid ? */
2N/A}
2N/A
2N/A/*
2N/A * Execute log entry 'j' on this db. 'j' is executed if its version is
2N/A * later than that of the database; if executed, the database's version
2N/A * will be changed to that of 'j', regardless of the status of the operation.
2N/A * Returns TRUE if 'j' was executed; FALSE if it was not.
2N/A * Log entry is added to this database's log if log_entry is applied.
2N/A */
2N/Abool_t
2N/Adb::execute_log_entry(db_log_entry *j)
2N/A{
2N/A int count = 0;
2N/A apply_log_entry (j, (char *) &internal_db, &count);
2N/A bool_t copylog = FALSE;
2N/A db_action action;
2N/A
2N/A /*
2N/A * If this is a synchronous operation on the master we should
2N/A * not copy the log for each operation. Doing so causes
2N/A * massive disk IO that hampers the performance of these operations.
2N/A * Where as on the replica these operations are not synchronous
2N/A * (batched) and don't affect the performance as much.
2N/A */
2N/A
2N/A action = j->get_action();
2N/A if ((action == DB_ADD_NOSYNC) || (action == DB_REMOVE_NOSYNC))
2N/A copylog = TRUE;
2N/A
2N/A /*
2N/A * should really record the log entry first, but can''t do that without
2N/A * knowing whether the log entry is applicable.
2N/A */
2N/A WRITELOCK(this, FALSE, "w db::execute_log_entry");
2N/A if (count == 1) {
2N/A if (open_log(copylog) < 0) {
2N/A WRITEUNLOCK(this, FALSE, "wu db::execute_log_entry");
2N/A return (FALSE);
2N/A }
2N/A
2N/A if (logfile->append(j) < 0) {
2N/A close_log();
2N/A WARNING_M(
2N/A "db::execute_log_entry: could not add log entry: ");
2N/A WRITEUNLOCK(this, FALSE, "wu db::execute_log_entry");
2N/A return (FALSE);
2N/A }
2N/A// close_log(); /* do this asynchronously */
2N/A }
2N/A WRITEUNLOCK(this, FALSE, "wu db::execute_log_entry");
2N/A
2N/A return (count == 1);
2N/A}
2N/A
2N/A/* Incorporate updates in log to database already loaded.
2N/A Does not affect "logfile" */
2N/Aint
2N/Adb::incorporate_log(char* filename)
2N/A{
2N/A db_log f(filename, PICKLE_READ);
2N/A int ret;
2N/A
2N/A WRITELOCK(this, -1, "w db::incorporate_log");
2N/A WRITELOCK2((&internal_db), -1, "w internal_db db::incorporate_log",
2N/A this);
2N/A internal_db.setNoWriteThrough();
2N/A ret = f.execute_on_log(&(apply_log_entry), (char *) &internal_db);
2N/A internal_db.clearNoWriteThrough();
2N/A WRITEUNLOCK2(this, (&internal_db), ret, ret,
2N/A "wu db::incorporate_log",
2N/A "wu mindex db::incorporate_log");
2N/A return (ret);
2N/A}
2N/A
2N/A/* Load database and incorporate any logged updates into the loaded copy.
2N/A Return TRUE if load succeeds; FALSE otherwise. */
2N/Abool_t
2N/Adb::load()
2N/A{
2N/A int count;
2N/A int load_status;
2N/A
2N/A WRITELOCK(this, FALSE, "w db::load");
2N/A if (changed == TRUE)
2N/A syslog(LOG_ERR,
2N/A "WARNING: the current db '%s' has been changed but not checkpointed",
2N/A dbfilename);
2N/A
2N/A unlink(tmpfilename); /* get rid of partial checkpoints */
2N/A
2N/A if ((load_status = internal_db.load(dbfilename)) != 0) {
2N/A if (load_status < 0)
2N/A syslog(LOG_ERR, "Load of db '%s' failed", dbfilename);
2N/A /* otherwise, there was just nothing to load */
2N/A WRITEUNLOCK(this, FALSE, "wu db::load");
2N/A return (FALSE);
2N/A }
2N/A
2N/A changed = FALSE;
2N/A reset_log();
2N/A WRITELOCK2((&internal_db), FALSE, "w internal_db db::load", this);
2N/A internal_db.setInitialLoad();
2N/A if ((count = incorporate_log(logfilename)) < 0)
2N/A syslog(LOG_ERR, "incorporation of db logfile '%s' load failed",
2N/A logfilename);
2N/A changed = (count > 0);
2N/A internal_db.clearInitialLoad();
2N/A WRITEUNLOCK2(this, (&internal_db),
2N/A (changed ? TRUE : FALSE), (changed ? TRUE : FALSE),
2N/A "wu db::load", "wu internal_db db::load");
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Initialize the database using table scheme 's'.
2N/A * Because the 'scheme' must be 'remembered' between restarts,
2N/A * after the initialization, the empty database is checkpointed to record
2N/A * the scheme. Returns TRUE if initialization succeeds; FALSE otherwise.
2N/A */
2N/Abool_t
2N/Adb::init(db_scheme * s)
2N/A{
2N/A bool_t ret = FALSE;
2N/A
2N/A WRITELOCK(this, FALSE, "w db::init");
2N/A internal_db.init(s);
2N/A if (internal_db.good()) {
2N/A unlink(tmpfilename); /* delete partial checkpoints */
2N/A unlink(logfilename); /* delete previous logfile */
2N/A reset_log();
2N/A changed = TRUE; /* force dump to get scheme stored. */
2N/A ret = checkpoint();
2N/A }
2N/A WRITEUNLOCK(this, FALSE, "wu db::init");
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A Write out in-memory copy of database to file.
2N/A 1. Update major version.
2N/A 2. Dump contents to temporary file.
2N/A 3. Rename temporary file to real database file.
2N/A 4. Remove log file.
2N/A A checkpoint is done only if it has changed since the previous checkpoint.
2N/A Returns TRUE if checkpoint was successful; FALSE otherwise.
2N/A*/
2N/Abool_t
2N/Adb::checkpoint()
2N/A{
2N/A WRITELOCK(this, FALSE, "w db::checkpoint");
2N/A if (changed == FALSE) {
2N/A WRITEUNLOCK(this, FALSE, "wu db::checkpoint");
2N/A return (TRUE);
2N/A }
2N/A
2N/A vers *oldversion = new vers(internal_db.get_version()); /* copy */
2N/A vers *nextversion = oldversion->nextmajor(); /* get next version */
2N/A internal_db.change_version(nextversion); /* change version */
2N/A
2N/A if (internal_db.dump(tmpfilename) < 0) { /* dump to tempfile */
2N/A WARNING_M("db::checkpoint: could not dump database: ");
2N/A internal_db.change_version(oldversion); /* rollback */
2N/A delete nextversion;
2N/A delete oldversion;
2N/A WRITEUNLOCK(this, FALSE, "wu db::checkpoint");
2N/A return (FALSE);
2N/A }
2N/A if (rename(tmpfilename, dbfilename) < 0){ /* rename permanently */
2N/A WARNING_M(
2N/A "db::checkpoint: could not rename temp file to db file: ");
2N/A internal_db.change_version(oldversion); /* rollback */
2N/A delete nextversion;
2N/A delete oldversion;
2N/A WRITEUNLOCK(this, FALSE, "wu db::checkpoint");
2N/A return (FALSE);
2N/A }
2N/A reset_log(); /* should check for what? */
2N/A unlink(logfilename); /* should do atomic rename and log delete */
2N/A delete nextversion;
2N/A delete oldversion;
2N/A changed = FALSE;
2N/A WRITEUNLOCK(this, FALSE, "wu db::checkpoint");
2N/A return (TRUE);
2N/A}
2N/A
2N/A
2N/A/* For generating log_list */
2N/A
2N/Astruct traverse_info {
2N/A vers *version; // version to check for
2N/A db_log_entry * head; // head of list of log entries found
2N/A db_log_entry * tail; // tail of list of log entries found
2N/A};
2N/A
2N/A/*
2N/A * For the given entry determine, if it is later than the version supplied,
2N/A * 1. increment 'count'.
2N/A * 2. add the entry to the list of log entries found.
2N/A *
2N/A * Since traversal happens on an automatic (struct traverse_info) in
2N/A * db::get_log_entries_since(), no locking is necessary.
2N/A */
2N/Astatic bool_t entry_since(db_log_entry * j, char * tichar, int *count)
2N/A{
2N/A traverse_info *ti = (traverse_info*) tichar;
2N/A
2N/A if (ti->version->earlier_than(j->get_version())) {
2N/A ++ *count;
2N/A// j->print(); // debug
2N/A if (ti->head == NULL)
2N/A ti->head = j;
2N/A else {
2N/A ti->tail->setnextptr(j); // make last entry point to j
2N/A }
2N/A ti->tail = j; // make j new last entry
2N/A }
2N/A
2N/A return (TRUE);
2N/A}
2N/A
2N/A/* Return structure db_log_list containing entries that are later
2N/A than the version 'v' given. */
2N/Adb_log_list*
2N/Adb::get_log_entries_since(vers * v)
2N/A{
2N/A int count;
2N/A struct traverse_info ti;
2N/A db_log f(logfilename, PICKLE_READ);
2N/A
2N/A ti.version = v;
2N/A ti.head = ti.tail = NULL;
2N/A
2N/A count = f.execute_on_log(&(entry_since), (char *) &ti, FALSE);
2N/A
2N/A db_log_list * answer = new db_log_list;
2N/A
2N/A if (answer == NULL)
2N/A FATAL3("db::get_log_entries_since: cannot allocate space",
2N/A DB_MEMORY_LIMIT, NULL);
2N/A
2N/A answer->list.list_len = count;
2N/A
2N/A if (count > 0) {
2N/A db_log_entry_p *entries;
2N/A db_log_entry_p currentry, nextentry;
2N/A int i;
2N/A
2N/A entries = answer->list.list_val = new db_log_entry_p[count];
2N/A if (entries == NULL) {
2N/A delete answer;
2N/A FATAL3(
2N/A "db::get_log_entries_since: cannot allocate space for entries",
2N/A DB_MEMORY_LIMIT, NULL);
2N/A }
2N/A currentry = ti.head;
2N/A for (i = 0, currentry = ti.head;
2N/A i < count && currentry != NULL;
2N/A i++) {
2N/A entries[i] = currentry;
2N/A nextentry = currentry->getnextptr();
2N/A currentry->setnextptr(NULL);
2N/A currentry = nextentry;
2N/A }
2N/A } else
2N/A answer->list.list_val = NULL;
2N/A
2N/A return (answer);
2N/A}
2N/A
2N/A/* Delete all files associated with database. */
2N/Aint
2N/Adb::remove_files()
2N/A{
2N/A WRITELOCK(this, -1, "w db::remove_files");
2N/A unlink(tmpfilename); /* delete partial checkpoints */
2N/A reset_log();
2N/A unlink(logfilename); /* delete logfile */
2N/A unlink(dbfilename); /* delete database file */
2N/A WRITEUNLOCK(this, -1, "wu db::remove_files");
2N/A return (0);
2N/A}
2N/A
2N/Adb_status
2N/Adb::sync_log() {
2N/A
2N/A db_status ret;
2N/A
2N/A WRITELOCK(this, DB_LOCK_ERROR, "w db::sync_log");
2N/A if (logfile == 0) {
2N/A ret = DB_BADTABLE;
2N/A } else {
2N/A if (logfile_opened == FALSE || logfile->sync_log())
2N/A ret = DB_SUCCESS;
2N/A else
2N/A ret = DB_SYNC_FAILED;
2N/A }
2N/A WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db::sync_log");
2N/A return (ret);
2N/A}
2N/A
2N/A/* Pass configuration information to the db_mindex */
2N/Abool_t
2N/Adb::configure(char *objName) {
2N/A return (internal_db.configure(objName));
2N/A}
2N/A
2N/Adb_mindex *
2N/Adb::mindex(void) {
2N/A return (&internal_db);
2N/A}