ctdlmigrate: exit cleanly when a signal is received, tell the user the exit code
[citadel.git] / citadel / ctdlmigrate.c
index 7dbf5d389a8d85f29a7fe2b325a5f98594e2d687..7d9313604fa6e5a7f0ea38d5f9cccf4a577ce4f0 100644 (file)
@@ -1,10 +1,6 @@
 /*
  * 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-2021 citadel.org
  *
  * This program is open source software; you can redistribute it and/or modify
@@ -14,9 +10,6 @@
  * 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.
- *
- * (Note: a useful future enhancement might be to support "-h" on both sides)
- *
  */
 
 #include <stdlib.h>
@@ -45,7 +38,6 @@
 #include "citadel_dirs.h"
 
 
-
 /*
  * Replacement for gets() that doesn't throw a compiler warning.
  * We're only using it for some simple prompts, so we don't need
@@ -65,24 +57,40 @@ void getz(char *buf) {
 }
 
 
+// These variables are used by both main() and ctdlmigrate_exit()
+// They are global so that ctdlmigrate_exit can be called from a signal handler
+char socket_path[PATH_MAX];
+pid_t sshpid = (-1);
+
+void ctdlmigrate_exit(int cmdexit) {
+       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;
-       strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+       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);
+               ctdlmigrate_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);
+               ctdlmigrate_exit(3);
        }
 
        return s;
@@ -162,10 +170,9 @@ void serv_puts(int serv_sock, char *buf) {
 int main(int argc, char *argv[]) {
        char ctdldir[PATH_MAX]=CTDLDIR;
        char yesno[5];
-       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 remote_user[SIZ];
        char remote_host[SIZ];
        char remote_sendcommand[PATH_MAX];
@@ -178,7 +185,7 @@ int main(int argc, char *argv[]) {
        while ((a = getopt(argc, argv, "h:")) != EOF) {
                switch (a) {
                case 'h':
-                       strncpy(ctdldir, optarg, sizeof ctdldir);
+                       strcpy(ctdldir, optarg);
                        break;
                default:
                        fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
@@ -191,157 +198,199 @@ int main(int argc, char *argv[]) {
                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);
+
        printf( "\033[2J\033[H\n"
-               "-------------------------------------------\n"
-               "Over-the-wire migration utility for Citadel\n"
-               "-------------------------------------------\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"
-               "Citadel version %d 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"
-               "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"
                "on this system will be ERASED.  Your target system will be on this\n"
                "host in %s.\n"
                "\n"
-               "Do you wish to continue? "
+               "\033[33mDo you wish to continue? \033[0m"
                ,
                EXPORT_REV_MIN, ctdldir
        );
 
        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("Checking connectivity to Citadel in %s...\n", ctdldir);
-       local_admin_socket = uds_connectsock("citadel-admin.socket");
-
-
-       serv_gets(local_admin_socket, buf);
-       puts(buf);
-       if (buf[0] != '2') {
-               exit(1);
-       }
-       serv_puts(local_admin_socket, "ECHO Connection to Citadel Server succeeded.");
-       serv_gets(local_admin_socket, buf);
-       puts(buf);
-       if (buf[0] != '2') {
-               exit(1);
+       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");
+
+               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"
-               "--> ");
-       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);
+       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");
-       sprintf(socket_path, "/tmp/ctdlmigrate.XXXXXX");
-       mktemp(socket_path);
-       unlink(socket_path);
-       snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host);
-       cmdexit = system(cmd);
-       printf("\n");
-       if (cmdexit != 0) {
-               printf("This program was unable to establish an SSH session to the source system.\n\n");
-               exit(cmdexit);
-       }
+       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("\nEstablishing an SSH connection to the source system...\n\n");
+               sprintf(socket_path, "/tmp/ctdlmigrate-socket.%ld.%d", time(NULL), getpid());
+               unlink(socket_path);
 
-       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");
-               exit(cmdexit);
+               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);
+               }
        }
 
-       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);
+               printf("\n");
+               if (cmdexit != 0) {
+                       printf("\033[31mRemote commands are not succeeding.\033[0m\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);
-       }
-       printf("\n");
-       if (cmdexit != 0) {
-               printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n");
-               exit(cmdexit);
-       }
 
-       printf("\033[2J\n");
-       printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
+               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));
-               exit(2);
+               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");
+                       }
+               }
        }
 
-       serv_puts(local_admin_socket, "MIGR import");
-       serv_gets(local_admin_socket, buf);
-       if (buf[0] != '4') {
-               printf("\n%s\n", buf);
-               exit(3);
+       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));
+               }
        }
 
-       char *ptr;
-       time_t time_started = time(NULL);
-       time_t last_update = time(NULL);
-       while (ptr = fgets(buf, SIZ, sourcefp), (ptr != NULL)) {
-               ptr = strchr(buf, '\n');
-               if (ptr) *ptr = 0;
-               ++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);
+       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;
                }
-               serv_puts(local_admin_socket, buf);
        }
 
-       serv_puts(local_admin_socket, "000");
+       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[8;75H\033[33m%d\033[0m\033[20;0H\n", atoi(&buf[10]));
+                       }
+                       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");
+       }
 
        // FIXME restart the local server now
 
-       pclose(sourcefp);
-       printf("\nShutting down the socket connection...\n\n");
-       snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s", socket_path, remote_user, remote_host);
-       system(cmd);
-       unlink(socket_path);
-       exit(0);
+       if (sourcefp) {
+               printf("Closing the data connection from the source system...\n");
+               pclose(sourcefp);
+       }
+
+       ctdlmigrate_exit(cmdexit);
 }