]> code.citadel.org Git - citadel.git/blobdiff - citadel/ctdlmigrate.c
Fixed an incorrect box drawing character
[citadel.git] / citadel / ctdlmigrate.c
index e0e601fbbc3661ae6b1f1fc528829d2a1cb30643..8c0cbe071ceaf208ad0d8a24f944e36cd28cb427 100644 (file)
@@ -1,12 +1,15 @@
 /*
 /*
- * $Id: $
- *
  * Across-the-wire migration utility for Citadel
  *
  * Across-the-wire migration utility for Citadel
  *
- * Copyright (c) 2009 citadel.org
+ * Copyright (c) 2009-2021 citadel.org
  *
  *
- * This program is licensed to you under the terms of the GNU General Public License v3
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
  *
  *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  */
 
 #include <stdlib.h>
  */
 
 #include <stdlib.h>
 #include <limits.h>
 #include <pwd.h>
 #include <time.h>
 #include <limits.h>
 #include <pwd.h>
 #include <time.h>
+#include <readline/readline.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 #include <libcitadel.h>
 #include "citadel.h"
 #include "axdefs.h"
 #include "sysdep.h"
 #include "config.h"
 #include "citadel_dirs.h"
 #include <libcitadel.h>
 #include "citadel.h"
 #include "axdefs.h"
 #include "sysdep.h"
 #include "config.h"
 #include "citadel_dirs.h"
