]> code.citadel.org Git - citadel.git/blobdiff - citadel/ctdlmigrate.c
ctdlmigrate: changed flow of program to avoid gotos, changed SSH to fork()
[citadel.git] / citadel / ctdlmigrate.c
index ad50ea9d9dfec03bdced89ef60b2b306985b3d09..78af852bd6bdf80349daae4a4b30af0910a80c23 100644 (file)
@@ -1,18 +1,15 @@
 /*
 /*
- * $Id$
- *
  * Across-the-wire migration utility for Citadel
  *
  * Across-the-wire migration utility for Citadel
  *
- * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here.
- * The scope of this program isn't wide enough to make a difference.  If you don't like
- * it you can rewrite it.
- *
- * Copyright (c) 2009 citadel.org
- *
- * This program is licensed to you under the terms of the GNU General Public License v3
+ * Copyright (c) 2009-2021 citadel.org
  *
  *
- * FIXME handle -h on both sides
+ * 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 <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
 
 
 
 
 
 
@@ -51,7 +47,7 @@
 void getz(char *buf) {
        char *ptr;
 
 void getz(char *buf) {
        char *ptr;
 
-       ptr = fgets(buf, 32767, stdin);
+       ptr = fgets(buf, SIZ, stdin);
        if (!ptr) {
                buf[0] = 0;
                return;
        if (!ptr) {
                buf[0] = 0;
                return;
@@ -62,36 +58,135 @@ void getz(char *buf) {
 }
 
 
 }
 
 
+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, "sendcommand: Can't create socket: %s\n", strerror(errno));
+               exit(3);
+       }
+
+       if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno));
+               close(s);
+               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;
+       }
+}
 
 
 
 
+/*
+ * 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;
+       }
+}
+
 
 
-int main(int argc, char *argv[])
-{
-       int relh=0;
-       int home=0;
-       char relhome[PATH_MAX]="";
+/*
+ * 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 cmdexit;
+       int cmdexit = 0;                                // when something fails, set cmdexit to nonzero, and skip to the end
        char cmd[PATH_MAX];
        char buf[PATH_MAX];
        char socket_path[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];
        FILE *sourcefp = NULL;
        char remote_sendcommand[PATH_MAX];
        FILE *sourcefp = NULL;
-       FILE *targetfp = NULL;
        int linecount = 0;
        int linecount = 0;
-       char spinning[4] = "-\\|/" ;
-       int exitcode = 0;
-       pid_t sshpid;
-       
-       calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
-       CtdlMakeTempFileName(socket_path, sizeof socket_path);
+       int a;
+       int local_admin_socket = (-1);
+       pid_t sshpid = (-1);
 
 
-       cmdexit = system("clear");
-       printf( "-------------------------------------------\n"
+       /* Parse command line */
+       while ((a = getopt(argc, argv, "h:")) != EOF) {
+               switch (a) {
+               case 'h':
+                       strcpy(ctdldir, optarg);
+                       break;
+               default:
+                       fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
+                       return(1);
+               }
+       }
+
+       if (chdir(ctdldir) != 0) {
+               fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
+               exit(errno);
+       }
+
+       printf( "\033[2J\033[H\n"
+               "-------------------------------------------\n"
                "Over-the-wire migration utility for Citadel\n"
                "-------------------------------------------\n"
                "\n"
                "Over-the-wire migration utility for Citadel\n"
                "-------------------------------------------\n"
                "\n"
@@ -99,216 +194,163 @@ int main(int argc, char *argv[])
                "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"
                "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 %d or newer, and the target system should be running\n"
                "either the same version or a newer version.  You will also need\n"
                "the 'rsync' utility, and OpenSSH v4 or newer.\n"
                "\n"
                "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
                "either the same version or a newer version.  You will also need\n"
                "the 'rsync' utility, and OpenSSH v4 or newer.\n"
                "\n"
                "You must run this utility on the TARGET SYSTEM.  Any existing data\n"
-               "on this system will be ERASED.\n"
+               "on this system will be ERASED.  Your target system will be on this\n"
+               "host in %s.\n"
                "\n"
                "Do you wish to continue? "
                ,
                "\n"
                "Do you wish to continue? "
                ,
-               EXPORT_REV_MIN / 100,
-               EXPORT_REV_MIN % 100
+               EXPORT_REV_MIN, ctdldir
        );
 
        if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
        );
 
        if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
