* Use fork() and exec() instead of system() to set up the master SSH session, so...
[citadel.git] / citadel / ctdlmigrate.c
1 /*
2  * $Id$
3  *
4  * Across-the-wire migration utility for Citadel
5  *
6  * Copyright (c) 2009 citadel.org
7  *
8  * This program is licensed to you under the terms of the GNU General Public License v3
9  *
10  */
11
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <fcntl.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/utsname.h>
21 #include <sys/wait.h>
22 #include <signal.h>
23 #include <netdb.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <pwd.h>
27 #include <time.h>
28 #include <libcitadel.h>
29 #include "citadel.h"
30 #include "axdefs.h"
31 #include "sysdep.h"
32 #include "config.h"
33 #include "citadel_dirs.h"
34 #if HAVE_BACKTRACE
35 #include <execinfo.h>
36 #endif
37
38
39
40 /*
41  * Replacement for gets() that doesn't throw a compiler warning.
42  * We're only using it for some simple prompts, so we don't need
43  * to worry about attackers exploiting it.
44  */
45 void getz(char *buf) {
46         char *ptr;
47
48         ptr = fgets(buf, 32767, stdin);
49         if (!ptr) {
50                 buf[0] = 0;
51                 return;
52         }
53
54         ptr = strchr(buf, '\n');
55         if (ptr) *ptr = 0;
56 }
57
58
59
60
61
62 int main(int argc, char *argv[])
63 {
64         int relh=0;
65         int home=0;
66         char relhome[PATH_MAX]="";
67         char ctdldir[PATH_MAX]=CTDLDIR;
68         char yesno[5];
69         char sendcommand[PATH_MAX];
70         int cmdexit;
71         char cmd[PATH_MAX];
72         char buf[PATH_MAX];
73         char socket_path[PATH_MAX];
74         char remote_user[256];
75         char remote_host[256];
76         char remote_sendcommand[PATH_MAX];
77         FILE *source_artv;
78         FILE *target_artv;
79         int linecount = 0;
80         char spinning[4] = "-\\|/" ;
81         int exitcode = 0;
82         pid_t sshpid;
83         
84         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
85         CtdlMakeTempFileName(socket_path, sizeof socket_path);
86
87         system("clear");
88         printf( "-------------------------------------------\n"
89                 "Over-the-wire migration utility for Citadel\n"
90                 "-------------------------------------------\n"
91                 "\n"
92                 "This utility is designed to migrate your Citadel installation\n"
93                 "to a new host system via a network connection, without disturbing\n"
94                 "the source system.  The target may be a different CPU architecture\n"
95                 "and/or operating system.  The source system should be running\n"
96                 "Citadel %d.%02d or newer, and the target system should be running\n"
97                 "either the same version or a newer version.  You will also need\n"
98                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
99                 "\n"
100                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
101                 "on this system will be ERASED.\n"
102                 "\n"
103                 "Do you wish to continue? "
104                 ,
105                 EXPORT_REV_MIN / 100,
106                 EXPORT_REV_MIN % 100
107         );
108
109         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
110                 exit(0);
111         }
112
113         printf("\n\nGreat!  First we will check some things out here on our target\n"
114                 "system to make sure it is ready to receive data.\n\n");
115
116         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
117         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
118         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
119         cmdexit = system(cmd);
120         if (cmdexit != 0) {
121                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
122                         "here on the target system.  Is Citadel running?\n\n");
123                 exit(1);
124         }
125         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
126
127         printf("Enter the host name or IP address of the source system\n"
128                 "(example: ctdl.foo.org)\n"
129                 "--> ");
130         getz(remote_host);
131         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
132                 "(usually root)\n--> ",
133                 remote_host);
134         getz(remote_user);
135
136         sshpid = fork();
137         if (sshpid < 0)
138         {
139                 printf("\n%s\n", strerror(errno));
140                 exitcode = errno;
141                 goto THEEND;
142         }
143         else if (sshpid == 0)
144         {
145                 printf("\nEstablishing an SSH connection to the source system...\n\n");
146                 unlink(socket_path);
147                 snprintf(cmd, sizeof cmd, "%s@%s", remote_user, remote_host);
148                 execlp("ssh", "ssh", "-MNf", "-S", socket_path, cmd, NULL);
149                 cmdexit = errno;
150                 printf("\n%s\n", strerror(cmdexit));
151                 exit(cmdexit);          /* child process exits */
152         }
153
154         /* If we get here we are the parent process */
155         if (waitpid(sshpid, &cmdexit, 0) <= 0) {
156                 exitcode = errno;
157                 printf("\n%s\n", strerror(errno));
158                 goto THEEND;
159         }
160
161         if (WIFSIGNALED(cmdexit)) {
162                 exitcode = errno;
163                 printf("\n%s\n", strerror(errno));
164                 goto THEEND;
165         }
166
167         if ((WIFEXITED(cmdexit)) && (WEXITSTATUS(cmdexit) != 0)) {
168                 exitcode = WEXITSTATUS(cmdexit);
169                 printf("\n%s\n", strerror(errno));
170                 goto THEEND;
171         }
172
173         printf("\nTesting a command over the connection...\n\n");
174         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
175                 socket_path, remote_user, remote_host);
176         cmdexit = system(cmd);
177         printf("\n");
178         if (cmdexit != 0) {
179                 printf("Remote commands are not succeeding.\n\n");
180                 exitcode = cmdexit;
181                 goto THEEND;
182         }
183
184         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
185         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
186         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
187                 socket_path, remote_user, remote_host, remote_sendcommand);
188         cmdexit = system(cmd);
189         if (cmdexit != 0) {
190                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
191                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
192                         socket_path, remote_user, remote_host, remote_sendcommand);
193                 cmdexit = system(cmd);
194         }
195         if (cmdexit != 0) {
196                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
197                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
198                         "(example: /opt/foo/citadel)\n"
199                         "--> ", remote_host);
200                 getz(buf);
201                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
202                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
203                         socket_path, remote_user, remote_host, remote_sendcommand);
204                 cmdexit = system(cmd);
205         }
206         printf("\n");
207         if (cmdexit != 0) {
208                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
209                 exitcode = cmdexit;
210                 goto THEEND;
211         }
212
213         printf("ctdlmigrate will now begin a database migration...\n");
214
215         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
216                 socket_path, remote_user, remote_host, remote_sendcommand);
217         source_artv = popen(cmd, "r");
218         if (!source_artv) {
219                 printf("\n%s\n\n", strerror(errno));
220                 exitcode = 2;
221                 goto THEEND;
222         }
223
224         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
225         target_artv = popen(cmd, "w");
226         if (!target_artv) {
227                 printf("\n%s\n\n", strerror(errno));
228                 exitcode = 3;
229                 goto THEEND;
230         }
231
232         while (fgets(buf, sizeof buf, source_artv) != NULL) {
233                 if (fwrite(buf, strlen(buf), 1, target_artv) < 1) {
234                         exitcode = 4;
235                         printf("%s\n", strerror(errno));
236                         goto FAIL;
237                 }
238                 ++linecount;
239                 if ((linecount % 100) == 0) {
240                         printf("%c\r", spinning[((linecount / 100) % 4)]);
241                         fflush(stdout);
242                 }
243         }
244
245 FAIL:   pclose(source_artv);
246         pclose(target_artv);
247
248         // FIXME handle -h on both sides
249         printf("If this program was finished we would do more.  FIXME\n");
250
251 THEEND: kill(sshpid, SIGKILL);
252         unlink(socket_path);
253         exit(exitcode);
254 }