-#if HAVE_BACKTRACE
-#include <execinfo.h>
-#endif
 
 
 
 
 
 
+// yeah, this wouldn't work in a multithreaded program.
+static int gmaxlen = 0;
+static char *gdeftext = NULL;
+
+static int limit_rl(FILE *dummy) {
+       if (rl_end > gmaxlen) {
+               return '\b';
+       }
+       return rl_getc(dummy);
+}
+
+
+static int getz_deftext(void) {
+       if (gdeftext) {
+               rl_insert_text(gdeftext);
+               gdeftext = NULL;
+               rl_startup_hook = NULL;
+       }
+       return 0;
+}
+
+
+/*
+ * Replacement for gets() that doesn't throw a compiler warning.
+ * We're only using it for some simple prompts, so we don't need
+ * to worry about attackers exploiting it.
+ */
+void getz(char *buf, int maxlen, char *default_value, char *prompt) {
+       rl_startup_hook = getz_deftext;
+       rl_getc_function = limit_rl;
+       gmaxlen = maxlen;
+       gdeftext = default_value;
+       strcpy(buf, readline(prompt));
+}
+
+
+// These variables are used by both main() and ctdlmigrate_exit()
+// They are global so that ctdlmigrate_exit can be called from a signal handler
+FILE *sourcefp = NULL;
+char socket_path[PATH_MAX];
+pid_t sshpid = (-1);
+
+void ctdlmigrate_exit(int cmdexit) {
+       if (sourcefp) {
+               printf("Closing the data connection from the source system...\n");
+               pclose(sourcefp);
+       }
+       unlink(socket_path);
+       if (sshpid > 0) {
+               printf("Shutting down the SSH session...\n");
+               kill(sshpid, SIGKILL);
+       }
+       printf("\n\n\033[3%dmExit code %d\033[0m\n", (cmdexit ? 1 : 2), cmdexit);
+       exit(cmdexit);
+}
+
+
+int uds_connectsock(char *sockpath) {
+       int s;
+       struct sockaddr_un addr;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, sockpath);
+
+       s = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (s < 0) {
+               fprintf(stderr, "ctdlmigrate: Can't create socket: %s\n", strerror(errno));
+               ctdlmigrate_exit(3);
+       }
+
+       if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
+               close(s);
+               ctdlmigrate_exit(3);
+       }
+
+       return s;
+}
+
+
+/*
+ * input binary data from socket
+ */
+void serv_read(int serv_sock, char *buf, int bytes) {
+       int len, rlen;
+
+       len = 0;
+       while (len < bytes) {
+               rlen = read(serv_sock, &buf[len], bytes - len);
+               if (rlen < 1) {
+                       return;
+               }
+               len = len + rlen;
+       }
+}
+
 
 
-int main(int argc, char *argv[])
-{
-       int relh=0;
-       int home=0;
-       char relhome[PATH_MAX]="";
+/*
+ * send binary to server
+ */
+void serv_write(int serv_sock, char *buf, int nbytes) {
+       int bytes_written = 0;
+       int retval;
+       while (bytes_written < nbytes) {
+               retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
+               if (retval < 1) {
+                       return;
+               }
+               bytes_written = bytes_written + retval;
+       }
+}
+
+
+/*
+ * input string from socket - implemented in terms of serv_read()
+ */
+void serv_gets(int serv_sock, char *buf) {
+       int i;
+
+       /* Read one character at a time.
+        */
+       for (i = 0;; i++) {
+               serv_read(serv_sock, &buf[i], 1);
+               if (buf[i] == '\n' || i == (SIZ-1))
+                       break;
+       }
+
+       /* If we got a long line, discard characters until the newline.
+        */
+       if (i == (SIZ-1)) {
+               while (buf[i] != '\n') {
+                       serv_read(serv_sock, &buf[i], 1);
+               }
+       }
+
+       /* Strip all trailing nonprintables (crlf)
+        */
+       buf[i] = 0;
+}
+
+
+/*
+ * send line to server - implemented in terms of serv_write()
+ */
+void serv_puts(int serv_sock, char *buf) {
+       serv_write(serv_sock, buf, strlen(buf));
+       serv_write(serv_sock, "\n", 1);
+}
+
+
+int main(int argc, char *argv[]) {
        char ctdldir[PATH_MAX]=CTDLDIR;
        char yesno[5];
        char ctdldir[PATH_MAX]=CTDLDIR;
        char yesno[5];
-       char sendcommand[PATH_MAX];
-       int exitcode;
+       int cmdexit = 0;                                // when something fails, set cmdexit to nonzero, and skip to the end
        char cmd[PATH_MAX];
        char buf[PATH_MAX];
        char cmd[PATH_MAX];
        char buf[PATH_MAX];
-       char socket_path[PATH_MAX];
-       char remote_user[256];
-       char remote_host[256];
+       char remote_user[SIZ];
+       char remote_host[SIZ];
        char remote_sendcommand[PATH_MAX];
        char remote_sendcommand[PATH_MAX];
-       FILE *source_artv;
-       FILE *target_artv;
        int linecount = 0;
        int linecount = 0;
-       char spinning[4] = "-\\|/" ;
-       
-       calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
-       CtdlMakeTempFileName(socket_path, sizeof socket_path);
+       int a;
+       int local_admin_socket = (-1);
+       int progress = 0;
+
+       /* Parse command line */
+       while ((a = getopt(argc, argv, "h:")) != EOF) {
+               switch (a) {
+               case 'h':
+                       strcpy(ctdldir, optarg);
+                       break;
+               default:
+                       fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
+                       return(1);
+               }
+       }
+
+       if (chdir(ctdldir) != 0) {
+               fprintf(stderr, "ctdlmigrate: %s: %s\n", ctdldir, strerror(errno));
+               exit(errno);
+       }
+
+       signal(SIGINT, ctdlmigrate_exit);
+       signal(SIGQUIT, ctdlmigrate_exit);
+       signal(SIGTERM, ctdlmigrate_exit);
+       signal(SIGSEGV, ctdlmigrate_exit);
+       signal(SIGHUP, ctdlmigrate_exit);
+       signal(SIGPIPE, ctdlmigrate_exit);
 
 
-       system("clear");
-       printf( "-------------------------------------------\n"
-               "Over-the-wire migration utility for Citadel\n"
-               "-------------------------------------------\n"
+       printf( "\033[2J\033[H\n"
+               "          \033[32m╔═══════════════════════════════════════════════╗\n"
+               "          ║                                               ║\n"
+               "          ║    \033[33mCitadel over-the-wire migration utility    \033[32m║\n"
+               "          ║                                               ║\n"
+               "          ╚═══════════════════════════════════════════════╝\033[0m\n"
                "\n"
                "This utility is designed to migrate your Citadel installation\n"
                "to a new host system via a network connection, without disturbing\n"
                "the source system.  The target may be a different CPU architecture\n"
                "and/or operating system.  The source system should be running\n"
                "\n"
                "This utility is designed to migrate your Citadel installation\n"
                "to a new host system via a network connection, without disturbing\n"
                "the source system.  The target may be a different CPU architecture\n"
                "and/or operating system.  The source system should be running\n"
-               "Citadel %d.%02d or newer, and the target system should be running\n"
+               "Citadel version \033[33m%d\033[0m or newer, and the target system should be running\n"
                "either the same version or a newer version.  You will also need\n"
                "either the same version or a newer version.  You will also need\n"
-               "the 'rsync' utility, and OpenSSH v4 or newer.\n"
+               "the \033[33mrsync\033[0m utility, and OpenSSH v4 or newer.\n"
                "\n"
                "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
                "\n"
                "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
-               "on this system will be ERASED.\n"
-               "\n"
-               "Do you wish to continue? "
-               ,
-               EXPORT_REV_MIN / 100,
-               EXPORT_REV_MIN % 100
+               "on this system will be ERASED.  Your target system will be on this\n"
+               "host in %s.\n"
+               "\n",
+               EXPORT_REV_MIN, ctdldir
        );
 
        );
 
-       if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
-               exit(0);
+       getz(yesno, 1, NULL, "Do you wish to continue? ");
+       puts(yesno);
+       if (tolower(yesno[0]) != 'y') {
+               cmdexit = 1;
        }
 
        }
 
-       printf("\n\nGreat!  First we will check some things out here on our target\n"
-               "system to make sure it is ready to receive data.\n\n");
+       if (!cmdexit) {
+
+               printf( "\033[2J\033[H\n"
+                       "          \033[32m╔═══════════════════════════════════════════════╗\n"
+                       "          ║                                               ║\n"
+                       "          ║       \033[33mValidate source and target systems\033[32m      ║\n"
+                       "          ║                                               ║\n"
+                       "          ╚═══════════════════════════════════════════════╝\033[0m\n"
+                       "\n\n");
+       
+               printf("First we must validate that the local target system is running and ready to receive data.\n");
+               printf("Checking connectivity to Citadel in %s...\n", ctdldir);
+               local_admin_socket = uds_connectsock("citadel-admin.socket");
 
 
-       printf("Locating 'sendcommand' and checking connectivity to Citadel...\n");
-       snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir);
-       snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand);
-       exitcode = system(cmd);
-       if (exitcode != 0) {
-               printf("\nctdlmigrate was unable to attach to the Citadel server\n"
-                       "here on the target system.  Is Citadel running?\n\n");
-               exit(1);
+               serv_gets(local_admin_socket, buf);
+               puts(buf);
+               if (buf[0] != '2') {
+                       cmdexit = 1;
+               }
        }
        }