-               exit(0);
+               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");
-
-       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);
-       cmdexit = system(cmd);
-       if (cmdexit != 0) {
-               printf("\nctdlmigrate was unable to attach to the Citadel server\n"
-                       "here on the target system.  Is Citadel running?\n\n");
-               exit(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"
-               "--> ");
-       getz(remote_host);
-       printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
-               "(usually root)\n--> ",
-               remote_host);
-       getz(remote_user);
-
-       sshpid = fork();
-       if (sshpid < 0)
-       {
-               printf("\n%s\n", strerror(errno));
-               exitcode = errno;
-               goto THEEND;
-       }
-       else if (sshpid == 0)
-       {
-               printf("\nEstablishing an SSH connection to the source system...\n\n");
-               unlink(socket_path);
-               snprintf(cmd, sizeof cmd, "%s@%s", remote_user, remote_host);
-               execlp("ssh", "ssh", "-MNf", "-S", socket_path, cmd, NULL);
-               cmdexit = errno;
-               printf("\n%s\n", strerror(cmdexit));
-               exit(cmdexit);          /* child process exits */
-       }
+       if (!cmdexit) {
+               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");
+       
+               printf("Checking connectivity to Citadel in %s...\n", ctdldir);
+               local_admin_socket = uds_connectsock("citadel-admin.socket");
 
 
-       /* If we get here we are the parent process */
-       if (waitpid(sshpid, &cmdexit, 0) <= 0) {
-               exitcode = errno;
-               printf("\n%s\n", strerror(errno));
-               goto THEEND;
+               serv_gets(local_admin_socket, buf);
+               puts(buf);
+               if (buf[0] != '2') {
+                       cmdexit = 1;
+               }
        }
 
        }
 
-       if (WIFSIGNALED(cmdexit)) {
-               exitcode = errno;
-               printf("\n%s\n", strerror(errno));
-               goto THEEND;
+       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;
+               }
        }
 
        }
 
-       if ((WIFEXITED(cmdexit)) && (WEXITSTATUS(cmdexit) != 0)) {
-               exitcode = WEXITSTATUS(cmdexit);
-               printf("\n%s\n", strerror(errno));
-               goto THEEND;
-       }
+       if (!cmdexit) {
+               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"
+                       "--> ");
+               getz(remote_host);
+       
+               while (IsEmptyStr(remote_user)) {
+                       printf("\nEnter the name of a user on %s who has full access to Citadel files\n"
+                               "(usually root)\n--> ",
+                               remote_host);
+                       getz(remote_user);
+               }
 
 
-       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("Remote commands are not succeeding.\n\n");
-               exitcode = cmdexit;
-               goto THEEND;
+               printf("\nEstablishing an SSH connection to the source system...\n\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, NULL, 0);
+               }
        }
 
        }
 
-       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 != 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);
+       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);
                cmdexit = system(cmd);
+               printf("\n");
+               if (cmdexit != 0) {
+                       printf("Remote commands are not succeeding.\n\n");
+               }
        }
        }
-       if (cmdexit != 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);
-               getz(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);
+
+       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);
                cmdexit = system(cmd);
-       }
-       printf("\n");
-       if (cmdexit != 0) {
-               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
-               exitcode = cmdexit;
-               goto THEEND;
-       }
 
 
-       printf("ctdlmigrate will now begin a database migration...\n");
+               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);
+               }
 
 
-       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) {
-               printf("\n%s\n\n", strerror(errno));
-               exitcode = 2;
-               goto THEEND;
+               if (cmdexit) {
+                       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);
+                       getz(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);
+                       cmdexit = system(cmd);
+                       if (!cmdexit) {
+                               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
+                       }
+               }
        }
 
        }
 
