ctdlmigrate now uses the new directory semantics
[citadel.git] / citadel / utils / ctdlmigrate.c
1 /*
2  * Across-the-wire migration utility for Citadel
3  *
4  * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here.
5  * The scope of this program isn't wide enough to make a difference.  If you don't like
6  * it you can rewrite it.
7  *
8  * Copyright (c) 2009-2021 citadel.org
9  *
10  * This program is open source software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 3.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * (Note: a useful future enhancement might be to support "-h" on both sides)
19  *
20  */
21
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <fcntl.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/utsname.h>
31 #include <sys/wait.h>
32 #include <signal.h>
33 #include <netdb.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <pwd.h>
37 #include <time.h>
38 #include <libcitadel.h>
39 #include "citadel.h"
40 #include "axdefs.h"
41 #include "sysdep.h"
42 #include "config.h"
43 #include "citadel_dirs.h"
44
45
46
47 /*
48  * Replacement for gets() that doesn't throw a compiler warning.
49  * We're only using it for some simple prompts, so we don't need
50  * to worry about attackers exploiting it.
51  */
52 void getz(char *buf) {
53         char *ptr;
54
55         ptr = fgets(buf, SIZ, stdin);
56         if (!ptr) {
57                 buf[0] = 0;
58                 return;
59         }
60
61         ptr = strchr(buf, '\n');
62         if (ptr) *ptr = 0;
63 }
64
65
66
67
68
69 int main(int argc, char *argv[]) {
70         char ctdldir[PATH_MAX]=CTDLDIR;
71         char yesno[5];
72         char sendcommand[PATH_MAX];
73         int cmdexit;
74         char cmd[PATH_MAX];
75         char buf[PATH_MAX];
76         char socket_path[PATH_MAX];
77         char remote_user[SIZ];
78         char remote_host[SIZ];
79         char remote_sendcommand[PATH_MAX];
80         FILE *sourcefp = NULL;
81         FILE *targetfp = NULL;
82         int linecount = 0;
83         char spinning[4] = "-\\|/" ;
84         int exitcode = 0;
85         
86         CtdlMakeTempFileName(socket_path, sizeof socket_path);
87         if (chdir(ctdldir) != 0) {
88                 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
89                 exit(errno);
90         }
91
92
93         printf( "\033[2J\033[H\n"
94                 "-------------------------------------------\n"
95                 "Over-the-wire migration utility for Citadel\n"
96                 "-------------------------------------------\n"
97                 "\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 version %d 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"
105                 "\n"
106                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
107                 "on this system will be ERASED.\n"
108                 "\n"
109                 "Do you wish to continue? "
110                 ,
111                 EXPORT_REV_MIN
112         );
113
114         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
115                 exit(0);
116         }
117
118         printf("\n\nGreat!  First we will check some things out here on our target\n"
119                 "system to make sure it is ready to receive data.\n\n");
120
121         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
122         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
123         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
124         cmdexit = system(cmd);
125         if (cmdexit != 0) {
126                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
127                         "here on the target system.  Is Citadel running?\n\n");
128                 exit(1);
129         }
130         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
131
132         printf("Enter the host name or IP address of the source system\n"
133                 "(example: ctdl.foo.org)\n"
134                 "--> ");
135         getz(remote_host);
136
137 get_remote_user:
138         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
139                 "(usually root)\n--> ",
140                 remote_host);
141         getz(remote_user);
142         if (IsEmptyStr(remote_user))
143                 goto get_remote_user;
144
145         printf("\nEstablishing an SSH connection to the source system...\n\n");
146         unlink(socket_path);
147         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
148         cmdexit = system(cmd);
149         printf("\n");
150         if (cmdexit != 0) {
151                 printf("This program was unable to establish an SSH session to the source system.\n\n");
152                 exitcode = cmdexit;
153                 goto THEEND;
154         }
155
156         printf("\nTesting a command over the connection...\n\n");
157         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
158                 socket_path, remote_user, remote_host);
159         cmdexit = system(cmd);
160         printf("\n");
161         if (cmdexit != 0) {
162                 printf("Remote commands are not succeeding.\n\n");
163                 exitcode = cmdexit;
164                 goto THEEND;
165         }
166
167         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
168         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
169         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
170                 socket_path, remote_user, remote_host, remote_sendcommand);
171         cmdexit = system(cmd);
172         if (cmdexit != 0) {
173                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
174                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
175                         socket_path, remote_user, remote_host, remote_sendcommand);
176                 cmdexit = system(cmd);
177         }
178         if (cmdexit != 0) {
179                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
180                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
181                         "(example: /opt/foo/citadel)\n"
182                         "--> ", remote_host);
183                 getz(buf);
184                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
185                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
186                         socket_path, remote_user, remote_host, remote_sendcommand);
187                 cmdexit = system(cmd);
188         }
189         printf("\n");
190         if (cmdexit != 0) {
191                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
192                 exitcode = cmdexit;
193                 goto THEEND;
194         }
195
196         printf("ctdlmigrate will now begin a database migration...\n");
197         printf("  if the system doesn't start working, \n");
198         printf("  have a look at the syslog for pending jobs needing to be terminated.\n");
199
200         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
201                 socket_path, remote_user, remote_host, remote_sendcommand);
202         sourcefp = popen(cmd, "r");
203         if (!sourcefp) {
204                 printf("\n%s\n\n", strerror(errno));
205                 exitcode = 2;
206                 goto THEEND;
207         }
208
209         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
210         targetfp = popen(cmd, "w");
211         if (!targetfp) {
212                 printf("\n%s\n\n", strerror(errno));
213                 exitcode = 3;
214                 goto THEEND;
215         }
216
217         while (fgets(buf, sizeof buf, sourcefp) != NULL) {
218                 if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
219                         exitcode = 4;
220                         printf("%s\n", strerror(errno));
221                         goto FAIL;
222                 }
223                 ++linecount;
224                 if ((linecount % 100) == 0) {
225                         printf("%c\r", spinning[((linecount / 100) % 4)]);
226                         fflush(stdout);
227                 }
228         }
229
230 FAIL:   if (sourcefp) pclose(sourcefp);
231         if (targetfp) pclose(targetfp);
232         if (exitcode != 0) goto THEEND;
233
234         /* We need to copy a bunch of other stuff, and will do so using rsync */
235
236         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
237                 socket_path, remote_user, remote_host, remote_sendcommand);
238         sourcefp = popen(cmd, "r");
239         if (!sourcefp) {
240                 printf("\n%s\n\n", strerror(errno));
241                 exitcode = 2;
242                 goto THEEND;
243         }
244         while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
245                 buf[strlen(buf)-1] = 0;
246
247                 if (!strncasecmp(buf, "files|", 6)) {
248                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
249                                 socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
250                 }
251                 else if (!strncasecmp(buf, "messages|", 9)) {
252                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
253                                 socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
254                 }
255                 else if (!strncasecmp(buf, "keys|", 5)) {
256                         snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
257                                 socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
258                 }
259                 else {
260                         strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
261                 }
262                 printf("%s\n", cmd);
263                 cmdexit = system(cmd);
264                 if (cmdexit != 0) {
265                         exitcode += cmdexit;
266                 }
267         }
268         pclose(sourcefp);
269
270 THEEND: if (exitcode == 0) {
271                 printf("\n\n *** Citadel migration was successful! *** \n\n");
272         }
273         else {
274                 printf("\n\n *** Citadel migration was unsuccessful. *** \n\n");
275         }
276         printf("\nShutting down the socket connection...\n\n");
277         snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s",
278                 socket_path, remote_user, remote_host);
279         cmdexit = system(cmd);
280         printf("\n");
281         if (cmdexit != 0) {
282                 printf("There was an error shutting down the socket.\n\n");
283                 exitcode = cmdexit;
284         }
285
286         unlink(socket_path);
287         exit(exitcode);
288 }