-       printf("\nOK, this side is ready to go.  Now we must connect to the source system.\n\n");
 
 
-       printf("Enter the host name or IP address of the source system\n"
-               "(example: ctdl.foo.org)\n"
-               "--> ");
-       gets(remote_host);
-       printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
-               "(usually root)\n--> ",
-               remote_host);
-       gets(remote_user);
+       if (!cmdexit) {
+               serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
+               serv_gets(local_admin_socket, buf);
+               puts(buf);
+               if (buf[0] != '2') {
+                       cmdexit = 1;
+               }
+       }
 
 
-       printf("\nEstablishing an SSH connection to the source system...\n\n");
-       unlink(socket_path);
-       snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
-       exitcode = system(cmd);
-       if (exitcode != 0) {
-               printf("\nctdlmigrate was unable to establish an SSH connection to the\n"
-                       "source system, and cannot continue.\n\n");
-               exit(exitcode);
-       }
-
-       printf("\nTesting a command over the connection...\n\n");
-       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
-               socket_path, remote_user, remote_host);
-       exitcode = system(cmd);
-       printf("\n");
-       if (exitcode != 0) {
-               printf("Remote commands are not succeeding.\n\n");
-               exit(exitcode);
-       }
-
-       printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
-       snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
-       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
-               socket_path, remote_user, remote_host, remote_sendcommand);
-       exitcode = system(cmd);
-       if (exitcode != 0) {
-               snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
-               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
-                       socket_path, remote_user, remote_host, remote_sendcommand);
-               exitcode = system(cmd);
-       }
-       if (exitcode != 0) {
-               printf("\nUnable to locate Citadel programs on the remote system.  Please enter\n"
-                       "the name of the directory on %s which contains the 'sendcommand' program.\n"
-                       "(example: /opt/foo/citadel)\n"
-                       "--> ", remote_host);
-               gets(buf);
-               snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
-               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP",
-                       socket_path, remote_user, remote_host, remote_sendcommand);
-               exitcode = system(cmd);
-       }
-       printf("\n");
-       if (exitcode != 0) {
-               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
-               exit(exitcode);
-       }
-
-       printf("ctdlmigrate will now begin a database migration...\n");
-
-       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export",
-               socket_path, remote_user, remote_host, remote_sendcommand);
-       source_artv = popen(cmd, "r");
-       if (!source_artv) {
-               printf("\n%s\n\n", strerror(errno));
-               exit(2);
-       }
-
-       snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
-       target_artv = popen(cmd, "w");
-       if (!target_artv) {
-               printf("\n%s\n\n", strerror(errno));
-               exit(3);
-       }
-
-       while (fgets(buf, sizeof buf, source_artv) != NULL) {
-               fwrite(buf, strlen(buf), 1, target_artv);
-               ++linecount;
-               if ((linecount % 100) == 0) {
-                       printf("%c\r", spinning[((linecount / 100) % 4)]);
-                       fflush(stdout);
-               }
-       }
-
-       pclose(source_artv);
-       pclose(target_artv);
-
-       // FIXME handle -h on both sides
-       // FIXME kill the master ssh session
-       printf("If this program was finished we would do more.  FIXME\n");
-       exit(0);
+       if (!cmdexit) {
+               printf("\n");
+               printf("OK, this side is ready to go.  Now we must connect to the source system.\n\n");
+               printf("Enter the host name or IP address of the source system\n");
+               printf("(example: ctdl.foo.org)\n");
+               getz(remote_host, sizeof remote_host, NULL, "--> ");
+       
+               while (IsEmptyStr(remote_user)) {
+                       printf("\n");
+                       printf("Enter the name of a user on %s who has full access to Citadel files.\n", remote_host);
+                       getz(remote_user, sizeof remote_user, "root", "--> ");
+               }
+
+               printf("\n");
+               printf("Establishing an SSH connection to the source system...\n");
+               sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
+               unlink(socket_path);
+
+               snprintf(cmd, sizeof cmd, "ssh -MNf -S %s -l %s %s", socket_path, remote_user, remote_host);
+               sshpid = fork();
+               if (sshpid < 0) {
+                       printf("%s\n", strerror(errno));
+                       cmdexit = errno;
+               }
+               else if (sshpid == 0) {
+                       execl("/bin/bash", "bash", "-c", cmd, (char *) NULL);
+                       exit(1);
+               }
+               else {                                          // Wait for SSH to go into the background
+                       waitpid(sshpid, &cmdexit, 0);
+               }
+       }
+
+       if (!cmdexit) {
+               printf("\nTesting a command over the connection...\n\n");
+               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'",
+                       socket_path, remote_user, remote_host);
+               cmdexit = system(cmd);
+               printf("\n");
+               if (cmdexit != 0) {
+                       printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
+               }
+       }
+
+       if (!cmdexit) {
+               printf("\nLocating the remote 'sendcommand' and Citadel installation...\n");
+               snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand");
+               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
+               cmdexit = system(cmd);
+
+               if (cmdexit) {
+                       snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand");
+                       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
+                       cmdexit = system(cmd);
+               }
+
+               if (cmdexit) {
+                       printf("\n");
+                       printf("Unable to locate Citadel programs on the remote system.  Please enter\n"
+                               "the name of the directory on %s which contains the 'sendcommand' program.\n"
+                               "(example: /opt/foo/citadel)\n", remote_host);
+                       getz(remote_host, sizeof remote_host, "/usr/local/citadel", "--> ");
+                       snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf);
+                       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand);
+                       cmdexit = system(cmd);
+                       if (!cmdexit) {
+                               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
+                       }
+               }
+       }
+
+       if (!cmdexit) {
+               printf( "\033[2J\033[H\n"
+                       "          \033[32m╔═══════════════════════════════════════════════════════════════════╗\n"
+                       "          ║                                                                   ║\n"
+                       "          ║ \033[33mMigrating from: %-50s\033[32m║\n"
+                       "          ║                                                                   ║\n"
+                       "          ╠═══════════════════════════════════════════════════════════════════╣\n"
+                       "          ║                                                                   ║\n"
+                       "          ║ Lines received: 0                           Percent complete: 0   ║\n"
+                       "          ║                                                                   ║\n"
+                       "          ╚═══════════════════════════════════════════════════════════════════╝\033[0m\n"
+                       "\n", remote_host
+               );
+
+               snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand);
+               sourcefp = popen(cmd, "r");
+               if (!sourcefp) {
+                       cmdexit = errno;
+                       printf("\n%s\n\n", strerror(errno));
+               }
+       }
+
+       if (!cmdexit) {
+               serv_puts(local_admin_socket, "MIGR import");
+               serv_gets(local_admin_socket, buf);
+               if (buf[0] != '4') {
+                       printf("\n\033[31m%s\033[0m\n", buf);
+                       cmdexit = 3;
+               }
+       }
+
+       if (!cmdexit) {
+               char *ptr;
+               time_t last_update = time(NULL);
+               while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
+                       ptr = strchr(buf, '\n');
+                       if (ptr) *ptr = 0;      // remove the newline character
+                       ++linecount;
+                       if (!strncasecmp(buf, "<progress>", 10)) {
+                               progress = atoi(&buf[10]);
+                               printf("\033[8;75H\033[33m%d\033[0m\033[20;0H\n", progress);
+                       }
+                       if (time(NULL) != last_update) {
+                               last_update = time(NULL);
+                               printf("\033[8;29H\033[33m%d\033[0m\033[20;0H\n", linecount);
+                       }
+                       serv_puts(local_admin_socket, buf);
+               }
+       
+               serv_puts(local_admin_socket, "000");
+       }
+
+       if ( (cmdexit == 0) && (progress < 100) ) {
+               printf("\033[31mERROR: source stream ended before 100 percent of data was received.\033[0m\n");
+               ctdlmigrate_exit(86);
+       }
+
+       // FIXME restart the local server now
+
+       ctdlmigrate_exit(cmdexit);
 }
 }