- djm@cvs.openbsd.org 2004/05/19 12:17:33
     [sftp-client.c sftp.c]
     gracefully abort transfers on receipt of SIGINT, also ignore SIGINT while
     waiting for a command; ok markus@
diff --git a/sftp.c b/sftp.c
index a47ccf5..0bc68f0 100644
--- a/sftp.c
+++ b/sftp.c
@@ -16,7 +16,7 @@
 
 #include "includes.h"
 
-RCSID("$OpenBSD: sftp.c,v 1.45 2004/03/03 09:31:20 djm Exp $");
+RCSID("$OpenBSD: sftp.c,v 1.46 2004/05/19 12:17:33 djm Exp $");
 
 #include "buffer.h"
 #include "xmalloc.h"
@@ -46,6 +46,9 @@
 /* This is set to 0 if the progressmeter is not desired. */
 int showprogress = 1;
 
+/* SIGINT received during command processing */
+volatile sig_atomic_t interrupted = 0;
+
 int remote_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
 
@@ -131,6 +134,24 @@
 int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
 
 static void
+killchild(int signo)
+{
+	if (sshpid > 1)
+		kill(sshpid, SIGTERM);
+
+	_exit(1);
+}
+
+static void
+cmd_interrupt(int signo)
+{
+	const char msg[] = "\rInterrupt  \n";
+
+	write(STDERR_FILENO, msg, sizeof(msg) - 1);
+	interrupted = 1;
+}
+
+static void
 help(void)
 {
 	printf("Available commands:\n");
@@ -465,7 +486,7 @@
 		goto out;
 	}
 
-	for (i = 0; g.gl_pathv[i]; i++) {
+	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 		if (infer_path(g.gl_pathv[i], &tmp)) {
 			err = -1;
 			goto out;
@@ -534,7 +555,7 @@
 		goto out;
 	}
 
-	for (i = 0; g.gl_pathv[i]; i++) {
+	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 		if (!is_reg(g.gl_pathv[i])) {
 			error("skipping non-regular file %s",
 			    g.gl_pathv[i]);
@@ -621,7 +642,7 @@
 
 	qsort(d, n, sizeof(*d), sdirent_comp);
 
-	for (n = 0; d[n] != NULL; n++) {
+	for (n = 0; d[n] != NULL && !interrupted; n++) {
 		char *tmp, *fname;
 
 		tmp = path_append(path, d[n]->filename);
@@ -673,6 +694,9 @@
 		return (-1);
 	}
 
+	if (interrupted)
+		goto out;
+
 	/*
 	 * If the glob returns a single match, which is the same as the
 	 * input glob, and it is a directory, then just list its contents
@@ -706,7 +730,7 @@
 		colspace = width / columns;
 	}
 
-	for (i = 0; g.gl_pathv[i]; i++) {
+	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 		char *fname;
 
 		fname = path_strip(g.gl_pathv[i], strip_path);
@@ -743,6 +767,7 @@
 	if (!(lflag & LONG_VIEW) && (c != 1))
 		printf("\n");
 
+ out:
 	if (g.gl_pathc)
 		globfree(&g);
 
@@ -952,7 +977,7 @@
 	case I_RM:
 		path1 = make_absolute(path1, *pwd);
 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
+		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			printf("Removing %s\n", g.gl_pathv[i]);
 			err = do_rm(conn, g.gl_pathv[i]);
 			if (err != 0 && err_abort)
@@ -1041,7 +1066,7 @@
 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
 		a.perm = n_arg;
 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
+		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			printf("Changing mode on %s\n", g.gl_pathv[i]);
 			err = do_setstat(conn, g.gl_pathv[i], &a);
 			if (err != 0 && err_abort)
@@ -1052,7 +1077,7 @@
 	case I_CHGRP:
 		path1 = make_absolute(path1, *pwd);
 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
+		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
 				if (err != 0 && err_abort)
 					break;
@@ -1180,6 +1205,8 @@
 	for (;;) {
 		char *cp;
 
+		signal(SIGINT, SIG_IGN);
+
 		printf("sftp> ");
 
 		/* XXX: use libedit */
@@ -1195,6 +1222,10 @@
 		if (cp)
 			*cp = '\0';
 
+		/* Handle user interrupts gracefully during commands */
+		interrupted = 0;
+		signal(SIGINT, cmd_interrupt);
+
 		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
 		if (err != 0)
 			break;
@@ -1206,15 +1237,6 @@
 }
 
 static void
-killchild(int signo)
-{
-	if (sshpid > 1)
-		kill(sshpid, signo);
-
-	_exit(1);
-}
-
-static void
 connect_to_server(char *path, char **args, int *in, int *out)
 {
 	int c_in, c_out;
@@ -1249,6 +1271,14 @@
 		close(*out);
 		close(c_in);
 		close(c_out);
+
+		/*
+		 * The underlying ssh is in the same process group, so we must
+		 * ignore SIGINT if we want to gracefully abort commands, 
+		 * otherwise the signal will make it to the ssh process and 
+		 * kill it too
+		 */
+		signal(SIGINT, SIG_IGN);
 		execv(path, args);
 		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
 		exit(1);