#include <limits.h>
#include <pwd.h>
#include <time.h>
+#include <readline/readline.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <libcitadel.h>
+// 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) {
- char *ptr;
+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));
+}
- ptr = fgets(buf, SIZ, stdin);
- if (!ptr) {
- buf[0] = 0;
- return;
- }
- ptr = strchr(buf, '\n');
- if (ptr) *ptr = 0;
+// 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);
}
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
- fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno));
- exit(3);
+ 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, "sendcommand: can't connect: %s\n", strerror(errno));
+ fprintf(stderr, "ctdlmigrate: can't connect: %s\n", strerror(errno));
close(s);
- exit(3);
+ ctdlmigrate_exit(3);
}
return s;
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];
- FILE *sourcefp = NULL;
int linecount = 0;
int a;
int local_admin_socket = (-1);
- pid_t sshpid = (-1);
+ int progress = 0;
/* Parse command line */
while ((a = getopt(argc, argv, "h:")) != EOF) {
strcpy(ctdldir, optarg);
break;
default:
- fprintf(stderr, "sendcommand: usage: ctdlmigrate [-h server_dir]\n");
+ fprintf(stderr, "ctdlmigrate: usage: ctdlmigrate [-h server_dir]\n");
return(1);
}
}
if (chdir(ctdldir) != 0) {
- fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
+ 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);
+
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? "
- ,
+ "\n",
EXPORT_REV_MIN, ctdldir
);
- if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) {
+ getz(yesno, 1, NULL, "Do you wish to continue? ");
+ puts(yesno);
+ if (tolower(yesno[0]) != 'y') {
cmdexit = 1;
}
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( "\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");
}
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);
+ 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("\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("\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("\nEstablishing an SSH connection to the source system...\n\n");
+ 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);
exit(1);
}
else { // Wait for SSH to go into the background
- waitpid(sshpid, NULL, 0);
+ waitpid(sshpid, &cmdexit, 0);
}
}
cmdexit = system(cmd);
printf("\n");
if (cmdexit != 0) {
- printf("Remote commands are not succeeding.\n\n");
+ printf("\033[31mRemote commands are not succeeding.\033[0m\n\n");
}
}
}
if (cmdexit) {
- printf("\nUnable to locate Citadel programs on the remote system. Please enter\n"
+ 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(buf);
+ "(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("\033[2J\n");
- printf("\033[2;0H\033[33mMigrating from %s\033[0m\n\n", remote_host);
+ 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");
serv_puts(local_admin_socket, "MIGR import");
serv_gets(local_admin_socket, buf);
if (buf[0] != '4') {
- printf("\n%s\n", buf);
+ printf("\n\033[31m%s\033[0m\n", buf);
cmdexit = 3;
}
}
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]));
+ 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[10;0H Lines received: \033[32m%d\033[0m\n", linecount);
+ 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
- pclose(sourcefp);
- printf("\nShutting down the socket connection...\n\n");
- unlink(socket_path);
- kill(sshpid, SIGKILL);
- exit(cmdexit);
+ ctdlmigrate_exit(cmdexit);
}