-       snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand);
-       targetfp = popen(cmd, "w");
-       if (!targetfp) {
-               printf("\n%s\n\n", strerror(errno));
-               exitcode = 3;
-               goto THEEND;
-       }
+       if (!cmdexit) {
+               printf("\033[2J\n");
+               printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
 
 
-       while (fgets(buf, sizeof buf, sourcefp) != NULL) {
-               if (fwrite(buf, strlen(buf), 1, targetfp) < 1) {
-                       exitcode = 4;
-                       printf("%s\n", strerror(errno));
-                       goto FAIL;
-               }
-               ++linecount;
-               if ((linecount % 100) == 0) {
-                       printf("%c\r", spinning[((linecount / 100) % 4)]);
-                       fflush(stdout);
+               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));
                }
        }
 
                }
        }
 
-FAIL:  if (sourcefp) pclose(sourcefp);
-       if (targetfp) pclose(targetfp);
-       if (exitcode != 0) goto THEEND;
-
-       /* We need to copy a bunch of other stuff, and will do so using rsync */
-
-       snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs",
-               socket_path, remote_user, remote_host, remote_sendcommand);
-       sourcefp = popen(cmd, "r");
-       if (!sourcefp) {
-               printf("\n%s\n\n", strerror(errno));
-               exitcode = 2;
-               goto THEEND;
+       if (!cmdexit) {
+               serv_puts(local_admin_socket, "MIGR import");
+               serv_gets(local_admin_socket, buf);
+               if (buf[0] != '4') {
+                       printf("\n%s\n", buf);
+                       cmdexit = 3;
+               }
        }
        }
-       while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) {
-               buf[strlen(buf)-1] = 0;
 
 
-               if (!strncasecmp(buf, "bio|", 4)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[4], ctdl_bio_dir);
-               }
-               else if (!strncasecmp(buf, "files|", 6)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir);
-               }
-               else if (!strncasecmp(buf, "userpics|", 9)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[9], ctdl_usrpic_dir);
-               }
-               else if (!strncasecmp(buf, "messages|", 9)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir);
-               }
-               else if (!strncasecmp(buf, "netconfigs|", 11)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[11], ctdl_netcfg_dir);
-               }
-               else if (!strncasecmp(buf, "keys|", 5)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir);
-               }
-               else if (!strncasecmp(buf, "images|", 7)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir);
-               }
-               else if (!strncasecmp(buf, "info|", 5)) {
-                       snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/",
-                               socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir);
-               }
-               else {
-                       strcpy(cmd, "false");   /* cheap and sleazy way to throw an error */
-               }
-               printf("%s\n", cmd);
-               cmdexit = system(cmd);
-               if (cmdexit != 0) {
-                       exitcode += cmdexit;
+       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)) {
+                               printf("\033[11;0HPercent complete: \033[32m%d\033[0m\n", atoi(&buf[10]));
+                       }
+                       if (time(NULL) != last_update) {
+                               last_update = time(NULL);
+                               printf("\033[10;0H  Lines received: \033[32m%d\033[0m\n", linecount);
+                       }
+                       serv_puts(local_admin_socket, buf);
                }
                }
+       
+               serv_puts(local_admin_socket, "000");
        }
        }
-       pclose(sourcefp);
 
 
-THEEND:        kill(sshpid, SIGKILL);
+       // FIXME restart the local server now
+
+       pclose(sourcefp);
+       printf("\nShutting down the socket connection...\n\n");
        unlink(socket_path);
        unlink(socket_path);
-       exit(exitcode);
+       kill(sshpid, SIGKILL);
+       exit(cmdexit);
 }
 }