summaryrefslogtreecommitdiffstats
path: root/ldap/servers/slapd/task.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers/slapd/task.c')
-rw-r--r--ldap/servers/slapd/task.c1703
1 files changed, 1703 insertions, 0 deletions
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
new file mode 100644
index 00000000..7e4bd423
--- /dev/null
+++ b/ldap/servers/slapd/task.c
@@ -0,0 +1,1703 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * directory online tasks (import, export, backup, restore)
+ */
+
+#include "slap.h"
+
+
+/* don't panic, this is only used when creating new tasks or removing old
+ * ones...
+ */
+static Slapi_Task *global_task_list = NULL;
+static PRLock *global_task_lock = NULL;
+static int shutting_down = 0;
+
+
+#define TASK_BASE_DN "cn=tasks, cn=config"
+#define TASK_IMPORT_DN "cn=import, cn=tasks, cn=config"
+#define TASK_EXPORT_DN "cn=export, cn=tasks, cn=config"
+#define TASK_BACKUP_DN "cn=backup, cn=tasks, cn=config"
+#define TASK_RESTORE_DN "cn=restore, cn=tasks, cn=config"
+#define TASK_INDEX_DN "cn=index, cn=tasks, cn=config"
+#if defined(UPGRADEDB)
+#define TASK_UPGRADEDB_DN "cn=upgradedb, cn=tasks, cn=config"
+#endif
+
+#define TASK_LOG_NAME "nsTaskLog"
+#define TASK_STATUS_NAME "nsTaskStatus"
+#define TASK_EXITCODE_NAME "nsTaskExitCode"
+#define TASK_PROGRESS_NAME "nsTaskCurrentItem"
+#define TASK_WORK_NAME "nsTaskTotalItems"
+
+#define DEFAULT_TTL "120" /* seconds */
+
+
+static int task_modify(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg);
+static int task_deny(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg);
+static int task_generic_destructor(Slapi_Task *task);
+
+/* create new task, fill in DN, and setup modify callback */
+static Slapi_Task *new_task(const char *dn)
+{
+ Slapi_Task *task = (Slapi_Task *)slapi_ch_calloc(1, sizeof(Slapi_Task));
+
+ if (task == NULL)
+ return NULL;
+ PR_Lock(global_task_lock);
+ task->next = global_task_list;
+ global_task_list = task;
+ PR_Unlock(global_task_lock);
+
+ task->task_dn = slapi_ch_strdup(dn);
+ task->destructor = task_generic_destructor;
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, dn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", task_modify, (void *)task);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, dn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", task_deny, NULL);
+ /* don't add entries under this one */
+#if 0
+ /* don't know why, but this doesn't work. it makes the current add
+ * operation fail. :(
+ */
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, dn,
+ LDAP_SCOPE_SUBTREE, "(objectclass=*)", task_deny, NULL);
+#endif
+
+ return task;
+}
+
+/* called by the event queue to destroy a task */
+static void destroy_task(time_t when, void *arg)
+{
+ Slapi_Task *task = (Slapi_Task *)arg;
+ Slapi_Task *t1;
+ Slapi_PBlock *pb = slapi_pblock_new();
+
+ if (task->destructor != NULL)
+ (*task->destructor)(task);
+
+ /* if when == 0, we're already locked (called during shutdown) */
+ if (when != 0) {
+ PR_Lock(global_task_lock);
+ }
+ if (global_task_list == task) {
+ global_task_list = task->next;
+ } else {
+ for (t1 = global_task_list; t1; t1 = t1->next) {
+ if (t1->next == task) {
+ t1->next = task->next;
+ break;
+ }
+ }
+ }
+ if (when != 0) {
+ PR_Unlock(global_task_lock);
+ }
+
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP,
+ task->task_dn, LDAP_SCOPE_BASE, "(objectclass=*)", task_modify);
+ slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP,
+ task->task_dn, LDAP_SCOPE_BASE, "(objectclass=*)", task_deny);
+ slapi_delete_internal_set_pb(pb, task->task_dn, NULL, NULL,
+ (void *)plugin_get_default_component_id(), 0);
+
+ slapi_delete_internal_pb(pb);
+ slapi_pblock_destroy(pb);
+
+ slapi_ch_free((void **)&task->task_dn);
+ slapi_ch_free((void **)&task);
+}
+
+
+/********** some useful helper functions **********/
+
+
+/* extract a single value from the entry (as a string) -- if it's not in the
+ * entry, the default will be returned (which can be NULL).
+ * you do not need to free anything returned by this.
+ */
+static const char *fetch_attr(Slapi_Entry *e, const char *attrname,
+ const char *default_val)
+{
+ Slapi_Attr *attr;
+ Slapi_Value *val = NULL;
+
+ if (slapi_entry_attr_find(e, attrname, &attr) != 0)
+ return default_val;
+ slapi_attr_first_value(attr, &val);
+ return slapi_value_get_string(val);
+}
+
+/* supply the pblock, destroy it when you're done */
+static Slapi_Entry *get_internal_entry(Slapi_PBlock *pb, char *dn)
+{
+ Slapi_Entry **entries = NULL;
+ int ret = 0;
+
+ slapi_search_internal_set_pb(pb, dn, LDAP_SCOPE_BASE, "(objectclass=*)",
+ NULL, 0, NULL, NULL, (void *)plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+ if (ret != LDAP_SUCCESS) {
+ LDAPDebug(LDAP_DEBUG_ANY, "WARNING: can't find task entry '%s'\n",
+ dn, 0, 0);
+ return NULL;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if ((NULL == entries) || (NULL == entries[0])) {
+ LDAPDebug(LDAP_DEBUG_ANY, "WARNING: can't find task entry '%s'\n",
+ dn, 0, 0);
+ return NULL;
+ }
+ return entries[0];
+}
+
+static void modify_internal_entry(char *dn, LDAPMod **mods)
+{
+ Slapi_PBlock pb;
+ Slapi_Operation *op;
+ int ret = 0;
+ int tries = 0;
+ int dont_write_file = 1;
+
+ do {
+
+ pblock_init(&pb);
+
+ slapi_modify_internal_set_pb(&pb, dn, mods, NULL, NULL,
+ (void *)plugin_get_default_component_id(), 0);
+
+ /* all modifications to the cn=tasks subtree are transient --
+ * we erase them all when the server starts up next time, so there's
+ * no need to save them in the dse file.
+ */
+
+ slapi_pblock_set(&pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &dont_write_file);
+ /* Make sure these mods are not logged in audit or changelog */
+ slapi_pblock_get(&pb, SLAPI_OPERATION, &op);
+ operation_set_flag(op, OP_FLAG_ACTION_NOLOG);
+
+ slapi_modify_internal_pb(&pb);
+ slapi_pblock_get(&pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+ if (ret != LDAP_SUCCESS) {
+ /* could be waiting for another thread to finish adding this
+ * entry -- try at least 3 times before giving up.
+ */
+ tries++;
+ if (tries == 3) {
+ LDAPDebug(LDAP_DEBUG_ANY, "WARNING: can't modify task "
+ "entry '%s'; %s (%d)\n", dn, ldap_err2string(ret), ret);
+ pblock_done(&pb);
+ return;
+ }
+ DS_Sleep(PR_SecondsToInterval(1));
+ }
+
+ pblock_done(&pb);
+
+ } while (ret != LDAP_SUCCESS);
+}
+
+
+/********** helper functions for dealing with task logging **********/
+
+#define LOG_BUFFER 256
+/* if the cumul. log gets larger than this, it's truncated: */
+#define MAX_SCROLLBACK_BUFFER 8192
+
+/* this changes the 'nsTaskStatus' value, which is transient (anything logged
+ * here wipes out any previous status)
+ */
+void slapi_task_log_status(Slapi_Task *task, char *format, ...)
+{
+ va_list ap;
+
+ if (! task->task_status)
+ task->task_status = (char *)slapi_ch_malloc(10 * LOG_BUFFER);
+ if (! task->task_status)
+ return; /* out of memory? */
+
+ va_start(ap, format);
+ PR_vsnprintf(task->task_status, (10 * LOG_BUFFER), format, ap);
+ va_end(ap);
+ slapi_task_status_changed(task);
+}
+
+/* this adds a line to the 'nsTaskLog' value, which is cumulative (anything
+ * logged here is added to the end)
+ */
+void slapi_task_log_notice(Slapi_Task *task, char *format, ...)
+{
+ va_list ap;
+ char buffer[LOG_BUFFER];
+ size_t len;
+
+ va_start(ap, format);
+ PR_vsnprintf(buffer, LOG_BUFFER, format, ap);
+ va_end(ap);
+
+ len = 2 + strlen(buffer) + (task->task_log ? strlen(task->task_log) : 0);
+ if (len > MAX_SCROLLBACK_BUFFER) {
+ size_t i;
+ char *newbuf;
+
+ /* start from middle of buffer, and find next linefeed */
+ i = strlen(task->task_log)/2;
+ while (task->task_log[i] && (task->task_log[i] != '\n'))
+ i++;
+ if (task->task_log[i])
+ i++;
+ len = strlen(task->task_log) - i + 2 + strlen(buffer);
+ newbuf = (char *)slapi_ch_malloc(len);
+ if (! newbuf)
+ return; /* out of memory? */
+ strcpy(newbuf, task->task_log + i);
+ slapi_ch_free((void **)&task->task_log);
+ task->task_log = newbuf;
+ } else {
+ if (! task->task_log) {
+ task->task_log = (char *)slapi_ch_malloc(len);
+ task->task_log[0] = 0;
+ } else {
+ task->task_log = (char *)slapi_ch_realloc(task->task_log, len);
+ }
+ if (! task->task_log)
+ return; /* out of memory? */
+ }
+
+ if (task->task_log[0])
+ strcat(task->task_log, "\n");
+ strcat(task->task_log, buffer);
+
+ slapi_task_status_changed(task);
+}
+
+static int task_generic_destructor(Slapi_Task *task)
+{
+ if (task->task_log) {
+ slapi_ch_free((void **)&task->task_log);
+ }
+ if (task->task_status) {
+ slapi_ch_free((void **)&task->task_status);
+ }
+ task->task_log = task->task_status = NULL;
+ return 0;
+}
+
+
+/********** actual task callbacks **********/
+
+
+static int task_deny(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ /* internal operations (conn=NULL) are allowed to do whatever they want */
+ if (pb->pb_conn == NULL) {
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static int task_modify(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ Slapi_Task *task = (Slapi_Task *)arg;
+ LDAPMod **mods;
+ int i;
+
+ /* the connection block will be NULL for internal operations */
+ if (pb->pb_conn == NULL) {
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ /* ignore eAfter, just scan the mods for anything unacceptable */
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ for (i = 0; mods[i] != NULL; i++) {
+ /* for some reason, "modifiersName" and "modifyTimestamp" are
+ * stuck in by the server */
+ if ((strcasecmp(mods[i]->mod_type, "ttl") != 0) &&
+ (strcasecmp(mods[i]->mod_type, "nsTaskCancel") != 0) &&
+ (strcasecmp(mods[i]->mod_type, "modifiersName") != 0) &&
+ (strcasecmp(mods[i]->mod_type, "modifyTimestamp") != 0)) {
+ /* you aren't allowed to change this! */
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+
+ /* okay, we've decided to accept these changes. now look at the new
+ * entry and absorb any new values.
+ */
+ if (strcasecmp(fetch_attr(eAfter, "nsTaskCancel", "false"), "true") == 0) {
+ /* cancel this task, if not already */
+ if (task->task_state != SLAPI_TASK_CANCELLED) {
+ task->task_state = SLAPI_TASK_CANCELLED;
+ if (task->cancel) {
+ (*task->cancel)(task);
+ LDAPDebug(LDAP_DEBUG_ANY, "Cancelling task '%s'\n",
+ fetch_attr(eAfter, "cn", "?"), 0, 0);
+ }
+ }
+ }
+ /* we fetch ttl from the entry when it's needed */
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int task_import_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ Slapi_Attr *attr;
+ Slapi_Value *val = NULL;
+ Slapi_Backend *be = NULL;
+ const char *cn, *instance_name;
+ char **ldif_file = NULL, **include = NULL, **exclude = NULL;
+ int idx, rv = 0;
+ const char *do_attr_indexes, *uniqueid_kind_str;
+ int uniqueid_kind = SLAPI_UNIQUEID_GENERATE_TIME_BASED;
+ Slapi_PBlock mypb;
+ Slapi_Task *task;
+ char *nameFrombe_name = NULL;
+ const char *encrypt_on_import = NULL;
+
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ instance_name = fetch_attr(e, "nsInstance", NULL);
+
+ encrypt_on_import = fetch_attr(e, "nsImportEncrypt", NULL);
+
+ /* include/exclude suffixes */
+ if (slapi_entry_attr_find(e, "nsIncludeSuffix", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&include, slapi_ch_strdup(slapi_value_get_string(val)));
+ }
+ }
+ if (slapi_entry_attr_find(e, "nsExcludeSuffix", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&exclude, slapi_ch_strdup(slapi_value_get_string(val)));
+ }
+ }
+
+ /*
+ * if instance is given, just use it to get the backend.
+ * otherwise, we use included/excluded suffix list to specify a backend.
+ */
+ if (NULL == instance_name) {
+ char **instances, **ip;
+ int counter;
+
+ if (slapi_lookup_instance_name_by_suffixes(include, exclude,
+ &instances) < 0) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: No backend instance is specified.\n", 0, 0, 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ if (instances) {
+ for (ip = instances, counter = 0; ip && *ip; ip++, counter++)
+ ;
+
+ if (counter == 1){
+ instance_name = *instances;
+ nameFrombe_name = *instances;
+
+ }
+ else if (counter == 0) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: No backend instance is specified.\n", 0, 0, 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: Multiple backend instances are specified: "
+ "%s, %s, ...\n", instances[0], instances[1], 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ } else {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+
+ /* lookup the backend */
+ be = slapi_be_select_by_instance_name(instance_name);
+ if (be == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't import to nonexistent backend %s\n",
+ instance_name, 0, 0);
+ slapi_ch_free_string(&nameFrombe_name);
+ *returncode = LDAP_NO_SUCH_OBJECT;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* refuse to do an import on pre-V3 plugins. plugin api V3 is the one
+ * for DS 5.0 where the import/export stuff changed a lot.
+ */
+ if (! SLAPI_PLUGIN_IS_V3(be->be_database)) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't perform an import with pre-V3 "
+ "backend plugin %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ slapi_ch_free_string(&nameFrombe_name);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ if (be->be_database->plg_ldif2db == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "ERROR: no ldif2db function defined for "
+ "backend %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ slapi_ch_free_string(&nameFrombe_name);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* get ldif filenames -- from here on, memory has been allocated */
+ if (slapi_entry_attr_find(e, "nsFilename", &attr) != 0) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ slapi_ch_free_string(&nameFrombe_name);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&ldif_file, slapi_ch_strdup(slapi_value_get_string(val)));
+ }
+
+ do_attr_indexes = fetch_attr(e, "nsImportIndexAttrs", "true");
+ uniqueid_kind_str = fetch_attr(e, "nsUniqueIdGenerator", NULL);
+ if (uniqueid_kind_str != NULL) {
+ if (strcasecmp(uniqueid_kind_str, "none") == 0) {
+ uniqueid_kind = SLAPI_UNIQUEID_GENERATE_NONE;
+ } else if (strcasecmp(uniqueid_kind_str, "deterministic") == 0) {
+ uniqueid_kind = SLAPI_UNIQUEID_GENERATE_NAME_BASED;
+ } else {
+ /* default - time based */
+ uniqueid_kind = SLAPI_UNIQUEID_GENERATE_TIME_BASED;
+ }
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ rv = LDAP_OPERATIONS_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+
+ memset(&mypb, 0, sizeof(mypb));
+ mypb.pb_backend = be;
+ mypb.pb_plugin = be->be_database;
+ mypb.pb_removedupvals = atoi(fetch_attr(e, "nsImportChunkSize", "0"));
+ mypb.pb_ldif2db_noattrindexes =
+ !(strcasecmp(do_attr_indexes, "true") == 0);
+ mypb.pb_ldif_generate_uniqueid = uniqueid_kind;
+ mypb.pb_ldif_namespaceid =
+ (char *)fetch_attr(e, "nsUniqueIdGeneratorNamespace", NULL);
+ mypb.pb_instance_name = (char *)instance_name;
+ mypb.pb_ldif_files = ldif_file;
+ mypb.pb_ldif_include = include;
+ mypb.pb_ldif_exclude = exclude;
+ mypb.pb_task = task;
+ mypb.pb_task_flags = TASK_RUNNING_AS_TASK;
+ if (NULL != encrypt_on_import && 0 == strcasecmp(encrypt_on_import, "true") ) {
+ mypb.pb_ldif_encrypt = 1;
+ }
+
+ rv = (*mypb.pb_plugin->plg_ldif2db)(&mypb);
+ if (rv == 0) {
+ slapi_entry_attr_set_charptr(e, TASK_LOG_NAME, "");
+ slapi_entry_attr_set_charptr(e, TASK_STATUS_NAME, "");
+ slapi_entry_attr_set_int(e, TASK_PROGRESS_NAME, task->task_progress);
+ slapi_entry_attr_set_int(e, TASK_WORK_NAME, task->task_work);
+ }
+
+out:
+ slapi_ch_free_string(&nameFrombe_name);
+ charray_free(ldif_file);
+ charray_free(include);
+ charray_free(exclude);
+ if (rv != 0) {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ destroy_task(1, task);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static void task_export_thread(void *arg)
+{
+ Slapi_PBlock *pb = (Slapi_PBlock *)arg;
+ char **instance_names = (char **)pb->pb_instance_name;
+ char **inp;
+ char *ldif_file = pb->pb_ldif_file;
+ char *this_ldif_file = NULL;
+ Slapi_Backend *be = NULL;
+ int rv = -1;
+ int count;
+ Slapi_Task *task = pb->pb_task;
+
+ for (count = 0, inp = instance_names; *inp; inp++, count++)
+ ;
+ task->task_work = count;
+ task->task_progress = 0;
+ task->task_state = SLAPI_TASK_RUNNING;
+ slapi_task_status_changed(task);
+
+ for (inp = instance_names; *inp; inp++) {
+ int release_me = 0;
+ /* lookup the backend */
+ be = slapi_be_select_by_instance_name((const char *)*inp);
+ if (be == NULL) {
+ /* shouldn't happen */
+ LDAPDebug(LDAP_DEBUG_ANY, "ldbm2ldif: backend '%s' is AWOL!\n",
+ (const char *)*inp, 0, 0);
+ continue;
+ }
+
+ pb->pb_backend = be;
+ pb->pb_plugin = be->be_database;
+ pb->pb_instance_name = (char *)*inp;
+
+ /* ldif_file name for each? */
+ if (pb->pb_ldif_printkey & EXPORT_APPENDMODE) {
+ if (inp == instance_names) { /* first export */
+ pb->pb_ldif_printkey |= EXPORT_APPENDMODE_1;
+ } else {
+ pb->pb_ldif_printkey &= ~EXPORT_APPENDMODE_1;
+ }
+ } else {
+ if (strcmp(ldif_file, "-")) { /* not '-' */
+ char *p;
+#if defined( _WIN32 )
+ char sep = '\\';
+ if (NULL != strchr(ldif_file, '/'))
+ sep = '/';
+#else
+ char sep = '/';
+#endif
+ this_ldif_file = (char *)slapi_ch_malloc(strlen(ldif_file) +
+ strlen(*inp) + 2);
+ p = strrchr(ldif_file, sep);
+ if (NULL == p) {
+ sprintf(this_ldif_file, "%s_%s", *inp, ldif_file);
+ } else {
+ char *q;
+
+ q = p + 1;
+ *p = '\0';
+ sprintf(this_ldif_file, "%s%c%s_%s",
+ ldif_file, sep, *inp, q);
+ *p = sep;
+ }
+ pb->pb_ldif_file = this_ldif_file;
+ release_me = 1;
+ }
+ }
+
+ slapi_task_log_notice(task, "Beginning export of '%s'", *inp);
+ LDAPDebug(LDAP_DEBUG_ANY, "Beginning export of '%s'\n", *inp, 0, 0);
+
+ rv = (*pb->pb_plugin->plg_db2ldif)(pb);
+ if (rv != 0) {
+ slapi_task_log_notice(task, "backend '%s' export failed (%d)",
+ *inp, rv);
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ldbm2ldif: backend '%s' export failed (%d)\n",
+ (const char *)*inp, rv, 0);
+ }
+
+ if (release_me) {
+ slapi_ch_free((void **)&this_ldif_file);
+ }
+
+ if (rv != 0)
+ break;
+
+ task->task_progress++;
+ slapi_task_status_changed(task);
+ }
+
+ /* free the memory now */
+ charray_free(instance_names);
+ slapi_ch_free((void **)&ldif_file);
+ charray_free(pb->pb_ldif_include);
+ charray_free(pb->pb_ldif_exclude);
+ slapi_pblock_destroy(pb);
+
+ if (rv == 0) {
+ slapi_task_log_notice(task, "Export finished.");
+ LDAPDebug(LDAP_DEBUG_ANY, "Export finished.\n", 0, 0, 0);
+ } else {
+ slapi_task_log_notice(task, "Export failed.");
+ LDAPDebug(LDAP_DEBUG_ANY, "Export failed.\n", 0, 0, 0);
+ }
+
+ task->task_exitcode = rv;
+ task->task_state = SLAPI_TASK_FINISHED;
+ slapi_task_status_changed(task);
+}
+
+static int task_export_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ Slapi_Attr *attr;
+ Slapi_Value *val = NULL;
+ Slapi_Backend *be = NULL;
+ const char *cn;
+ char *ldif_file = NULL;
+ char **instance_names = NULL, **inp;
+ char **include = NULL, **exclude = NULL;
+ int idx, rv = SLAPI_DSE_CALLBACK_OK;
+ int export_replica_flag = 0;
+ int ldif_printkey_flag = 0;
+ int dump_uniqueid_flag = 0;
+ int instance_cnt = 0;
+ const char *my_ldif_file;
+ const char *use_one_file;
+ const char *export_replica;
+ const char *ldif_printkey;
+ const char *dump_uniqueid;
+ Slapi_PBlock *mypb = NULL;
+ Slapi_Task *task = NULL;
+ PRThread *thread;
+ const char *decrypt_on_export = NULL;
+
+ *returncode = LDAP_SUCCESS;
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ decrypt_on_export = fetch_attr(e, "nsExportDecrypt", NULL);
+
+ /* nsInstances -- from here on, memory has been allocated */
+ if (slapi_entry_attr_find(e, "nsInstance", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&instance_names,
+ slapi_ch_strdup(slapi_value_get_string(val)));
+ instance_cnt++;
+ }
+ }
+
+ /* include/exclude suffixes */
+ if (slapi_entry_attr_find(e, "nsIncludeSuffix", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&include, slapi_ch_strdup(slapi_value_get_string(val)));
+ }
+ }
+ if (slapi_entry_attr_find(e, "nsExcludeSuffix", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ charray_add(&exclude, slapi_ch_strdup(slapi_value_get_string(val)));
+ }
+ }
+
+ if (NULL == instance_names) {
+ char **ip;
+
+ if (slapi_lookup_instance_name_by_suffixes(include, exclude,
+ &instance_names) < 0) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: No backend instance is specified.\n", 0, 0, 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ if (instance_names) {
+ for (ip = instance_names, instance_cnt = 0; ip && *ip;
+ ip++, instance_cnt++)
+ ;
+
+ if (instance_cnt == 0) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: No backend instance is specified.\n", 0, 0, 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ } else {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: No backend instance is specified.\n", 0, 0, 0);
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ }
+
+ /* ldif file name */
+ if ((my_ldif_file = fetch_attr(e, "nsFilename", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ ldif_file = slapi_ch_strdup(my_ldif_file);
+ /* if true, multiple backends are dumped into one ldif file */
+ use_one_file = fetch_attr(e, "nsUseOneFile", "true");
+ if (strcasecmp(use_one_file, "true") == 0) {
+ ldif_printkey_flag |= EXPORT_APPENDMODE;
+ }
+
+ /* -r: export replica */
+ export_replica = fetch_attr(e, "nsExportReplica", "false");
+ if (!strcasecmp(export_replica, "true")) /* true */
+ export_replica_flag = 1;
+
+ /* -N: eq "false" ==> does not print out key value */
+ ldif_printkey = fetch_attr(e, "nsPrintKey", "true");
+ if (!strcasecmp(ldif_printkey, "true")) /* true */
+ ldif_printkey_flag |= EXPORT_PRINTKEY;
+
+ /* -C: eq "true" ==> use only id2entry file */
+ ldif_printkey = fetch_attr(e, "nsUseId2Entry", "false");
+ if (!strcasecmp(ldif_printkey, "true")) /* true */
+ ldif_printkey_flag |= EXPORT_ID2ENTRY_ONLY;
+
+ /* if "true" ==> 8-bit strings are not base64 encoded */
+ ldif_printkey = fetch_attr(e, "nsMinimalEncoding", "false");
+ if (!strcasecmp(ldif_printkey, "true")) /* true */
+ ldif_printkey_flag |= EXPORT_MINIMAL_ENCODING;
+
+ /* -U: eq "true" ==> does not fold the output */
+ ldif_printkey = fetch_attr(e, "nsNoWrap", "false");
+ if (!strcasecmp(ldif_printkey, "true")) /* true */
+ ldif_printkey_flag |= EXPORT_NOWRAP;
+
+ /* -1: eq "true" ==> does not print version line */
+ ldif_printkey = fetch_attr(e, "nsNoVersionLine", "false");
+ if (!strcasecmp(ldif_printkey, "true")) /* true */
+ ldif_printkey_flag |= EXPORT_NOVERSION;
+
+ /* -u: eq "false" ==> does not dump unique id */
+ dump_uniqueid = fetch_attr(e, "nsDumpUniqId", "true");
+ if (!strcasecmp(dump_uniqueid, "true")) /* true */
+ dump_uniqueid_flag = 1;
+
+ /* check that all the backends are ok */
+ for (inp = instance_names; *inp; inp++) {
+ /* lookup the backend */
+ be = slapi_be_select_by_instance_name((const char *)*inp);
+ if (be == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "can't export to nonexistent backend %s\n", *inp, 0, 0);
+ *returncode = LDAP_NO_SUCH_OBJECT;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* refuse to do an export on pre-V3 plugins. plugin api V3 is the one
+ * for DS 5.0 where the import/export stuff changed a lot.
+ */
+ if (! SLAPI_PLUGIN_IS_V3(be->be_database)) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't perform an export with pre-V3 "
+ "backend plugin %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ if (be->be_database->plg_db2ldif == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "ERROR: no db2ldif function defined for "
+ "backend %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+ task->task_work = instance_cnt;
+ task->task_progress = 0;
+
+ mypb = slapi_pblock_new();
+ if (mypb == NULL) {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ mypb->pb_ldif_include = include;
+ mypb->pb_ldif_exclude = exclude;
+ mypb->pb_ldif_printkey = ldif_printkey_flag;
+ mypb->pb_ldif_dump_replica = export_replica_flag;
+ mypb->pb_ldif_dump_uniqueid = dump_uniqueid_flag;
+ mypb->pb_ldif_file = ldif_file;
+ /* horrible hack */
+ mypb->pb_instance_name = (char *)instance_names;
+ mypb->pb_task = task;
+ mypb->pb_task_flags = TASK_RUNNING_AS_TASK;
+ if (NULL != decrypt_on_export && 0 == strcasecmp(decrypt_on_export, "true") ) {
+ mypb->pb_ldif_encrypt = 1;
+ }
+
+ /* start the export as a separate thread */
+ thread = PR_CreateThread(PR_USER_THREAD, task_export_thread,
+ (void *)mypb, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (thread == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "unable to create ldbm2ldif thread!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ slapi_pblock_destroy(mypb);
+ goto out;
+ }
+
+ /* thread successful -- don't free the pb, let the thread do that. */
+ return SLAPI_DSE_CALLBACK_OK;
+
+out:
+ charray_free(instance_names);
+ charray_free(include);
+ charray_free(exclude);
+ if (ldif_file != NULL) {
+ slapi_ch_free((void **)&ldif_file);
+ }
+ if (task) {
+ destroy_task(1, task);
+ }
+
+ return rv;
+}
+
+
+static void task_backup_thread(void *arg)
+{
+ Slapi_PBlock *pb = (Slapi_PBlock *)arg;
+ Slapi_Task *task = pb->pb_task;
+ int rv;
+
+ task->task_work = 1;
+ task->task_progress = 0;
+ task->task_state = SLAPI_TASK_RUNNING;
+ slapi_task_status_changed(task);
+
+ slapi_task_log_notice(task, "Beginning backup of '%s'",
+ pb->pb_plugin->plg_name);
+ LDAPDebug(LDAP_DEBUG_ANY, "Beginning backup of '%s'\n",
+ pb->pb_plugin->plg_name, 0, 0);
+
+ rv = (*pb->pb_plugin->plg_db2archive)(pb);
+ if (rv != 0) {
+ slapi_task_log_notice(task, "Backup failed (error %d)", rv);
+ slapi_task_log_status(task, "Backup failed (error %d)", rv);
+ LDAPDebug(LDAP_DEBUG_ANY, "Backup failed (error %d)\n", rv, 0, 0);
+ } else {
+ slapi_task_log_notice(task, "Backup finished.");
+ slapi_task_log_status(task, "Backup finished.");
+ LDAPDebug(LDAP_DEBUG_ANY, "Backup finished.\n", 0, 0, 0);
+ }
+
+ task->task_progress = 1;
+ task->task_exitcode = rv;
+ task->task_state = SLAPI_TASK_FINISHED;
+ slapi_task_status_changed(task);
+
+ slapi_ch_free((void **)&pb->pb_seq_val);
+ slapi_pblock_destroy(pb);
+}
+
+static int task_backup_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ Slapi_Backend *be = NULL;
+ PRThread *thread = NULL;
+ const char *cn;
+ const char *archive_dir = NULL;
+ const char *my_database_type = NULL;
+ const char *database_type = "ldbm database";
+ char *cookie = NULL;
+ int rv = SLAPI_DSE_CALLBACK_OK;
+ Slapi_PBlock *mypb = NULL;
+ Slapi_Task *task = NULL;
+
+ *returncode = LDAP_SUCCESS;
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* archive dir name */
+ if ((archive_dir = fetch_attr(e, "nsArchiveDir", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* database type */
+ my_database_type = fetch_attr(e, "nsDatabaseType", NULL);
+ if (NULL != my_database_type)
+ database_type = my_database_type;
+
+ /* get backend that has db2archive and the database type matches. */
+ cookie = NULL;
+ be = slapi_get_first_backend(&cookie);
+ while (be) {
+ if (NULL != be->be_database->plg_db2archive &&
+ !strcasecmp(database_type, be->be_database->plg_name))
+ break;
+
+ be = (backend *)slapi_get_next_backend (cookie);
+ }
+ if (NULL == be || NULL == be->be_database->plg_db2archive) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: no db2archive function defined.\n", 0, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ if (! SLAPI_PLUGIN_IS_V3(be->be_database)) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't perform an backup with pre-V3 "
+ "backend plugin %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+ task->task_work = 1;
+ task->task_progress = 0;
+
+ mypb = slapi_pblock_new();
+ if (mypb == NULL) {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ mypb->pb_seq_val = slapi_ch_strdup(archive_dir);
+ mypb->pb_plugin = be->be_database;
+ mypb->pb_task = task;
+ mypb->pb_task_flags = TASK_RUNNING_AS_TASK;
+
+ /* start the backup as a separate thread */
+ thread = PR_CreateThread(PR_USER_THREAD, task_backup_thread,
+ (void *)mypb, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (thread == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "unable to create backup thread!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ slapi_ch_free((void **)&mypb->pb_seq_val);
+ slapi_pblock_destroy(mypb);
+ goto out;
+ }
+
+ /* thread successful -- don't free the pb, let the thread do that. */
+ return SLAPI_DSE_CALLBACK_OK;
+
+out:
+ if (task) {
+ destroy_task(1, task);
+ }
+ return rv;
+}
+
+
+static void task_restore_thread(void *arg)
+{
+ Slapi_PBlock *pb = (Slapi_PBlock *)arg;
+ Slapi_Task *task = pb->pb_task;
+ int rv;
+
+ task->task_work = 1;
+ task->task_progress = 0;
+ task->task_state = SLAPI_TASK_RUNNING;
+ slapi_task_status_changed(task);
+
+ slapi_task_log_notice(task, "Beginning restore to '%s'",
+ pb->pb_plugin->plg_name);
+ LDAPDebug(LDAP_DEBUG_ANY, "Beginning restore to '%s'\n",
+ pb->pb_plugin->plg_name, 0, 0);
+
+ rv = (*pb->pb_plugin->plg_archive2db)(pb);
+ if (rv != 0) {
+ slapi_task_log_notice(task, "Restore failed (error %d)", rv);
+ slapi_task_log_status(task, "Restore failed (error %d)", rv);
+ LDAPDebug(LDAP_DEBUG_ANY, "Restore failed (error %d)\n", rv, 0, 0);
+ } else {
+ slapi_task_log_notice(task, "Restore finished.");
+ slapi_task_log_status(task, "Restore finished.");
+ LDAPDebug(LDAP_DEBUG_ANY, "Restore finished.\n", 0, 0, 0);
+ }
+
+ task->task_progress = 1;
+ task->task_exitcode = rv;
+ task->task_state = SLAPI_TASK_FINISHED;
+ slapi_task_status_changed(task);
+
+ slapi_ch_free((void **)&pb->pb_seq_val);
+ slapi_pblock_destroy(pb);
+}
+
+static int task_restore_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ Slapi_Backend *be = NULL;
+ const char *cn;
+ const char *archive_dir = NULL;
+ const char *my_database_type = NULL;
+ const char *database_type = "ldbm database";
+ char *cookie = NULL;
+ int rv = SLAPI_DSE_CALLBACK_OK;
+ Slapi_PBlock *mypb = NULL;
+ Slapi_Task *task = NULL;
+ PRThread *thread = NULL;
+
+ *returncode = LDAP_SUCCESS;
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* archive dir name */
+ if ((archive_dir = fetch_attr(e, "nsArchiveDir", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* database type */
+ my_database_type = fetch_attr(e, "nsDatabaseType", NULL);
+ if (NULL != my_database_type)
+ database_type = my_database_type;
+
+ /* get backend that has archive2db and the database type matches. */
+ cookie = NULL;
+ be = slapi_get_first_backend (&cookie);
+ while (be) {
+ if (NULL != be->be_database->plg_archive2db &&
+ !strcasecmp(database_type, be->be_database->plg_name))
+ break;
+
+ be = (backend *)slapi_get_next_backend (cookie);
+ }
+ if (NULL == be || NULL == be->be_database->plg_archive2db) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: no db2archive function defined.\n", 0, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* refuse to do an export on pre-V3 plugins. plugin api V3 is the one
+ * for DS 5.0 where the import/export stuff changed a lot.
+ */
+ if (! SLAPI_PLUGIN_IS_V3(be->be_database)) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't perform an restore with pre-V3 "
+ "backend plugin %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+ task->task_work = 1;
+ task->task_progress = 0;
+
+ mypb = slapi_pblock_new();
+ if (mypb == NULL) {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ mypb->pb_seq_val = slapi_ch_strdup(archive_dir);
+ mypb->pb_plugin = be->be_database;
+ mypb->pb_task = task;
+ mypb->pb_task_flags = TASK_RUNNING_AS_TASK;
+
+ /* start the restore as a separate thread */
+ thread = PR_CreateThread(PR_USER_THREAD, task_restore_thread,
+ (void *)mypb, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (thread == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "unable to create restore thread!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ slapi_ch_free((void **)&mypb->pb_seq_val);
+ slapi_pblock_destroy(mypb);
+ goto out;
+ }
+
+ /* thread successful -- don't free the pb, let the thread do that. */
+ return SLAPI_DSE_CALLBACK_OK;
+
+out:
+ if (task) {
+ destroy_task(1, task);
+ }
+ return rv;
+}
+
+
+static void task_index_thread(void *arg)
+{
+ Slapi_PBlock *pb = (Slapi_PBlock *)arg;
+ Slapi_Task *task = pb->pb_task;
+ int rv;
+
+ task->task_work = 1;
+ task->task_progress = 0;
+ task->task_state = SLAPI_TASK_RUNNING;
+ slapi_task_status_changed(task);
+
+ rv = (*pb->pb_plugin->plg_db2index)(pb);
+ if (rv != 0) {
+ slapi_task_log_notice(task, "Index failed (error %d)", rv);
+ slapi_task_log_status(task, "Index failed (error %d)", rv);
+ LDAPDebug(LDAP_DEBUG_ANY, "Index failed (error %d)\n", rv, 0, 0);
+ }
+
+ task->task_progress = task->task_work;
+ task->task_exitcode = rv;
+ task->task_state = SLAPI_TASK_FINISHED;
+ slapi_task_status_changed(task);
+
+ charray_free(pb->pb_db2index_attrs);
+ slapi_ch_free((void **)&pb->pb_instance_name);
+ slapi_pblock_destroy(pb);
+}
+
+static int task_index_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
+{
+ const char *instance_name;
+ const char *cn;
+ int rv = SLAPI_DSE_CALLBACK_OK;
+ Slapi_Backend *be = NULL;
+ Slapi_Task *task = NULL;
+ Slapi_Attr *attr;
+ Slapi_Value *val = NULL;
+ char **indexlist = NULL;
+ int idx;
+ Slapi_PBlock *mypb = NULL;
+ PRThread *thread = NULL;
+
+ *returncode = LDAP_SUCCESS;
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ if ((instance_name = fetch_attr(e, "nsInstance", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* lookup the backend */
+ be = slapi_be_select_by_instance_name(instance_name);
+ if (be == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "can't import to nonexistent backend %s\n",
+ instance_name, 0, 0);
+ *returncode = LDAP_NO_SUCH_OBJECT;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ if (be->be_database->plg_db2index == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "ERROR: no db2index function defined for "
+ "backend %s\n", be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* normal indexes */
+ if (slapi_entry_attr_find(e, "nsIndexAttribute", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ const char *indexname = slapi_value_get_string(val);
+ char *index = (char *)slapi_ch_malloc(strlen(indexname) + 2);
+
+ if (index != NULL) {
+ sprintf(index, "t%s", indexname);
+ charray_add(&indexlist, index);
+ }
+ }
+ }
+
+ /* vlv indexes */
+ if (slapi_entry_attr_find(e, "nsIndexVlvAttribute", &attr) == 0) {
+ for (idx = slapi_attr_first_value(attr, &val);
+ idx >= 0; idx = slapi_attr_next_value(attr, idx, &val)) {
+ const char *indexname = slapi_value_get_string(val);
+ char *index = (char *)slapi_ch_malloc(strlen(indexname) + 2);
+
+ if (index != NULL) {
+ sprintf(index, "T%s", indexname);
+ charray_add(&indexlist, index);
+ }
+ }
+ }
+
+ if (NULL == indexlist) {
+ LDAPDebug(LDAP_DEBUG_ANY, "no index is specified!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_OK;
+ goto out;
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+ task->task_work = 1;
+ task->task_progress = 0;
+
+ mypb = slapi_pblock_new();
+ if (mypb == NULL) {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ mypb->pb_backend = be;
+ mypb->pb_plugin = be->be_database;
+ mypb->pb_instance_name = slapi_ch_strdup(instance_name);
+ mypb->pb_db2index_attrs = indexlist;
+ mypb->pb_task = task;
+ mypb->pb_task_flags = TASK_RUNNING_AS_TASK;
+
+ /* start the db2index as a separate thread */
+ thread = PR_CreateThread(PR_USER_THREAD, task_index_thread,
+ (void *)mypb, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (thread == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "unable to create index thread!\n", 0, 0, 0);
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ slapi_ch_free((void **)&mypb->pb_instance_name);
+ slapi_pblock_destroy(mypb);
+ goto out;
+ }
+
+ /* thread successful -- don't free the pb, let the thread do that. */
+ return SLAPI_DSE_CALLBACK_OK;
+
+out:
+ if (task) {
+ destroy_task(1, task);
+ }
+ if (indexlist) {
+ charray_free(indexlist);
+ }
+ return rv;
+}
+
+#if defined(UPGRADEDB)
+static int
+task_upgradedb_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ const char *cn;
+ int rv = SLAPI_DSE_CALLBACK_OK;
+ Slapi_Backend *be = NULL;
+ Slapi_Task *task = NULL;
+ Slapi_PBlock mypb;
+ PRThread *thread = NULL;
+ const char *archive_dir = NULL;
+ const char *force = NULL;
+ const char *database_type = "ldbm database";
+ const char *my_database_type = NULL;
+ char *cookie = NULL;
+
+ *returncode = LDAP_SUCCESS;
+ if ((cn = fetch_attr(e, "cn", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* archive dir name */
+ if ((archive_dir = fetch_attr(e, "nsArchiveDir", NULL)) == NULL) {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* database type */
+ my_database_type = fetch_attr(e, "nsDatabaseType", NULL);
+ if (NULL != my_database_type)
+ database_type = my_database_type;
+
+ /* force to reindex? */
+ force = fetch_attr(e, "nsForceToReindex", NULL);
+
+ /* get backend that has db2archive and the database type matches. */
+ cookie = NULL;
+ be = slapi_get_first_backend(&cookie);
+ while (be) {
+ if (NULL != be->be_database->plg_upgradedb)
+ break;
+
+ be = (backend *)slapi_get_next_backend (cookie);
+ }
+ if (NULL == be || NULL == be->be_database->plg_upgradedb ||
+ strcasecmp(database_type, be->be_database->plg_name)) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: no upgradedb is defined in %s.\n",
+ be->be_database->plg_name, 0, 0);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* allocate new task now */
+ task = new_task(slapi_entry_get_ndn(e));
+ if (task == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to allocate new task!\n", 0, 0, 0);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ task->task_state = SLAPI_TASK_SETUP;
+ task->task_work = 1;
+ task->task_progress = 0;
+
+ memset(&mypb, 0, sizeof(mypb));
+ mypb.pb_backend = be;
+ mypb.pb_plugin = be->be_database;
+ if (force && 0 == strcasecmp(force, "true"))
+ mypb.pb_seq_type = SLAPI_UPGRADEDB_FORCE; /* force; reindex all regardless the dbversion */
+ mypb.pb_seq_val = slapi_ch_strdup(archive_dir);
+ mypb.pb_task = task;
+ mypb.pb_task_flags = TASK_RUNNING_AS_TASK;
+
+ rv = (mypb.pb_plugin->plg_upgradedb)(&mypb);
+ if (rv == 0) {
+ slapi_entry_attr_set_charptr(e, TASK_LOG_NAME, "");
+ slapi_entry_attr_set_charptr(e, TASK_STATUS_NAME, "");
+ slapi_entry_attr_set_int(e, TASK_PROGRESS_NAME, task->task_progress);
+ slapi_entry_attr_set_int(e, TASK_WORK_NAME, task->task_work);
+ }
+
+out:
+ if (rv != 0) {
+ if (task)
+ destroy_task(1, task);
+
+ *returncode = LDAP_OPERATIONS_ERROR;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+#endif
+
+/* update attributes in the entry under "cn=tasks" to match the current
+ * status of the task.
+ */
+#define NEXTMOD(_type, _val) do { \
+ modlist[cur].mod_op = LDAP_MOD_REPLACE; \
+ modlist[cur].mod_type = (_type); \
+ modlist[cur].mod_values = (char **)slapi_ch_malloc(2*sizeof(char *)); \
+ modlist[cur].mod_values[0] = (_val); \
+ modlist[cur].mod_values[1] = NULL; \
+ mod[cur] = &modlist[cur]; \
+ cur++; \
+} while (0)
+void slapi_task_status_changed(Slapi_Task *task)
+{
+ LDAPMod modlist[20];
+ LDAPMod *mod[20];
+ int cur = 0, i;
+ char s1[20], s2[20], s3[20];
+
+ if (shutting_down) {
+ /* don't care about task status updates anymore */
+ return;
+ }
+
+ NEXTMOD(TASK_LOG_NAME, task->task_log);
+ NEXTMOD(TASK_STATUS_NAME, task->task_status);
+ sprintf(s1, "%d", task->task_exitcode);
+ sprintf(s2, "%d", task->task_progress);
+ sprintf(s3, "%d", task->task_work);
+ NEXTMOD(TASK_PROGRESS_NAME, s2);
+ NEXTMOD(TASK_WORK_NAME, s3);
+ /* only add the exit code when the job is done */
+ if ((task->task_state == SLAPI_TASK_FINISHED) ||
+ (task->task_state == SLAPI_TASK_CANCELLED)) {
+ NEXTMOD(TASK_EXITCODE_NAME, s1);
+ /* make sure the console can tell the task has ended */
+ if (task->task_progress != task->task_work) {
+ task->task_progress = task->task_work;
+ }
+ }
+
+ mod[cur] = NULL;
+ modify_internal_entry(task->task_dn, mod);
+
+ for (i = 0; i < cur; i++)
+ slapi_ch_free((void **)&modlist[i].mod_values);
+
+ if ((task->task_state == SLAPI_TASK_FINISHED) &&
+ !(task->task_flags & SLAPI_TASK_DESTROYING)) {
+ /* queue an event to destroy the state info */
+ Slapi_Eq_Context event;
+ Slapi_PBlock *pb = slapi_pblock_new();
+ Slapi_Entry *e;
+ int ttl;
+ time_t expire;
+
+ e = get_internal_entry(pb, task->task_dn);
+ if (e == NULL)
+ return;
+ ttl = atoi(fetch_attr(e, "ttl", DEFAULT_TTL));
+ if (ttl > 3600)
+ ttl = 3600; /* be reasonable. */
+ expire = time(NULL) + ttl;
+ task->task_flags |= SLAPI_TASK_DESTROYING;
+ event = slapi_eq_once(destroy_task, (void *)task, expire);
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+ }
+}
+
+
+/* cleanup old tasks that may still be in the DSE from a previous session
+ * (this can happen if the server crashes [no matter how unlikely we like
+ * to think that is].)
+ */
+static void cleanup_old_tasks(void)
+{
+ Slapi_PBlock *pb = slapi_pblock_new();
+ Slapi_Entry **entries = NULL;
+ int ret = 0, i, x;
+ Slapi_DN *rootDN;
+
+ slapi_search_internal_set_pb(pb, TASK_BASE_DN, LDAP_SCOPE_SUBTREE,
+ "(objectclass=*)", NULL, 0, NULL, NULL,
+ (void *)plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+ if (ret != LDAP_SUCCESS) {
+ LDAPDebug(LDAP_DEBUG_ANY, "WARNING: entire cn=tasks tree seems to "
+ "be AWOL!\n", 0, 0, 0);
+ slapi_pblock_destroy(pb);
+ return;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries) {
+ LDAPDebug(LDAP_DEBUG_ANY, "WARNING: entire cn=tasks tree seems to "
+ "be AWOL!\n", 0, 0, 0);
+ slapi_pblock_destroy(pb);
+ return;
+ }
+
+ rootDN = slapi_sdn_new_dn_byval(TASK_BASE_DN);
+
+ /* rotate through entries, skipping the base dn */
+ for (i = 0; entries[i] != NULL; i++) {
+ const Slapi_DN *sdn = slapi_entry_get_sdn_const(entries[i]);
+ Slapi_PBlock *mypb;
+ Slapi_Operation *op;
+
+ if (slapi_sdn_compare(sdn, rootDN) == 0)
+ continue;
+
+ mypb = slapi_pblock_new();
+ if (mypb == NULL) {
+ continue;
+ }
+ slapi_delete_internal_set_pb(mypb, slapi_sdn_get_dn(sdn), NULL, NULL,
+ plugin_get_default_component_id(), 0);
+
+ /* Make sure these deletes don't appear in the audit and change logs */
+ slapi_pblock_get(mypb, SLAPI_OPERATION, &op);
+ operation_set_flag(op, OP_FLAG_ACTION_NOLOG);
+
+ x = 1;
+ slapi_pblock_set(mypb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &x);
+ slapi_delete_internal_pb(mypb);
+ slapi_pblock_destroy(mypb);
+ }
+
+ slapi_sdn_free(&rootDN);
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+}
+
+/* name is, for exmaple, "import" */
+int slapi_task_register_handler(const char *name, dseCallbackFn func)
+{
+ char *dn = NULL;
+ Slapi_PBlock *pb = NULL;
+ Slapi_Operation *op;
+ LDAPMod *mods[3];
+ LDAPMod mod[3];
+ const char *objectclass[3];
+ const char *cnvals[2];
+ int ret = -1;
+ int x;
+
+ dn = slapi_ch_malloc(strlen(name) + strlen(TASK_BASE_DN) + 20);
+ if (dn == NULL) {
+ goto out;
+ }
+ sprintf(dn, "cn=%s, %s", name, TASK_BASE_DN);
+
+ pb = slapi_pblock_new();
+ if (pb == NULL) {
+ goto out;
+ }
+
+ /* this is painful :( */
+ mods[0] = &mod[0];
+ mod[0].mod_op = LDAP_MOD_ADD;
+ mod[0].mod_type = "objectClass";
+ mod[0].mod_values = (char **)objectclass;
+ objectclass[0] = "top";
+ objectclass[1] = "extensibleObject";
+ objectclass[2] = NULL;
+ mods[1] = &mod[1];
+ mod[1].mod_op = LDAP_MOD_ADD;
+ mod[1].mod_type = "cn";
+ mod[1].mod_values = (char **)cnvals;
+ cnvals[0] = name;
+ cnvals[1] = NULL;
+ mods[2] = NULL;
+ slapi_add_internal_set_pb(pb, dn, mods, NULL,
+ plugin_get_default_component_id(), 0);
+ x = 1;
+ slapi_pblock_set(pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &x);
+ /* Make sure these adds don't appear in the audit and change logs */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ operation_set_flag(op, OP_FLAG_ACTION_NOLOG);
+
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &x);
+ if ((x != LDAP_SUCCESS) && (x != LDAP_ALREADY_EXISTS)) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "Can't create task node '%s' (error %d)\n",
+ name, x, 0);
+ ret = x;
+ goto out;
+ }
+
+ /* register add callback */
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP,
+ dn, LDAP_SCOPE_SUBTREE, "(objectclass=*)", func, NULL);
+ /* deny modify/delete of the root task entry */
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP,
+ dn, LDAP_SCOPE_BASE, "(objectclass=*)", task_deny, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP,
+ dn, LDAP_SCOPE_BASE, "(objectclass=*)", task_deny, NULL);
+
+ ret = 0;
+
+out:
+ if (dn) {
+ slapi_ch_free((void **)&dn);
+ }
+ if (pb) {
+ slapi_pblock_destroy(pb);
+ }
+ return ret;
+}
+
+
+void task_init(void)
+{
+ global_task_lock = PR_NewLock();
+ if (global_task_lock == NULL) {
+ LDAPDebug(LDAP_DEBUG_ANY, "unable to create global tasks lock! "
+ "(that's bad)\n", 0, 0, 0);
+ return;
+ }
+
+ cleanup_old_tasks();
+
+ slapi_task_register_handler("import", task_import_add);
+ slapi_task_register_handler("export", task_export_add);
+ slapi_task_register_handler("backup", task_backup_add);
+ slapi_task_register_handler("restore", task_restore_add);
+ slapi_task_register_handler("index", task_index_add);
+#if defined(UPGRADEDB)
+ slapi_task_register_handler("upgradedb", task_upgradedb_add);
+#endif
+}
+
+/* called when the server is shutting down -- abort all existing tasks */
+void task_shutdown(void)
+{
+ Slapi_Task *task;
+ int found_any = 0;
+
+ /* first, cancel all tasks */
+ PR_Lock(global_task_lock);
+ shutting_down = 1;
+ for (task = global_task_list; task; task = task->next) {
+ if ((task->task_state != SLAPI_TASK_CANCELLED) &&
+ (task->task_state != SLAPI_TASK_FINISHED)) {
+ task->task_state = SLAPI_TASK_CANCELLED;
+ if (task->cancel) {
+ LDAPDebug(LDAP_DEBUG_ANY, "Cancelling task '%s'\n",
+ task->task_dn, 0, 0);
+ (*task->cancel)(task);
+ found_any = 1;
+ }
+ }
+ }
+
+ if (found_any) {
+ /* give any tasks 1 second to say their last rites */
+ DS_Sleep(PR_SecondsToInterval( 1 ));
+ }
+
+ while (global_task_list) {
+ destroy_task(0, global_task_list);
+ }
+ PR_Unlock(global_task_lock);
+}