X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fmigrate%2Fserv_migrate.c;h=31815f485903d0c76cb945c472640f6f9ff85edb;hb=068d33e5d8569b2c4a2c8582178427892b0a8dee;hp=3f9ee3a495848d50afb128c51eac1f7b8be7fe12;hpb=cbc736c6fd45401b653454914a1b437050600a00;p=citadel.git diff --git a/citadel/modules/migrate/serv_migrate.c b/citadel/modules/migrate/serv_migrate.c index 3f9ee3a49..31815f485 100644 --- a/citadel/modules/migrate/serv_migrate.c +++ b/citadel/modules/migrate/serv_migrate.c @@ -1,29 +1,28 @@ -/* - * This module dumps and/or loads the Citadel database in XML format. - * - * Copyright (c) 1987-2015 by the citadel.org team - * - * This program is open source software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3. - * - * 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. - */ - -/* - * Explanation of tags: - * - * 0% nothing - * 2% finished exporting configuration - * 7% finished exporting users - * 12% finished exporting openids - * 17% finished exporting rooms - * 18% finished exporting floors - * 25% finished exporting visits - * 100% finished exporting messages - */ +// This module dumps and/or loads the Citadel database in XML format. +// +// Copyright (c) 1987-2021 by the citadel.org team +// +// This program is open source software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 3. +// +// 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. +// +// Explanation of tags: +// +// 0% started +// 2% finished exporting configuration +// 7% finished exporting users +// 12% finished exporting openids +// 17% finished exporting rooms +// 18% finished exporting floors +// 25% finished exporting visits +// 26-99% exporting messages +// 100% finished exporting messages +// +// These tags are inserted into the XML stream to give the reader an approximation of its progress. #include "sysdep.h" #include @@ -34,18 +33,7 @@ #include #include #include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - +#include #include #include #include @@ -63,20 +51,17 @@ #include "euidindex.h" #include "ctdl_module.h" -#define END_OF_MESSAGE "---eom---dbd---" - char migr_tempfilename1[PATH_MAX]; char migr_tempfilename2[PATH_MAX]; FILE *migr_global_message_list; int total_msgs = 0; +char *ikey = NULL; // If we're importing a config key we store it here. -/* - * Code which implements the export appears in this section - */ +//***************************************************************************** +//* Code which implements the export appears in this section * +//***************************************************************************** -/* - * Output a string to the client with these characters escaped: & < > - */ +// Output a string to the client with these characters escaped: & < > " ' void xml_strout(char *str) { char *c = str; @@ -87,19 +72,19 @@ void xml_strout(char *str) { while (*c != 0) { if (*c == '\"') { - client_write(""", 6); + client_write(HKEY(""")); } else if (*c == '\'') { - client_write("'", 6); + client_write(HKEY("'")); } else if (*c == '<') { - client_write("<", 4); + client_write(HKEY("<")); } else if (*c == '>') { - client_write(">", 4); + client_write(HKEY(">")); } else if (*c == '&') { - client_write("&", 5); + client_write(HKEY("&")); } else { client_write(c, 1); @@ -109,23 +94,32 @@ void xml_strout(char *str) { } -/* - * Export a user record as XML - */ -void migr_export_users_backend(struct ctdluser *buf, void *data) { - client_write("\n", 7); - cprintf("%d\n", buf->version); - cprintf("%ld\n", (long)buf->uid); - client_write("", 12); xml_strout(buf->password); client_write("\n", 14); - cprintf("%u\n", buf->flags); - cprintf("%ld\n", buf->timescalled); - cprintf("%ld\n", buf->posted); - cprintf("%d\n", buf->axlevel); - cprintf("%ld\n", buf->usernum); - cprintf("%ld\n", (long)buf->lastcall); - cprintf("%d\n", buf->USuserpurge); - client_write("", 12); xml_strout(buf->fullname); client_write("\n", 14); - client_write("\n", 8); +// Export a user record as XML +void migr_export_users_backend(char *username, void *data) { + + struct ctdluser u; + if (CtdlGetUser(&u, username) != 0) { + return; + } + + client_write(HKEY("\n")); + cprintf("%d\n", u.version); + cprintf("%ld\n", (long)u.uid); + client_write(HKEY("")); xml_strout(u.password); client_write(HKEY("\n")); + cprintf("%u\n", u.flags); + cprintf("%ld\n", u.timescalled); + cprintf("%ld\n", u.posted); + cprintf("%d\n", u.axlevel); + cprintf("%ld\n", u.usernum); + cprintf("%ld\n", (long)u.lastcall); + cprintf("%d\n", u.USuserpurge); + client_write(HKEY("")); xml_strout(u.fullname); client_write(HKEY("\n")); + cprintf("%ld\n", u.msgnum_bio); + cprintf("%ld\n", u.msgnum_pic); + cprintf("%s\n", u.emailaddrs); + cprintf("%ld\n", u.msgnum_inboxrules); + cprintf("%ld\n", u.lastproc_inboxrules); + client_write(HKEY("\n")); } @@ -135,25 +129,33 @@ void migr_export_users(void) { void migr_export_room_msg(long msgnum, void *userdata) { - cprintf("%ld\n", msgnum); + static int count = 0; + + cprintf("%ld,", msgnum); + if (++count%10==0) { + cprintf("\n"); + } fprintf(migr_global_message_list, "%ld\n", msgnum); } void migr_export_rooms_backend(struct ctdlroom *buf, void *data) { - client_write("\n", 7); - client_write("", 8); xml_strout(buf->QRname); client_write("\n", 10); - client_write("", 10); xml_strout(buf->QRpasswd); client_write("\n", 12); + client_write(HKEY("\n")); + client_write(HKEY("")); + xml_strout(buf->QRname); + client_write(HKEY("\n")); + client_write(HKEY("")); + xml_strout(buf->QRpasswd); + client_write(HKEY("\n")); cprintf("%ld\n", buf->QRroomaide); cprintf("%ld\n", buf->QRhighest); cprintf("%ld\n", (long)buf->QRgen); cprintf("%u\n", buf->QRflags); if (buf->QRflags & QR_DIRECTORY) { - client_write("", 11); + client_write(HKEY("")); xml_strout(buf->QRdirname); - client_write("\n", 13); + client_write(HKEY("\n")); } - cprintf("%ld\n", buf->QRinfo); cprintf("%d\n", buf->QRfloor); cprintf("%ld\n", (long)buf->QRmtime); cprintf("%d\n", buf->QRep.expire_mode); @@ -162,19 +164,20 @@ void migr_export_rooms_backend(struct ctdlroom *buf, void *data) { cprintf("%d\n", buf->QRorder); cprintf("%u\n", buf->QRflags2); cprintf("%d\n", buf->QRdefaultview); - client_write("\n", 8); - - /* message list goes inside this tag */ + cprintf("%ld\n", buf->msgnum_info); + cprintf("%ld\n", buf->msgnum_pic); + client_write(HKEY("\n")); + // message list goes inside this tag CtdlGetRoom(&CC->room, buf->QRname); - client_write("", 15); - client_write("", 8); xml_strout(CC->room.QRname); client_write("\n", 10); - client_write("", 11); + client_write(HKEY("")); + client_write(HKEY("")); + xml_strout(buf->QRname); // buf->QRname rather than CC->room.QRname to guarantee consistency + client_write(HKEY("\n")); + client_write(HKEY("\n")); CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL); - client_write("\n", 13); - client_write("\n", 17); - - + client_write(HKEY("\n")); + client_write(HKEY("\n")); } @@ -186,17 +189,18 @@ void migr_export_rooms(void) { fclose(migr_global_message_list); } - /* - * Process the 'global' message list. (Sort it and remove dups. - * Dups are ok because a message may be in more than one room, but - * this will be handled by exporting the reference count, not by - * exporting the message multiple times.) - */ + // Process the 'global' message list. (Sort it and remove dups. + // Dups are ok because a message may be in more than one room, but + // this will be handled by exporting the reference count, not by + // exporting the message multiple times.) snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2); - if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno); + if (system(cmd) != 0) { + syslog(LOG_ERR, "migrate: error %d", errno); + } snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1); - if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno); - + if (system(cmd) != 0) { + syslog(LOG_ERR, "migrate: error %d", errno); + } snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1); FILE *fp = popen(cmd, "r"); @@ -208,7 +212,7 @@ void migr_export_rooms(void) { else { total_msgs = 1; // any nonzero just to keep it from barfing } - syslog(LOG_DEBUG, "Total messages to be exported: %d", total_msgs); + syslog(LOG_DEBUG, "migrate: total messages to be exported: %d", total_msgs); } @@ -217,23 +221,21 @@ void migr_export_floors(void) { int i; for (i=0; i < MAXFLOORS; ++i) { - client_write("\n", 8); + client_write(HKEY("\n")); cprintf("%d\n", i); CtdlGetFloor(&qfbuf, i); buf = &qfbuf; cprintf("%u\n", buf->f_flags); - client_write("", 8); xml_strout(buf->f_name); client_write("\n", 10); + client_write(HKEY("")); xml_strout(buf->f_name); client_write(HKEY("\n")); cprintf("%d\n", buf->f_ref_count); cprintf("%d\n", buf->f_ep.expire_mode); cprintf("%d\n", buf->f_ep.expire_value); - client_write("\n", 9); + client_write(HKEY("\n")); } } -/* - * Return nonzero if the supplied string contains only characters which are valid in a sequence set. - */ +// Return nonzero if the supplied string contains only characters which are valid in a sequence set. int is_sequence_set(char *s) { if (!s) return(0); @@ -248,10 +250,7 @@ int is_sequence_set(char *s) { } - -/* - * Traverse the visits file... - */ +// Traverse the visits file... void migr_export_visits(void) { visit vbuf; struct cdbdata *cdbv; @@ -265,29 +264,29 @@ void migr_export_visits(void) { sizeof(visit) : cdbv->len)); cdb_free(cdbv); - client_write("\n", 8); + client_write(HKEY("\n")); cprintf("%ld\n", vbuf.v_roomnum); cprintf("%ld\n", vbuf.v_roomgen); cprintf("%ld\n", vbuf.v_usernum); - client_write("", 8); + client_write(HKEY("")); if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) { xml_strout(vbuf.v_seen); } else { cprintf("%ld", vbuf.v_lastseen); } - client_write("", 9); + client_write(HKEY("")); if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) { - client_write("", 12); + client_write(HKEY("")); xml_strout(vbuf.v_answered); - client_write("\n", 14); + client_write(HKEY("\n")); } cprintf("%u\n", vbuf.v_flags); cprintf("%d\n", vbuf.v_view); - client_write("\n", 9); + client_write(HKEY("\n")); } } @@ -296,12 +295,14 @@ void migr_export_message(long msgnum) { struct MetaData smi; struct CtdlMessage *msg; struct ser_ret smr; + long bytes_written = 0; + long this_block = 0; + + // We can use a static buffer here because there will never be more than + // one of this operation happening at any given time, and it's really best + // to just keep it allocated once instead of torturing malloc/free. + // Call this function with msgnum "-1" to free the buffer when finished. - /* We can use a static buffer here because there will never be more than - * one of this operation happening at any given time, and it's really best - * to just keep it allocated once instead of torturing malloc/free. - * Call this function with msgnum "-1" to free the buffer when finished. - */ static int encoded_alloc = 0; static char *encoded_msg = NULL; @@ -314,22 +315,23 @@ void migr_export_message(long msgnum) { return; } - /* Ok, here we go ... */ + // Ok, here we go ... msg = CtdlFetchMessage(msgnum, 1); - if (msg == NULL) return; /* fail silently */ + if (msg == NULL) return; // fail silently - client_write("\n", 10); + client_write(HKEY("\n")); GetMetaData(&smi, msgnum); cprintf("%ld\n", msgnum); cprintf("%d\n", smi.meta_refcount); - client_write("", 23); xml_strout(smi.meta_content_type); client_write("\n", 25); + cprintf("%ld\n", smi.meta_rfc822_length); + client_write(HKEY("")); xml_strout(smi.meta_content_type); client_write(HKEY("\n")); - client_write("", 10); + client_write(HKEY("")); CtdlSerializeMessage(&smr, msg); CM_Free(msg); - /* Predict the buffer size we need. Expand the buffer if necessary. */ + // Predict the buffer size we need. Expand the buffer if necessary. int encoded_len = smr.len * 15 / 10 ; if (encoded_len > encoded_alloc) { encoded_alloc = encoded_len; @@ -337,39 +339,49 @@ void migr_export_message(long msgnum) { } if (encoded_msg == NULL) { - /* Questionable hack that hopes it'll work next time and we only lose one message */ + // Questionable hack that hopes it'll work next time and we only lose one message encoded_alloc = 0; } else { - /* Once we do the encoding we know the exact size */ + // Once we do the encoding we know the exact size encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1); - client_write(encoded_msg, encoded_len); + + // Careful now. If the message is gargantuan, trying to write multiple gigamegs in one + // big write operation can make our transport unhappy. So we'll chunk it up 10 KB at a time. + bytes_written = 0; + while ( (bytes_written < encoded_len) && (!server_shutting_down) ) { + this_block = encoded_len - bytes_written; + if (this_block > 10240) { + this_block = 10240; + } + client_write(&encoded_msg[bytes_written], this_block); + bytes_written += this_block; + } } free(smr.ser); - client_write("\n", 12); - client_write("\n", 11); + client_write(HKEY("\n")); + client_write(HKEY("\n")); } - void migr_export_openids(void) { struct cdbdata *cdboi; long usernum; char url[512]; - cdb_rewind(CDB_OPENID); - while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { + cdb_rewind(CDB_EXTAUTH); + while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) { if (cdboi->len > sizeof(long)) { - client_write("\n", 9); + client_write(HKEY("\n")); memcpy(&usernum, cdboi->ptr, sizeof(long)); snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) ); - client_write("", 9); + client_write(HKEY("")); xml_strout(url); - client_write("\n", 11); + client_write(HKEY("\n")); cprintf("%ld\n", usernum); - client_write("\n", 10); + client_write(HKEY("\n")); } cdb_free(cdboi); } @@ -399,8 +411,6 @@ void migr_export_configs(void) { } - - void migr_export_messages(void) { char buf[SIZ]; long msgnum; @@ -412,7 +422,7 @@ void migr_export_messages(void) { Ctx = CC; migr_global_message_list = fopen(migr_tempfilename1, "r"); if (migr_global_message_list != NULL) { - syslog(LOG_INFO, "Opened %s\n", migr_tempfilename1); + syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1); while ((Ctx->kill_me == 0) && (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) { msgnum = atol(buf); @@ -428,16 +438,17 @@ void migr_export_messages(void) { } fclose(migr_global_message_list); } - if (Ctx->kill_me == 0) - syslog(LOG_INFO, "Exported %d messages.\n", count); - else - syslog(LOG_ERR, "Export aborted due to client disconnect! \n"); + if (Ctx->kill_me == 0) { + syslog(LOG_INFO, "migrate: exported %d messages.", count); + } + else { + syslog(LOG_ERR, "migrate: export aborted due to client disconnect!"); + } - migr_export_message(-1L); /* This frees the encoding buffer */ + migr_export_message(-1L); // This frees the encoding buffer } - void migr_do_export(void) { CitContext *Ctx; @@ -445,12 +456,12 @@ void migr_do_export(void) { cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS); Ctx->dont_term = 1; - client_write("\n", 40); - client_write("\n", 23); + client_write(HKEY("\n")); + client_write(HKEY("\n")); cprintf("%d\n", REV_LEVEL); cprintf("%d\n", 0); - /* export the configuration database */ + // export the configuration database migr_export_configs(); cprintf("%d\n", 2); @@ -465,25 +476,21 @@ void migr_do_export(void) { if (Ctx->kill_me == 0) migr_export_visits(); cprintf("%d\n", 25); if (Ctx->kill_me == 0) migr_export_messages(); - client_write("\n", 24); cprintf("%d\n", 100); - client_write("000\n", 4); + client_write(HKEY("\n")); + client_write(HKEY("000\n")); Ctx->dont_term = 0; } +// Import code +// Here's the code that implements the import side. It's going to end up +// being one big loop with lots of global variables. I don't care. +// You wouldn't run multiple concurrent imports anyway. If this offends your +// delicate sensibilities then go rewrite it in Ruby on Rails or something. - -/* - * Here's the code that implements the import side. It's going to end up being - * one big loop with lots of global variables. I don't care. You wouldn't run - * multiple concurrent imports anyway. If this offends your delicate sensibilities - * then go rewrite it in Ruby on Rails or something. - */ - - -int citadel_migrate_data = 0; /* Are we inside a tag pair? */ +int citadel_migrate_data = 0; // Are we inside a tag pair? StrBuf *migr_chardata = NULL; StrBuf *migr_MsgData = NULL; struct ctdluser usbuf; @@ -497,11 +504,8 @@ visit vbuf; struct MetaData smi; long import_msgnum = 0; -/* - * This callback stores up the data which appears in between tags. - */ -void migr_xml_chardata(void *data, const XML_Char *s, int len) -{ +// This callback stores up the data which appears in between tags. +void migr_xml_chardata(void *data, const XML_Char *s, int len) { StrBufAppendBufPlain(migr_chardata, s, len, 0); } @@ -509,19 +513,19 @@ void migr_xml_chardata(void *data, const XML_Char *s, int len) void migr_xml_start(void *data, const char *el, const char **attr) { int i; - /*** GENERAL STUFF ***/ + // *** GENERAL STUFF *** - /* Reset chardata_len to zero and init buffer */ + // Reset chardata_len to zero and init buffer FlushStrBuf(migr_chardata); FlushStrBuf(migr_MsgData); if (!strcasecmp(el, "citadel_migrate_data")) { ++citadel_migrate_data; - /* As soon as it looks like the input data is a genuine Citadel XML export, - * whack the existing database on disk to make room for the new one. - */ + // As soon as it looks like the input data is a genuine Citadel XML export, + // whack the existing database on disk to make room for the new one. if (citadel_migrate_data == 1) { + syslog(LOG_INFO, "migrate: erasing existing databases so we can receive the incoming import"); for (i = 0; i < MAXCDB; ++i) { cdb_trunc(i); } @@ -530,13 +534,12 @@ void migr_xml_start(void *data, const char *el, const char **attr) { } if (citadel_migrate_data != 1) { - syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el); + syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el); return; } - /* When we begin receiving XML for one of these record types, clear out the associated - * buffer so we don't accidentally carry over any data from a previous record. - */ + // When we begin receiving XML for one of these record types, clear out the associated + // buffer so we don't accidentally carry over any data from a previous record. if (!strcasecmp(el, "user")) memset(&usbuf, 0, sizeof (struct ctdluser)); else if (!strcasecmp(el, "openid")) memset(openid_url, 0, sizeof openid_url); else if (!strcasecmp(el, "room")) memset(&qrbuf, 0, sizeof (struct ctdlroom)); @@ -549,14 +552,22 @@ void migr_xml_start(void *data, const char *el, const char **attr) { import_msgnum = 0; } else if (!strcasecmp(el, "config")) { - syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG START ELEMENT FIXME\033\0m"); + if (ikey != NULL) { + free(ikey); + ikey = NULL; + } + while (*attr) { + if (!strcasecmp(attr[0], "key")) { + ikey = strdup(attr[1]); + } + attr += 2; + } } } -int migr_userrecord(void *data, const char *el) -{ +int migr_userrecord(void *data, const char *el) { if (!strcasecmp(el, "u_version")) usbuf.version = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_uid")) usbuf.uid = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_password")) safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password); @@ -568,20 +579,24 @@ int migr_userrecord(void *data, const char *el) else if (!strcasecmp(el, "u_lastcall")) usbuf.lastcall = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_USuserpurge")) usbuf.USuserpurge = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname); + else if (!strcasecmp(el, "u_msgnum_bio")) usbuf.msgnum_bio = atol(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "u_msgnum_pic")) usbuf.msgnum_pic = atol(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "u_emailaddrs")) safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs); + else if (!strcasecmp(el, "u_msgnum_inboxrules")) usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "u_lastproc_inboxrules")) usbuf.lastproc_inboxrules = atol(ChrPtr(migr_chardata)); else return 0; return 1; } -int migr_roomrecord(void *data, const char *el) -{ - if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname); + +int migr_roomrecord(void *data, const char *el) { + if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname); else if (!strcasecmp(el, "QRpasswd")) safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd); else if (!strcasecmp(el, "QRroomaide")) qrbuf.QRroomaide = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRhighest")) qrbuf.QRhighest = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRgen")) qrbuf.QRgen = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRflags")) qrbuf.QRflags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRdirname")) safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname); - else if (!strcasecmp(el, "QRinfo")) qrbuf.QRinfo = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRfloor")) qrbuf.QRfloor = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRmtime")) qrbuf.QRmtime = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRexpire_mode")) qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata)); @@ -590,13 +605,15 @@ int migr_roomrecord(void *data, const char *el) else if (!strcasecmp(el, "QRorder")) qrbuf.QRorder = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRflags2")) qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRdefaultview")) qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "msgnum_info")) qrbuf.msgnum_info = atol(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "msgnum_pic")) qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata)); else return 0; return 1; } -int migr_floorrecord(void *data, const char *el) -{ - if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata)); + +int migr_floorrecord(void *data, const char *el) { + if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_flags")) flbuf.f_flags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_name")) safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name); else if (!strcasecmp(el, "f_ref_count")) flbuf.f_ref_count = atoi(ChrPtr(migr_chardata)); @@ -606,8 +623,8 @@ int migr_floorrecord(void *data, const char *el) return 1; } -int migr_visitrecord(void *data, const char *el) -{ + +int migr_visitrecord(void *data, const char *el) { if (!strcasecmp(el, "v_roomnum")) vbuf.v_roomnum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_roomgen")) vbuf.v_roomgen = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_usernum")) vbuf.v_usernum = atol(ChrPtr(migr_chardata)); @@ -634,14 +651,13 @@ int migr_visitrecord(void *data, const char *el) } -void migr_xml_end(void *data, const char *el) -{ +void migr_xml_end(void *data, const char *el) { const char *ptr; int msgcount = 0; long msgnum = 0L; long *msglist = NULL; int msglist_alloc = 0; - /*** GENERAL STUFF ***/ + // *** GENERAL STUFF *** if (!strcasecmp(el, "citadel_migrate_data")) { --citadel_migrate_data; @@ -649,58 +665,67 @@ void migr_xml_end(void *data, const char *el) } if (citadel_migrate_data != 1) { - syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el); + syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el); return; } // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : "")); - /*** CONFIG ***/ + // *** CONFIG *** - if (!strcasecmp(el, "config")) - { - syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG END ELEMENT FIXME\033\0m"); - CtdlSetConfigInt("c_enable_fulltext", 0); /* always disable FIXME put this somewhere more appropriate */ + if (!strcasecmp(el, "config")) { + syslog(LOG_DEBUG, "migrate: imported config key=%s", ikey); + + if (ikey != NULL) { + CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata)); + free(ikey); + ikey = NULL; + } + else { + syslog(LOG_INFO, "migrate: closed a tag but no key name was supplied."); + } } - /*** USER ***/ + // *** USER *** else if ((!strncasecmp(el, HKEY("u_"))) && migr_userrecord(data, el)) ; /* Nothing to do anymore */ else if (!strcasecmp(el, "user")) { CtdlPutUser(&usbuf); - syslog(LOG_INFO, "Imported user: %s\n", usbuf.fullname); + syslog(LOG_INFO, "migrate: imported user: %s", usbuf.fullname); } - /*** OPENID ***/ + // *** OPENID *** else if (!strcasecmp(el, "oid_url")) safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url); else if (!strcasecmp(el, "oid_usernum")) openid_usernum = atol(ChrPtr(migr_chardata)); - else if (!strcasecmp(el, "openid")) { /* see serv_openid_rp.c for a description of the record format */ + else if (!strcasecmp(el, "openid")) { // see serv_openid_rp.c for a description of the record format char *oid_data; int oid_data_len; oid_data_len = sizeof(long) + strlen(openid_url) + 1; oid_data = malloc(oid_data_len); memcpy(oid_data, &openid_usernum, sizeof(long)); memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1); - cdb_store(CDB_OPENID, openid_url, strlen(openid_url), oid_data, oid_data_len); + cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len); free(oid_data); - syslog(LOG_INFO, "Imported OpenID: %s (%ld)\n", openid_url, openid_usernum); + syslog(LOG_INFO, "migrate: imported OpenID: %s (%ld)", openid_url, openid_usernum); } - /*** ROOM ***/ + // *** ROOM *** else if ((!strncasecmp(el, HKEY("QR"))) && migr_roomrecord(data, el)) - ; /* Nothing to do anymore */ + ; // Nothing to do anymore else if (!strcasecmp(el, "room")) { CtdlPutRoom(&qrbuf); - syslog(LOG_INFO, "Imported room: %s\n", qrbuf.QRname); + syslog(LOG_INFO, "migrate: imported room: %s", qrbuf.QRname); } - /*** ROOM MESSAGE POINTERS ***/ + // *** ROOM MESSAGE POINTERS *** - else if (!strcasecmp(el, "FRname")) safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname); + else if (!strcasecmp(el, "FRname")) { + safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname); + } else if (!strcasecmp(el, "FRmsglist")) { if (!IsEmptyStr(FRname)) { @@ -708,7 +733,7 @@ void migr_xml_end(void *data, const char *el) msglist_alloc = 1000; msglist = malloc(sizeof(long) * msglist_alloc); - syslog(LOG_DEBUG, "Message list for: %s\n", FRname); + syslog(LOG_DEBUG, "migrate: message list for: %s", FRname); ptr = ChrPtr(migr_chardata); while (*ptr != 0) { @@ -736,80 +761,85 @@ void migr_xml_end(void *data, const char *el) free(msglist); msglist = NULL; msglist_alloc = 0; - syslog(LOG_DEBUG, "Imported %d messages.\n", msgcount); + syslog(LOG_DEBUG, "migrate: imported %d messages.", msgcount); if (server_shutting_down) { return; } } - /*** FLOORS ***/ + // *** FLOORS *** else if ((!strncasecmp(el, HKEY("f_"))) && migr_floorrecord(data, el)) - ; /* Nothing to do anymore */ + ; // Nothing to do anymore else if (!strcasecmp(el, "floor")) { CtdlPutFloor(&flbuf, floornum); - syslog(LOG_INFO, "Imported floor #%d (%s)\n", floornum, flbuf.f_name); + syslog(LOG_INFO, "migrate: imported floor #%d (%s)", floornum, flbuf.f_name); } - /*** VISITS ***/ - else if ((!strncasecmp(el, HKEY("v_"))) && - migr_visitrecord(data, el)) - ; /* Nothing to do anymore */ + // *** VISITS *** + else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) { + ; // Nothing to do anymore + } else if (!strcasecmp(el, "visit")) { put_visit(&vbuf); - syslog(LOG_INFO, "Imported visit: %ld/%ld/%ld\n", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum); + syslog(LOG_INFO, "migrate: imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum); } - /*** MESSAGES ***/ + // *** MESSAGES *** - else if (!strcasecmp(el, "msg_msgnum")) import_msgnum = atol(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "msg_msgnum")) smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "msg_meta_refcount")) smi.meta_refcount = atoi(ChrPtr(migr_chardata)); + else if (!strcasecmp(el, "msg_meta_rfc822_length")) smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "msg_meta_content_type")) safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type); - else if (!strcasecmp(el, "msg_text")) - { + else if (!strcasecmp(el, "msg_text")) { + long rc; + struct CtdlMessage *msg; FlushStrBuf(migr_MsgData); - StrBufDecodeBase64To(migr_MsgData, migr_MsgData); - - cdb_store(CDB_MSGMAIN, - &import_msgnum, - sizeof(long), - ChrPtr(migr_MsgData), - StrLength(migr_MsgData) + 1); - - smi.meta_msgnum = import_msgnum; - PutMetaData(&smi); + StrBufDecodeBase64To(migr_chardata, migr_MsgData); + + msg = CtdlDeserializeMessage(import_msgnum, -1, + ChrPtr(migr_MsgData), + StrLength(migr_MsgData)); + if (msg != NULL) { + rc = CtdlSaveThisMessage(msg, import_msgnum, 0); + if (rc == 0) { + PutMetaData(&smi); + } + CM_Free(msg); + } + else { + rc = -1; + } syslog(LOG_INFO, - "Imported message #%ld, size=%d, refcount=%d, content-type: %s\n", + "migrate: %s message #%ld, size=%d, ref=%d, body=%ld, content-type: %s", + (rc!= 0)?"failed to import":"imported", import_msgnum, StrLength(migr_MsgData), smi.meta_refcount, - smi.meta_content_type); + smi.meta_rfc822_length, + smi.meta_content_type + ); + memset(&smi, 0, sizeof(smi)); } - /*** MORE GENERAL STUFF ***/ + // *** MORE GENERAL STUFF *** FlushStrBuf(migr_chardata); } - - -/* - * Import begins here - */ +// Import begins here void migr_do_import(void) { - StrBuf *Buf; XML_Parser xp; - int Finished = 0; + char buf[SIZ]; unbuffer_output(); migr_chardata = NewStrBufPlain(NULL, SIZ * 20); migr_MsgData = NewStrBufPlain(NULL, SIZ * 20); - Buf = NewStrBufPlain(NULL, SIZ); xp = XML_ParserCreate(NULL); if (!xp) { cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR); @@ -825,64 +855,232 @@ void migr_do_import(void) { client_set_inbound_buf(SIZ * 10); - while (!Finished && client_read_random_blob(Buf, -1) >= 0) { - if ((StrLength(Buf) > 4) && - !strcmp(ChrPtr(Buf) + StrLength(Buf) - 4, "000\n")) - { - Finished = 1; - StrBufCutAt(Buf, StrLength(Buf) - 4, NULL); - } + while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) { + XML_Parse(xp, buf, strlen(buf), 0); if (server_shutting_down) break; // Should we break or return? - - if (StrLength(Buf) == 0) - continue; - - XML_Parse(xp, ChrPtr(Buf), StrLength(Buf), 0); - FlushStrBuf(Buf); } XML_Parse(xp, "", 0, 1); XML_ParserFree(xp); - FreeStrBuf(&Buf); FreeStrBuf(&migr_chardata); FreeStrBuf(&migr_MsgData); rebuild_euid_index(); rebuild_usersbynumber(); + CtdlSetConfigInt("MM_fulltext_wordbreaker", -1); // Set an invalid wordbreaker to force re-indexing CC->dont_term = 0; } +// ****************************************************************************** +// * Dispatcher, Common code * +// ****************************************************************************** -/* - * Dump out the pathnames of directories which can be copied "as is" - */ +// Dump out the pathnames of directories which can be copied "as is" void migr_do_listdirs(void) { cprintf("%d Don't forget these:\n", LISTING_FOLLOWS); - cprintf("bio|%s\n", ctdl_bio_dir); cprintf("files|%s\n", ctdl_file_dir); - cprintf("userpics|%s\n", ctdl_usrpic_dir); cprintf("messages|%s\n", ctdl_message_dir); - cprintf("netconfigs|%s\n", ctdl_netcfg_dir); cprintf("keys|%s\n", ctdl_key_dir); - cprintf("images|%s\n", ctdl_image_dir); - cprintf("info|%s\n", ctdl_info_dir); cprintf("000\n"); } -/* - * Common code appears in this section - */ +// ****************************************************************************** +// * Repair database integrity * +// ****************************************************************************** + +StrBuf *PlainMessageBuf = NULL; +HashList *UsedMessageIDS = NULL; +int migr_restore_message_metadata(long msgnum, int refcount) { + struct MetaData smi; + struct CtdlMessage *msg; + char *mptr = NULL; + + // We can use a static buffer here because there will never be more than + // one of this operation happening at any given time, and it's really best + // to just keep it allocated once instead of torturing malloc/free. + // Call this function with msgnum "-1" to free the buffer when finished. + static int encoded_alloc = 0; + static char *encoded_msg = NULL; + if (msgnum < 0) { + if ((encoded_alloc == 0) && (encoded_msg != NULL)) { + free(encoded_msg); + encoded_alloc = 0; + encoded_msg = NULL; + // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL; + } + return 0; + } + + if (PlainMessageBuf == NULL) { + PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ); + } + + // Ok, here we go ... + + msg = CtdlFetchMessage(msgnum, 1); + if (msg == NULL) { + return 1; + } + + GetMetaData(&smi, msgnum); + smi.meta_msgnum = msgnum; + smi.meta_refcount = refcount; + + // restore the content type from the message body: + mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:"); + if (mptr != NULL) { + char *aptr; + safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type); + striplt(smi.meta_content_type); + aptr = smi.meta_content_type; + while (!IsEmptyStr(aptr)) { + if ((*aptr == ';') + || (*aptr == ' ') + || (*aptr == 13) + || (*aptr == 10)) { + memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type)); + } + else aptr++; + } + } + + CC->redirect_buffer = PlainMessageBuf; + CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR); + smi.meta_rfc822_length = StrLength(CC->redirect_buffer); + CC->redirect_buffer = NULL; + + syslog(LOG_INFO, + "migrate: setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s", + smi.meta_msgnum, + smi.meta_refcount, + smi.meta_rfc822_length, + smi.meta_content_type + ); + + PutMetaData(&smi); + CM_Free(msg); + return 0; +} + + +void migr_check_room_msg(long msgnum, void *userdata) { + fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname); +} + + +void migr_check_rooms_backend(struct ctdlroom *buf, void *data) { + // message list goes inside this tag + CtdlGetRoom(&CC->room, buf->QRname); + CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL); +} + + +void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) { + struct MetaData smi; + const char *Pos = NULL; + StrBuf *oneRoom = NewStrBuf(); + + syslog(LOG_INFO, "migrate: removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec)); + + while (Pos != StrBufNOTNULL){ + StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|'); + CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, ""); + }; + GetMetaData(&smi, msgnum); + AdjRefCount(msgnum, -smi.meta_refcount); +} + + +void migr_do_restore_meta(void) { + char buf[SIZ]; + int failGetMessage; + long msgnum = 0; + int lastnum = 0; + int refcount = 0; + CitContext *Ctx; + char *prn; + StrBuf *RoomNames; + char cmd[SIZ]; + + migr_global_message_list = fopen(migr_tempfilename1, "w"); + if (migr_global_message_list != NULL) { + CtdlForEachRoom(migr_check_rooms_backend, NULL); + fclose(migr_global_message_list); + } + + // Process the 'global' message list. (Sort it and remove dups. + // Dups are ok because a message may be in more than one room, but + // this will be handled by exporting the reference count, not by + // exporting the message multiple times.) + snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2); + if (system(cmd) != 0) { + syslog(LOG_ERR, "migrate: error %d", errno); + } + + RoomNames = NewStrBuf(); + Ctx = CC; + migr_global_message_list = fopen(migr_tempfilename2, "r"); + if (migr_global_message_list != NULL) { + syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1); + while ((Ctx->kill_me == 0) && + (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) { + msgnum = atol(buf); + if (msgnum == 0L) + continue; + if (lastnum == 0) { + lastnum = msgnum; + } + prn = strchr(buf, ' '); + if (lastnum != msgnum) { + failGetMessage = migr_restore_message_metadata(lastnum, refcount); + if (failGetMessage) { + RemoveMessagesFromRooms(RoomNames, lastnum); + } + refcount = 1; + lastnum = msgnum; + if (prn != NULL) { + StrBufPlain(RoomNames, prn + 1, -1); + } + StrBufTrim(RoomNames); + } + else { + if (prn != NULL) { + if (StrLength(RoomNames) > 0) { + StrBufAppendBufPlain(RoomNames, HKEY("|"), 0); + } + StrBufAppendBufPlain(RoomNames, prn, -1, 1); + StrBufTrim(RoomNames); + } + refcount ++; + } + lastnum = msgnum; + } + failGetMessage = migr_restore_message_metadata(msgnum, refcount); + if (failGetMessage) { + RemoveMessagesFromRooms(RoomNames, lastnum); + } + fclose(migr_global_message_list); + } + + migr_restore_message_metadata(-1L, -1); // This frees the encoding buffer + cprintf("%d system analysis completed", CIT_OK); + Ctx->kill_me = 1; +} + + +// ****************************************************************************** +// * Dispatcher, Common code * +// ****************************************************************************** void cmd_migr(char *cmdbuf) { char cmd[32]; - if (CtdlAccessCheck(ac_internal)) return; + if (CtdlAccessCheck(ac_aide)) return; - if (CtdlTrySingleUser()) - { + if (CtdlTrySingleUser()) { CtdlDisableHouseKeeping(); CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1); CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2); @@ -897,6 +1095,9 @@ void cmd_migr(char *cmdbuf) { else if (!strcasecmp(cmd, "listdirs")) { migr_do_listdirs(); } + else if (!strcasecmp(cmd, "restoremeta")) { + migr_do_restore_meta(); + } else { cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE); } @@ -907,13 +1108,16 @@ void cmd_migr(char *cmdbuf) { CtdlEnableHouseKeeping(); CtdlEndSingleUser(); } - else - { + else { cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY); } } +// ****************************************************************************** +// * Module Hook * +// ****************************************************************************** + CTDL_MODULE_INIT(migrate) { if (!threading) @@ -921,6 +1125,6 @@ CTDL_MODULE_INIT(migrate) CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration"); } - /* return our module name for the log */ + // return our module name for the log return "migrate"; }