4 * Across-the-wire migration utility for Citadel
6 * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here.
7 * The scope of this program isn't wide enough to make a difference. If you don't like
8 * it you can rewrite it.
10 * Copyright (c) 2009 citadel.org
12 * This program is licensed to you under the terms of the GNU General Public License v3
14 * FIXME handle -h on both sides
24 #include <sys/types.h>
26 #include <sys/utsname.h>
34 #include <libcitadel.h>
39 #include "citadel_dirs.h"
47 * Replacement for gets() that doesn't throw a compiler warning.
48 * We're only using it for some simple prompts, so we don't need
49 * to worry about attackers exploiting it.
51 void getz(char *buf) {
54 ptr = fgets(buf, 32767, stdin);
60 ptr = strchr(buf, '\n');
68 int main(int argc, char *argv[])
72 char relhome[PATH_MAX]="";
73 char ctdldir[PATH_MAX]=CTDLDIR;
75 char sendcommand[PATH_MAX];
79 char socket_path[PATH_MAX];
80 char remote_user[256];
81 char remote_host[256];
82 char remote_sendcommand[PATH_MAX];
83 FILE *sourcefp = NULL;
84 FILE *targetfp = NULL;
86 char spinning[4] = "-\\|/" ;
90 calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
91 CtdlMakeTempFileName(socket_path, sizeof socket_path);
94 printf( "-------------------------------------------\n"
95 "Over-the-wire migration utility for Citadel\n"
96 "-------------------------------------------\n"
98 "This utility is designed to migrate your Citadel installation\n"
99 "to a new host system via a network connection, without disturbing\n"
100 "the source system. The target may be a different CPU architecture\n"
101 "and/or operating system. The source system should be running\n"
102 "Citadel %d.%02d or newer, and the target system should be running\n"
103 "either the same version or a newer version. You will also need\n"
104 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
106 "You must run this utility on the TARGET SYSTEM. Any existing data\n"
107 "on this system will be ERASED.\n"
109 "Do you wish to continue? "
111 EXPORT_REV_MIN / 100,
115 if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
119 printf("\n\nGreat! First we will check some things out here on our target\n"
120 "system to make sure it is ready to receive data.\n\n");
122 printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
123 snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
124 snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
125 cmdexit = system(cmd);
127 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
128 "here on the target system. Is Citadel running?\n\n");
131 printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n");
133 printf("Enter the host name or IP address of the source system\n"
134 "(example: ctdl.foo.org)\n"
137 printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
138 "(usually root)\n--> ",
145 printf("\n%s\n", strerror(errno));
149 else if (sshpid == 0)
151 printf("\nEstablishing an SSH connection to the source system...\n\n");
153 snprintf(cmd, sizeof cmd, "%s@%s", remote_user, remote_host);
154 execlp("ssh", "ssh", "-MNf", "-S", socket_path, cmd, NULL);
156 printf("\n%s\n", strerror(cmdexit));
157 exit(cmdexit); /* child process exits */
160 /* If we get here we are the parent process */
161 if (waitpid(sshpid, &cmdexit, 0) <= 0) {
163 printf("\n%s\n", strerror(errno));
167 if (WIFSIGNALED(cmdexit)) {
169 printf("\n%s\n", strerror(errno));
173 if ((WIFEXITED(cmdexit)) && (WEXITSTATUS(cmdexit) != 0)) {
174 exitcode = WEXITSTATUS(cmdexit);
175 printf("\n%s\n", strerror(errno));
179 printf("\nTesting a command over the connection...\n\n");
180 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
181 socket_path, remote_user, remote_host);
182 cmdexit = system(cmd);
185 printf("Remote commands are not succeeding.\n\n");
190 printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
191 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
192 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
193 socket_path, remote_user, remote_host, remote_sendcommand);
194 cmdexit = system(cmd);
196 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
197 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
198 socket_path, remote_user, remote_host, remote_sendcommand);
199 cmdexit = system(cmd);
202 printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
203 "the name of the directory on %s which contains the 'sendcommand' program.\n"
204 "(example: /opt/foo/citadel)\n"
205 "--> ", remote_host);
207 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
208 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
209 socket_path, remote_user, remote_host, remote_sendcommand);
210 cmdexit = system(cmd);
214 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
219 printf("ctdlmigrate will now begin a database migration...\n");
221 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
222 socket_path, remote_user, remote_host, remote_sendcommand);
223 sourcefp = popen(cmd, "r");
225 printf("\n%s\n\n", strerror(errno));
230 snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
231 targetfp = popen(cmd, "w");
233 printf("\n%s\n\n", strerror(errno));
238 while (fgets(buf, sizeof buf, sourcefp) != NULL) {
239 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
241 printf("%s\n", strerror(errno));
245 if ((linecount % 100) == 0) {
246 printf("%c\r", spinning[((linecount / 100) % 4)]);
251 FAIL: if (sourcefp) pclose(sourcefp);
252 if (targetfp) pclose(targetfp);
253 if (exitcode != 0) goto THEEND;
255 /* We need to copy a bunch of other stuff, and will do so using rsync */
257 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
258 socket_path, remote_user, remote_host, remote_sendcommand);
259 sourcefp = popen(cmd, "r");
261 printf("\n%s\n\n", strerror(errno));
265 while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
266 buf[strlen(buf)-1] = 0;
268 if (!strncasecmp(buf, "bio|", 4)) {
269 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
270 socket_path, remote_user, remote_host, &buf[4], ctdl_bio_dir);
272 else if (!strncasecmp(buf, "files|", 6)) {
273 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
274 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
276 else if (!strncasecmp(buf, "userpics|", 9)) {
277 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
278 socket_path, remote_user, remote_host, &buf[9], ctdl_usrpic_dir);
280 else if (!strncasecmp(buf, "messages|", 9)) {
281 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
282 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
284 else if (!strncasecmp(buf, "netconfigs|", 11)) {
285 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
286 socket_path, remote_user, remote_host, &buf[11], ctdl_netcfg_dir);
288 else if (!strncasecmp(buf, "keys|", 5)) {
289 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
290 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
292 else if (!strncasecmp(buf, "images|", 7)) {
293 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
294 socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
296 else if (!strncasecmp(buf, "info|", 5)) {
297 snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
298 socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
301 strcpy(cmd, "false"); /* cheap and sleazy way to throw an error */
304 cmdexit = system(cmd);
311 THEEND: kill(sshpid, SIGKILL);