* ctdlmigrate: added a local replacement for gets() that
[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 exitcode;
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         
82         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
83         CtdlMakeTempFileName(socket_path, sizeof socket_path);
84
85         system("clear");
86         printf( "-------------------------------------------\n"
87                 "Over-the-wire migration utility for Citadel\n"
88                 "-------------------------------------------\n"
89                 "\n"
90                 "This utility is designed to migrate your Citadel installation\n"
91                 "to a new host system via a network connection, without disturbing\n"
92                 "the source system.  The target may be a different CPU architecture\n"
93                 "and/or operating system.  The source system should be running\n"
94                 "Citadel %d.%02d or newer, and the target system should be running\n"
95                 "either the same version or a newer version.  You will also need\n"
96                 "the 'rsync' utility, and OpenSSH v4 or newer.\n"
97                 "\n"
98                 "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
99                 "on this system will be ERASED.\n"
100                 "\n"
101                 "Do you wish to continue? "
102                 ,
103                 EXPORT_REV_MIN / 100,
104                 EXPORT_REV_MIN % 100
105         );
106
107         if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
108                 exit(0);
109         }
110
111         printf("\n\nGreat!  First we will check some things out here on our target\n"
112                 "system to make sure it is ready to receive data.\n\n");
113
114         printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
115         snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
116         snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
117         exitcode = system(cmd);
118         if (exitcode != 0) {
119                 printf("\nctdlmigrate was unable to attach to the Citadel server\n"
120                         "here on the target system.  Is Citadel running?\n\n");
121                 exit(1);
122         }
123         printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
124
125         printf("Enter the host name or IP address of the source system\n"
126                 "(example: ctdl.foo.org)\n"
127                 "--> ");
128         getz(remote_host);
129         printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
130                 "(usually root)\n--> ",
131                 remote_host);
132         getz(remote_user);
133
134         printf("\nEstablishing an SSH connection to the source system...\n\n");
135         unlink(socket_path);
136         snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
137         exitcode = system(cmd);
138         if (exitcode != 0) {
139                 printf("\nctdlmigrate was unable to establish an SSH connection to the\n"
140                         "source system, and cannot continue.\n\n");
141                 exit(exitcode);
142         }
143
144         printf("\nTesting a command over the connection...\n\n");
145         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
146                 socket_path, remote_user, remote_host);
147         exitcode = system(cmd);
148         printf("\n");
149         if (exitcode != 0) {
150                 printf("Remote commands are not succeeding.\n\n");
151                 exit(exitcode);
152         }
153
154         printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
155         snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
156         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
157                 socket_path, remote_user, remote_host, remote_sendcommand);
158         exitcode = system(cmd);
159         if (exitcode != 0) {
160                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
161                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
162                         socket_path, remote_user, remote_host, remote_sendcommand);
163                 exitcode = system(cmd);
164         }
165         if (exitcode != 0) {
166                 printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
167                         "the name of the directory on %s which contains the 'sendcommand' program.\n"
168                         "(example: /opt/foo/citadel)\n"
169                         "--> ", remote_host);
170                 getz(buf);
171                 snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
172                 snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
173                         socket_path, remote_user, remote_host, remote_sendcommand);
174                 exitcode = system(cmd);
175         }
176         printf("\n");
177         if (exitcode != 0) {
178                 printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
179                 exit(exitcode);
180         }
181
182         printf("ctdlmigrate will now begin a database migration...\n");
183
184         snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
185                 socket_path, remote_user, remote_host, remote_sendcommand);
186         source_artv = popen(cmd, "r");
187         if (!source_artv) {
188                 printf("\n%s\n\n", strerror(errno));
189                 exit(2);
190         }
191
192         snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
193         target_artv = popen(cmd, "w");
194         if (!target_artv) {
195                 printf("\n%s\n\n", strerror(errno));
196                 exit(3);
197         }
198
199         while (fgets(buf, sizeof buf, source_artv) != NULL) {
200                 if (fwrite(buf, strlen(buf), 1, target_artv) < 1) {
201                         printf("%s\n", strerror(errno));
202                         goto FAIL;
203                 }
204                 ++linecount;
205                 if ((linecount % 100) == 0) {
206                         printf("%c\r", spinning[((linecount / 100) % 4)]);
207                         fflush(stdout);
208                 }
209         }
210
211 FAIL:   pclose(source_artv);
212         pclose(target_artv);
213
214         // FIXME handle -h on both sides
215         // FIXME kill the master ssh session
216         printf("If this program was finished we would do more.  FIXME\n");
217         exit(0);
218 }