2 * This module dumps and/or loads the Citadel database in XML format.
4 * Copyright (c) 1987-2015 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
16 * Explanation of <progress> tags:
19 * 2% finished exporting configuration
20 * 7% finished exporting users
21 * 12% finished exporting openids
22 * 17% finished exporting rooms
23 * 18% finished exporting floors
24 * 25% finished exporting visits
25 * 100% finished exporting messages
36 #include <sys/types.h>
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
43 # include <sys/time.h>
54 #include <libcitadel.h>
57 #include "citserver.h"
63 #include "euidindex.h"
64 #include "ctdl_module.h"
66 #define END_OF_MESSAGE "---eom---dbd---"
68 char migr_tempfilename1[PATH_MAX];
69 char migr_tempfilename2[PATH_MAX];
70 FILE *migr_global_message_list;
74 * Code which implements the export appears in this section
78 * Output a string to the client with these characters escaped: & < >
80 void xml_strout(char *str) {
90 client_write(""", 6);
92 else if (*c == '\'') {
93 client_write("'", 6);
96 client_write("<", 4);
99 client_write(">", 4);
101 else if (*c == '&') {
102 client_write("&", 5);
113 * Export a user record as XML
115 void migr_export_users_backend(struct ctdluser *buf, void *data) {
116 client_write("<user>\n", 7);
117 cprintf("<u_version>%d</u_version>\n", buf->version);
118 cprintf("<u_uid>%ld</u_uid>\n", (long)buf->uid);
119 client_write("<u_password>", 12); xml_strout(buf->password); client_write("</u_password>\n", 14);
120 cprintf("<u_flags>%u</u_flags>\n", buf->flags);
121 cprintf("<u_timescalled>%ld</u_timescalled>\n", buf->timescalled);
122 cprintf("<u_posted>%ld</u_posted>\n", buf->posted);
123 cprintf("<u_axlevel>%d</u_axlevel>\n", buf->axlevel);
124 cprintf("<u_usernum>%ld</u_usernum>\n", buf->usernum);
125 cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)buf->lastcall);
126 cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", buf->USuserpurge);
127 client_write("<u_fullname>", 12); xml_strout(buf->fullname); client_write("</u_fullname>\n", 14);
128 client_write("</user>\n", 8);
132 void migr_export_users(void) {
133 ForEachUser(migr_export_users_backend, NULL);
137 void migr_export_room_msg(long msgnum, void *userdata) {
138 cprintf("%ld\n", msgnum);
139 fprintf(migr_global_message_list, "%ld\n", msgnum);
143 void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
144 client_write("<room>\n", 7);
145 client_write("<QRname>", 8); xml_strout(buf->QRname); client_write("</QRname>\n", 10);
146 client_write("<QRpasswd>", 10); xml_strout(buf->QRpasswd); client_write("</QRpasswd>\n", 12);
147 cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
148 cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
149 cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
150 cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
151 if (buf->QRflags & QR_DIRECTORY) {
152 client_write("<QRdirname>", 11);
153 xml_strout(buf->QRdirname);
154 client_write("</QRdirname>\n", 13);
156 cprintf("<QRinfo>%ld</QRinfo>\n", buf->QRinfo);
157 cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
158 cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
159 cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
160 cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
161 cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
162 cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
163 cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
164 cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
165 client_write("</room>\n", 8);
167 /* message list goes inside this tag */
169 CtdlGetRoom(&CC->room, buf->QRname);
170 client_write("<room_messages>", 15);
171 client_write("<FRname>", 8); xml_strout(CC->room.QRname); client_write("</FRname>\n", 10);
172 client_write("<FRmsglist>", 11);
173 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
174 client_write("</FRmsglist>\n", 13);
175 client_write("</room_messages>\n", 17);
181 void migr_export_rooms(void) {
183 migr_global_message_list = fopen(migr_tempfilename1, "w");
184 if (migr_global_message_list != NULL) {
185 CtdlForEachRoom(migr_export_rooms_backend, NULL);
186 fclose(migr_global_message_list);
190 * Process the 'global' message list. (Sort it and remove dups.
191 * Dups are ok because a message may be in more than one room, but
192 * this will be handled by exporting the reference count, not by
193 * exporting the message multiple times.)
195 snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
196 if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno);
197 snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
198 if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno);
201 snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
202 FILE *fp = popen(cmd, "r");
204 fgets(cmd, sizeof cmd, fp);
206 total_msgs = atoi(cmd);
209 total_msgs = 1; // any nonzero just to keep it from barfing
211 syslog(LOG_DEBUG, "Total messages to be exported: %d", total_msgs);
215 void migr_export_floors(void) {
216 struct floor qfbuf, *buf;
219 for (i=0; i < MAXFLOORS; ++i) {
220 client_write("<floor>\n", 8);
221 cprintf("<f_num>%d</f_num>\n", i);
222 CtdlGetFloor(&qfbuf, i);
224 cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
225 client_write("<f_name>", 8); xml_strout(buf->f_name); client_write("</f_name>\n", 10);
226 cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
227 cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
228 cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
229 client_write("</floor>\n", 9);
235 * Return nonzero if the supplied string contains only characters which are valid in a sequence set.
237 int is_sequence_set(char *s) {
242 while (ch = *c++, ch) {
243 if (!strchr("0123456789*,:", ch)) {
253 * Traverse the visits file...
255 void migr_export_visits(void) {
257 struct cdbdata *cdbv;
259 cdb_rewind(CDB_VISIT);
261 while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
262 memset(&vbuf, 0, sizeof(visit));
263 memcpy(&vbuf, cdbv->ptr,
264 ((cdbv->len > sizeof(visit)) ?
265 sizeof(visit) : cdbv->len));
268 client_write("<visit>\n", 8);
269 cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
270 cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
271 cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
273 client_write("<v_seen>", 8);
274 if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
275 xml_strout(vbuf.v_seen);
278 cprintf("%ld", vbuf.v_lastseen);
280 client_write("</v_seen>", 9);
282 if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
283 client_write("<v_answered>", 12);
284 xml_strout(vbuf.v_answered);
285 client_write("</v_answered>\n", 14);
288 cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
289 cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
290 client_write("</visit>\n", 9);
295 void migr_export_message(long msgnum) {
297 struct CtdlMessage *msg;
300 /* We can use a static buffer here because there will never be more than
301 * one of this operation happening at any given time, and it's really best
302 * to just keep it allocated once instead of torturing malloc/free.
303 * Call this function with msgnum "-1" to free the buffer when finished.
305 static int encoded_alloc = 0;
306 static char *encoded_msg = NULL;
309 if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
317 /* Ok, here we go ... */
319 msg = CtdlFetchMessage(msgnum, 1);
320 if (msg == NULL) return; /* fail silently */
322 client_write("<message>\n", 10);
323 GetMetaData(&smi, msgnum);
324 cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
325 cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
326 client_write("<msg_meta_content_type>", 23); xml_strout(smi.meta_content_type); client_write("</msg_meta_content_type>\n", 25);
328 client_write("<msg_text>", 10);
329 CtdlSerializeMessage(&smr, msg);
332 /* Predict the buffer size we need. Expand the buffer if necessary. */
333 int encoded_len = smr.len * 15 / 10 ;
334 if (encoded_len > encoded_alloc) {
335 encoded_alloc = encoded_len;
336 encoded_msg = realloc(encoded_msg, encoded_alloc);
339 if (encoded_msg == NULL) {
340 /* Questionable hack that hopes it'll work next time and we only lose one message */
344 /* Once we do the encoding we know the exact size */
345 encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
346 client_write(encoded_msg, encoded_len);
351 client_write("</msg_text>\n", 12);
352 client_write("</message>\n", 11);
357 void migr_export_openids(void) {
358 struct cdbdata *cdboi;
362 cdb_rewind(CDB_OPENID);
363 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
364 if (cdboi->len > sizeof(long)) {
365 client_write("<openid>\n", 9);
366 memcpy(&usernum, cdboi->ptr, sizeof(long));
367 snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
368 client_write("<oid_url>", 9);
370 client_write("</oid_url>\n", 11);
371 cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
372 client_write("</openid>\n", 10);
379 void migr_export_configs(void) {
380 struct cdbdata *cdbcfg;
385 cdb_rewind(CDB_CONFIG);
386 while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
388 keylen = strlen(cdbcfg->ptr);
390 value = cdbcfg->ptr + keylen + 1;
392 client_write("<config key=\"", 13);
394 client_write("\">", 2);
396 client_write("</config>\n", 10);
404 void migr_export_messages(void) {
409 int prev_progress = 0;
413 migr_global_message_list = fopen(migr_tempfilename1, "r");
414 if (migr_global_message_list != NULL) {
415 syslog(LOG_INFO, "Opened %s\n", migr_tempfilename1);
416 while ((Ctx->kill_me == 0) &&
417 (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
420 migr_export_message(msgnum);
423 progress = (count * 74 / total_msgs) + 25 ;
424 if ((progress > prev_progress) && (progress < 100)) {
425 cprintf("<progress>%d</progress>\n", progress);
427 prev_progress = progress;
429 fclose(migr_global_message_list);
431 if (Ctx->kill_me == 0)
432 syslog(LOG_INFO, "Exported %d messages.\n", count);
434 syslog(LOG_ERR, "Export aborted due to client disconnect! \n");
436 migr_export_message(-1L); /* This frees the encoding buffer */
441 void migr_do_export(void) {
445 cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
448 client_write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", 40);
449 client_write("<citadel_migrate_data>\n", 23);
450 cprintf("<version>%d</version>\n", REV_LEVEL);
451 cprintf("<progress>%d</progress>\n", 0);
453 /* export the configuration database */
454 migr_export_configs();
455 cprintf("<progress>%d</progress>\n", 2);
457 if (Ctx->kill_me == 0) migr_export_users();
458 cprintf("<progress>%d</progress>\n", 7);
459 if (Ctx->kill_me == 0) migr_export_openids();
460 cprintf("<progress>%d</progress>\n", 12);
461 if (Ctx->kill_me == 0) migr_export_rooms();
462 cprintf("<progress>%d</progress>\n", 17);
463 if (Ctx->kill_me == 0) migr_export_floors();
464 cprintf("<progress>%d</progress>\n", 18);
465 if (Ctx->kill_me == 0) migr_export_visits();
466 cprintf("<progress>%d</progress>\n", 25);
467 if (Ctx->kill_me == 0) migr_export_messages();
468 client_write("</citadel_migrate_data>\n", 24);
469 cprintf("<progress>%d</progress>\n", 100);
470 client_write("000\n", 4);
479 * Here's the code that implements the import side. It's going to end up being
480 * one big loop with lots of global variables. I don't care. You wouldn't run
481 * multiple concurrent imports anyway. If this offends your delicate sensibilities
482 * then go rewrite it in Ruby on Rails or something.
486 int citadel_migrate_data = 0; /* Are we inside a <citadel_migrate_data> tag pair? */
487 StrBuf *migr_chardata = NULL;
488 StrBuf *migr_MsgData = NULL;
489 struct ctdluser usbuf;
490 struct ctdlroom qrbuf;
491 char openid_url[512];
492 long openid_usernum = 0;
493 char FRname[ROOMNAMELEN];
498 long import_msgnum = 0;
501 * This callback stores up the data which appears in between tags.
503 void migr_xml_chardata(void *data, const XML_Char *s, int len)
505 StrBufAppendBufPlain(migr_chardata, s, len, 0);
509 void migr_xml_start(void *data, const char *el, const char **attr) {
512 /*** GENERAL STUFF ***/
514 /* Reset chardata_len to zero and init buffer */
515 FlushStrBuf(migr_chardata);
516 FlushStrBuf(migr_MsgData);
518 if (!strcasecmp(el, "citadel_migrate_data")) {
519 ++citadel_migrate_data;
521 /* As soon as it looks like the input data is a genuine Citadel XML export,
522 * whack the existing database on disk to make room for the new one.
524 if (citadel_migrate_data == 1) {
525 for (i = 0; i < MAXCDB; ++i) {
532 if (citadel_migrate_data != 1) {
533 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el);
537 /* When we begin receiving XML for one of these record types, clear out the associated
538 * buffer so we don't accidentally carry over any data from a previous record.
540 if (!strcasecmp(el, "user")) memset(&usbuf, 0, sizeof (struct ctdluser));
541 else if (!strcasecmp(el, "openid")) memset(openid_url, 0, sizeof openid_url);
542 else if (!strcasecmp(el, "room")) memset(&qrbuf, 0, sizeof (struct ctdlroom));
543 else if (!strcasecmp(el, "room_messages")) memset(FRname, 0, sizeof FRname);
544 else if (!strcasecmp(el, "floor")) memset(&flbuf, 0, sizeof (struct floor));
545 else if (!strcasecmp(el, "visit")) memset(&vbuf, 0, sizeof (visit));
547 else if (!strcasecmp(el, "message")) {
548 memset(&smi, 0, sizeof (struct MetaData));
551 else if (!strcasecmp(el, "config")) {
552 syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG START ELEMENT FIXME\033\0m");
558 int migr_userrecord(void *data, const char *el)
560 if (!strcasecmp(el, "u_version")) usbuf.version = atoi(ChrPtr(migr_chardata));
561 else if (!strcasecmp(el, "u_uid")) usbuf.uid = atol(ChrPtr(migr_chardata));
562 else if (!strcasecmp(el, "u_password")) safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
563 else if (!strcasecmp(el, "u_flags")) usbuf.flags = atoi(ChrPtr(migr_chardata));
564 else if (!strcasecmp(el, "u_timescalled")) usbuf.timescalled = atol(ChrPtr(migr_chardata));
565 else if (!strcasecmp(el, "u_posted")) usbuf.posted = atol(ChrPtr(migr_chardata));
566 else if (!strcasecmp(el, "u_axlevel")) usbuf.axlevel = atoi(ChrPtr(migr_chardata));
567 else if (!strcasecmp(el, "u_usernum")) usbuf.usernum = atol(ChrPtr(migr_chardata));
568 else if (!strcasecmp(el, "u_lastcall")) usbuf.lastcall = atol(ChrPtr(migr_chardata));
569 else if (!strcasecmp(el, "u_USuserpurge")) usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
570 else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
575 int migr_roomrecord(void *data, const char *el)
577 if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
578 else if (!strcasecmp(el, "QRpasswd")) safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
579 else if (!strcasecmp(el, "QRroomaide")) qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
580 else if (!strcasecmp(el, "QRhighest")) qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
581 else if (!strcasecmp(el, "QRgen")) qrbuf.QRgen = atol(ChrPtr(migr_chardata));
582 else if (!strcasecmp(el, "QRflags")) qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
583 else if (!strcasecmp(el, "QRdirname")) safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
584 else if (!strcasecmp(el, "QRinfo")) qrbuf.QRinfo = atol(ChrPtr(migr_chardata));
585 else if (!strcasecmp(el, "QRfloor")) qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
586 else if (!strcasecmp(el, "QRmtime")) qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
587 else if (!strcasecmp(el, "QRexpire_mode")) qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
588 else if (!strcasecmp(el, "QRexpire_value")) qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
589 else if (!strcasecmp(el, "QRnumber")) qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
590 else if (!strcasecmp(el, "QRorder")) qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
591 else if (!strcasecmp(el, "QRflags2")) qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
592 else if (!strcasecmp(el, "QRdefaultview")) qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
597 int migr_floorrecord(void *data, const char *el)
599 if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata));
600 else if (!strcasecmp(el, "f_flags")) flbuf.f_flags = atoi(ChrPtr(migr_chardata));
601 else if (!strcasecmp(el, "f_name")) safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
602 else if (!strcasecmp(el, "f_ref_count")) flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
603 else if (!strcasecmp(el, "f_ep_expire_mode")) flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
604 else if (!strcasecmp(el, "f_ep_expire_value")) flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
609 int migr_visitrecord(void *data, const char *el)
611 if (!strcasecmp(el, "v_roomnum")) vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
612 else if (!strcasecmp(el, "v_roomgen")) vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
613 else if (!strcasecmp(el, "v_usernum")) vbuf.v_usernum = atol(ChrPtr(migr_chardata));
615 else if (!strcasecmp(el, "v_seen")) {
616 int is_textual_seen = 0;
618 int max = StrLength(migr_chardata);
620 vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
622 for (i=0; i < max; ++i)
623 if (!isdigit(ChrPtr(migr_chardata)[i]))
626 safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
629 else if (!strcasecmp(el, "v_answered")) safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
630 else if (!strcasecmp(el, "v_flags")) vbuf.v_flags = atoi(ChrPtr(migr_chardata));
631 else if (!strcasecmp(el, "v_view")) vbuf.v_view = atoi(ChrPtr(migr_chardata));
637 void migr_xml_end(void *data, const char *el)
642 long *msglist = NULL;
643 int msglist_alloc = 0;
644 /*** GENERAL STUFF ***/
646 if (!strcasecmp(el, "citadel_migrate_data")) {
647 --citadel_migrate_data;
651 if (citadel_migrate_data != 1) {
652 syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el);
656 // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
660 if (!strcasecmp(el, "config"))
662 syslog(LOG_DEBUG, "\033[31m IMPORT OF CONFIG END ELEMENT FIXME\033\0m");
663 CtdlSetConfigInt("c_enable_fulltext", 0); /* always disable FIXME put this somewhere more appropriate */
667 else if ((!strncasecmp(el, HKEY("u_"))) &&
668 migr_userrecord(data, el))
669 ; /* Nothing to do anymore */
670 else if (!strcasecmp(el, "user")) {
672 syslog(LOG_INFO, "Imported user: %s\n", usbuf.fullname);
677 else if (!strcasecmp(el, "oid_url")) safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
678 else if (!strcasecmp(el, "oid_usernum")) openid_usernum = atol(ChrPtr(migr_chardata));
680 else if (!strcasecmp(el, "openid")) { /* see serv_openid_rp.c for a description of the record format */
683 oid_data_len = sizeof(long) + strlen(openid_url) + 1;
684 oid_data = malloc(oid_data_len);
685 memcpy(oid_data, &openid_usernum, sizeof(long));
686 memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
687 cdb_store(CDB_OPENID, openid_url, strlen(openid_url), oid_data, oid_data_len);
689 syslog(LOG_INFO, "Imported OpenID: %s (%ld)\n", openid_url, openid_usernum);
693 else if ((!strncasecmp(el, HKEY("QR"))) &&
694 migr_roomrecord(data, el))
695 ; /* Nothing to do anymore */
696 else if (!strcasecmp(el, "room")) {
698 syslog(LOG_INFO, "Imported room: %s\n", qrbuf.QRname);
701 /*** ROOM MESSAGE POINTERS ***/
703 else if (!strcasecmp(el, "FRname")) safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
705 else if (!strcasecmp(el, "FRmsglist")) {
706 if (!IsEmptyStr(FRname)) {
708 msglist_alloc = 1000;
709 msglist = malloc(sizeof(long) * msglist_alloc);
711 syslog(LOG_DEBUG, "Message list for: %s\n", FRname);
713 ptr = ChrPtr(migr_chardata);
715 while ((*ptr != 0) && (!isdigit(*ptr))) {
718 if ((*ptr != 0) && (isdigit(*ptr))) {
721 if (msgcount >= msglist_alloc) {
723 msglist = realloc(msglist, sizeof(long) * msglist_alloc);
725 msglist[msgcount++] = msgnum;
728 while ((*ptr != 0) && (isdigit(*ptr))) {
734 CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
739 syslog(LOG_DEBUG, "Imported %d messages.\n", msgcount);
740 if (server_shutting_down) {
746 else if ((!strncasecmp(el, HKEY("f_"))) &&
747 migr_floorrecord(data, el))
748 ; /* Nothing to do anymore */
750 else if (!strcasecmp(el, "floor")) {
751 CtdlPutFloor(&flbuf, floornum);
752 syslog(LOG_INFO, "Imported floor #%d (%s)\n", floornum, flbuf.f_name);
756 else if ((!strncasecmp(el, HKEY("v_"))) &&
757 migr_visitrecord(data, el))
758 ; /* Nothing to do anymore */
759 else if (!strcasecmp(el, "visit")) {
761 syslog(LOG_INFO, "Imported visit: %ld/%ld/%ld\n", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
766 else if (!strcasecmp(el, "msg_msgnum")) import_msgnum = atol(ChrPtr(migr_chardata));
767 else if (!strcasecmp(el, "msg_meta_refcount")) smi.meta_refcount = atoi(ChrPtr(migr_chardata));
768 else if (!strcasecmp(el, "msg_meta_content_type")) safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
770 else if (!strcasecmp(el, "msg_text"))
773 FlushStrBuf(migr_MsgData);
774 StrBufDecodeBase64To(migr_MsgData, migr_MsgData);
776 cdb_store(CDB_MSGMAIN,
779 ChrPtr(migr_MsgData),
780 StrLength(migr_MsgData) + 1);
782 smi.meta_msgnum = import_msgnum;
786 "Imported message #%ld, size=%d, refcount=%d, content-type: %s\n",
788 StrLength(migr_MsgData),
790 smi.meta_content_type);
793 /*** MORE GENERAL STUFF ***/
795 FlushStrBuf(migr_chardata);
804 void migr_do_import(void) {
810 migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
811 migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
812 Buf = NewStrBufPlain(NULL, SIZ);
813 xp = XML_ParserCreate(NULL);
815 cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
818 XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
819 XML_SetCharacterDataHandler(xp, migr_xml_chardata);
823 cprintf("%d sock it to me\n", SEND_LISTING);
826 client_set_inbound_buf(SIZ * 10);
828 while (!Finished && client_read_random_blob(Buf, -1) >= 0) {
829 if ((StrLength(Buf) > 4) &&
830 !strcmp(ChrPtr(Buf) + StrLength(Buf) - 4, "000\n"))
833 StrBufCutAt(Buf, StrLength(Buf) - 4, NULL);
835 if (server_shutting_down)
836 break; // Should we break or return?
838 if (StrLength(Buf) == 0)
841 XML_Parse(xp, ChrPtr(Buf), StrLength(Buf), 0);
845 XML_Parse(xp, "", 0, 1);
848 FreeStrBuf(&migr_chardata);
849 FreeStrBuf(&migr_MsgData);
850 rebuild_euid_index();
851 rebuild_usersbynumber();
858 * Dump out the pathnames of directories which can be copied "as is"
860 void migr_do_listdirs(void) {
861 cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
862 cprintf("bio|%s\n", ctdl_bio_dir);
863 cprintf("files|%s\n", ctdl_file_dir);
864 cprintf("userpics|%s\n", ctdl_usrpic_dir);
865 cprintf("messages|%s\n", ctdl_message_dir);
866 cprintf("netconfigs|%s\n", ctdl_netcfg_dir);
867 cprintf("keys|%s\n", ctdl_key_dir);
868 cprintf("images|%s\n", ctdl_image_dir);
869 cprintf("info|%s\n", ctdl_info_dir);
875 * Common code appears in this section
879 void cmd_migr(char *cmdbuf) {
882 if (CtdlAccessCheck(ac_internal)) return;
884 if (CtdlTrySingleUser())
886 CtdlDisableHouseKeeping();
887 CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
888 CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
890 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
891 if (!strcasecmp(cmd, "export")) {
894 else if (!strcasecmp(cmd, "import")) {
897 else if (!strcasecmp(cmd, "listdirs")) {
901 cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
904 unlink(migr_tempfilename1);
905 unlink(migr_tempfilename2);
907 CtdlEnableHouseKeeping();
912 cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
917 CTDL_MODULE_INIT(migrate)
921 CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
924 /* return our module name for the log */