[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#158590: reget support for sftp



tags 158590 +patch
thanks

These patches will add a "reget" command to the current sid and sarge
versions of sftp.  They are not thoroughly tested and could do with some
cleaning up, but they appear to work.

The tricky bit, as was explained to me when I raised this issue
originally, is not in resuming a download, but in preventing an
incompletely downloaded file from having gaps.  sftp can send multiple
concurrent requests for several blocks of a file and may receive them
out of order, so it is necessary for it to keep track of any gaps and
truncate the file at the first gap if a download is aborted.  (Actually
it could hold onto isolated blocks in memory, but I'm not sure that the
resulting higher memory consumption would be acceptable.)

I believe I have ensured that the truncation will be done even if a
download is aborted by a signal or fatal error, but I'm not entirely
happy about the way that's done.  It's also difficult to test this as I
believe that it is uncommon for servers to send blocks out of order.

Ben.

-- 
Ben Hutchings
The two most common things in the universe are hydrogen and stupidity.
diff -ur openssh-4.2p1-5/progressmeter.c openssh-4.2p1/progressmeter.c
--- openssh-4.2p1-5/progressmeter.c	2005-06-16 04:18:05.000000000 +0100
+++ openssh-4.2p1/progressmeter.c	2005-12-29 19:03:10.000000000 +0000
@@ -240,7 +240,7 @@
 	start = last_update = time(NULL);
 	file = f;
 	end_pos = filesize;
-	cur_pos = 0;
+	cur_pos = *ctr;
 	counter = ctr;
 	stalled = 0;
 	bytes_per_second = 0;
diff -ur openssh-4.2p1-5/sftp.c openssh-4.2p1/sftp.c
--- openssh-4.2p1-5/sftp.c	2005-08-22 23:06:56.000000000 +0100
+++ openssh-4.2p1/sftp.c	2005-12-29 19:02:32.000000000 +0000
@@ -103,6 +103,7 @@
 #define I_SYMLINK	21
 #define I_VERSION	22
 #define I_PROGRESS	23
+#define I_REGET		24
 
 struct CMD {
 	const char *c;
@@ -119,6 +120,7 @@
 	{ "dir",	I_LS },
 	{ "exit",	I_QUIT },
 	{ "get",	I_GET },
+	{ "reget",	I_REGET },
 	{ "mget",	I_GET },
 	{ "help",	I_HELP },
 	{ "lcd",	I_LCHDIR },
@@ -502,7 +504,8 @@
 }
 
 static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag,
+	    int rflag)
 {
 	char *abs_src = NULL;
 	char *abs_dst = NULL;
@@ -554,7 +557,8 @@
 			abs_dst = tmp;
 
 		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
+		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag, rflag)
+		    == -1)
 			err = -1;
 		xfree(abs_dst);
 		abs_dst = NULL;
@@ -897,6 +901,7 @@
 	*path1 = *path2 = NULL;
 	switch (cmdnum) {
 	case I_GET:
+	case I_REGET:
 	case I_PUT:
 		if (parse_getput_flags(&cp, pflag))
 			return(-1);
@@ -1030,7 +1035,9 @@
 		err = -1;
 		break;
 	case I_GET:
-		err = process_get(conn, path1, path2, *pwd, pflag);
+	case I_REGET:
+		err = process_get(conn, path1, path2, *pwd, pflag,
+				  cmdnum == I_REGET);
 		break;
 	case I_PUT:
 		err = process_put(conn, path1, path2, *pwd, pflag);
diff -ur openssh-4.2p1-5/sftp-client.c openssh-4.2p1/sftp-client.c
--- openssh-4.2p1-5/sftp-client.c	2005-08-02 08:07:08.000000000 +0100
+++ openssh-4.2p1/sftp-client.c	2005-12-29 19:02:32.000000000 +0000
@@ -735,9 +735,15 @@
 	buffer_free(&msg);
 }
 
+/* Variables used for removing isolated fragments of the downloaded file
+ * in case of a signal or fatal error */
+static volatile int truncate_fd = -1;
+static volatile u_int32_t truncate_len_upper;
+static volatile u_int32_t truncate_len_lower;
+
 int
 do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
-    int pflag)
+    int pflag, int rflag)
 {
 	Attrib junk, *a;
 	Buffer msg;
@@ -753,10 +759,12 @@
 		u_int64_t offset;
 		TAILQ_ENTRY(request) tq;
 	};
-	TAILQ_HEAD(reqhead, request) requests;
+	TAILQ_HEAD(reqhead, request) requests, fragments;
+	u_int64_t contig_len;
 	struct request *req;
 
 	TAILQ_INIT(&requests);
+	TAILQ_INIT(&fragments);
 
 	a = do_stat(conn, remote_path, 0);
 	if (a == NULL)
@@ -799,7 +807,8 @@
 		return(-1);
 	}
 
-	local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
+	local_fd = open(local_path,
+	    O_WRONLY | O_CREAT | (rflag ? 0 : O_TRUNC),
 	    mode | S_IWRITE);
 	if (local_fd == -1) {
 		error("Couldn't open local file \"%s\" for writing: %s",
@@ -808,11 +817,32 @@
 		xfree(handle);
 		return(-1);
 	}
+	if (rflag) {
+		struct stat stat_buf;
+		if (fstat(local_fd, &stat_buf) == -1) {
+			error("Couldn't stat local file \"%s\": %s",
+			   local_path, strerror(errno));
+			close(local_fd);
+			buffer_free(&msg);
+			xfree(handle);
+			return(-1);
+		}
+		offset = stat_buf.st_size;
+	} else {
+		offset = 0;
+	}
 
 	/* Read from remote and write to local */
-	write_error = read_error = write_errno = num_req = offset = 0;
+	write_error = read_error = write_errno = num_req = 0;
 	max_req = 1;
-	progress_counter = 0;
+	progress_counter = contig_len = offset;
+
+	truncate_len_lower = contig_len;
+	truncate_len_upper = contig_len >> 32;
+	truncate_fd = local_fd;
+	signal(SIGTERM, cleanup_exit);
+	signal(SIGINT, cleanup_exit);
+	signal(SIGHUP, cleanup_exit);
 
 	if (showprogress && size != 0)
 		start_progress_meter(remote_path, size, &progress_counter);
@@ -892,9 +922,52 @@
 
 			if (len == req->len) {
 				TAILQ_REMOVE(&requests, req, tq);
-				xfree(req);
 				num_req--;
+			}
+
+			/* Update contig_len and/or fragments */
+			if (req->offset == contig_len) {
+				struct request *frag, *next_frag;
+				contig_len += len;
+				if (len == req->len)
+					xfree(req);
+				for (frag = TAILQ_FIRST(&fragments);
+				     frag && frag->offset == contig_len;
+				     frag = next_frag) {
+					next_frag = TAILQ_NEXT(frag, tq);
+					contig_len += frag->len;
+					TAILQ_REMOVE(&fragments, frag, tq);
+					xfree(frag);
+				}
+				truncate_len_lower = contig_len;
+				truncate_len_upper = contig_len >> 32;
 			} else {
+				struct request *frag, *new_frag;
+				if (len == req->len) {
+					new_frag = req;
+				} else {
+					/* Request will stay in the queue
+					 * so we need to duplicate it */
+					new_frag = xmalloc(sizeof(*req));
+					*new_frag = *req;
+					/* Set fragment len to actual len */
+					new_frag->len = len;
+				}
+				/* Insert the fragment at the appropriate
+				 * point in the list (ordered by offset) */
+				for (frag = TAILQ_FIRST(&fragments);
+				     frag && frag->offset < req->offset;
+				     frag = TAILQ_NEXT(frag, tq))
+					;
+				if (frag)
+					TAILQ_INSERT_BEFORE(frag, new_frag,
+							    tq);
+				else
+					TAILQ_INSERT_TAIL(&fragments, new_frag,
+							  tq);
+			}
+
+			if (len != req->len) {
 				/* Resend the request for the missing data */
 				debug3("Short data block, re-requesting "
 				    "%llu -> %llu (%2d)",
@@ -936,6 +1009,24 @@
 	if (TAILQ_FIRST(&requests) != NULL)
 		fatal("Transfer complete, but requests still in queue");
 
+	if (contig_len != progress_counter) {
+		struct request *frag, *next_frag;
+		for (frag = TAILQ_FIRST(&fragments);
+		     frag != NULL;
+		     frag = next_frag) {
+			next_frag = TAILQ_NEXT(frag, tq);
+			TAILQ_REMOVE(&fragments, frag, tq);
+			xfree(frag);
+		}
+		if (ftruncate(local_fd, contig_len) == -1) {
+			error("Couldn't truncate \"%s\": %s"
+			      "; there may be gaps in it",
+			      local_path, strerror(errno));
+		}
+	}
+
+	truncate_fd = -1;
+
 	if (read_error) {
 		error("Couldn't read from remote file \"%s\" : %s",
 		    remote_path, fx2txt(status));
@@ -1155,3 +1246,13 @@
 	buffer_free(&msg);
 	return(status);
 }
+
+void
+cleanup_exit(int i)
+{
+	// XXX: error reporting?
+	ftruncate(truncate_fd,
+	    ((u_int64_t)truncate_len_upper << 32) | truncate_len_lower);
+
+	_exit(i);
+}
diff -ur openssh-4.2p1-5/sftp-client.h openssh-4.2p1/sftp-client.h
--- openssh-4.2p1-5/sftp-client.h	2005-05-26 03:05:49.000000000 +0100
+++ openssh-4.2p1/sftp-client.h	2005-12-29 19:02:32.000000000 +0000
@@ -86,9 +86,10 @@
 
 /*
  * Download 'remote_path' to 'local_path'. Preserve permissions and times
- * if 'pflag' is set
+ * if 'pflag' is set. If 'rflag' is set, assume the contents of the local
+ * file (if it exists) are a prefix of the remote file.
  */
-int do_download(struct sftp_conn *, char *, char *, int);
+int do_download(struct sftp_conn *, char *, char *, int, int);
 
 /*
  * Upload 'local_path' to 'remote_path'. Preserve permissions and times
--- openssh-3.8.1p1-8.sarge.4/progressmeter.c	2004-02-06 05:41:37.000000000 +0000
+++ openssh-3.8.1p1/progressmeter.c	2005-12-29 18:18:08.000000000 +0000
@@ -231,7 +231,7 @@
 	start = last_update = time(NULL);
 	file = f;
 	end_pos = filesize;
-	cur_pos = 0;
+	cur_pos = *stat;
 	counter = stat;
 	stalled = 0;
 	bytes_per_second = 0;
diff -ur openssh-3.8.1p1-8.sarge.4/sftp.c openssh-3.8.1p1/sftp.c
--- openssh-3.8.1p1-8.sarge.4/sftp.c	2004-03-08 12:12:19.000000000 +0000
+++ openssh-3.8.1p1/sftp.c	2005-12-29 17:45:43.000000000 +0000
@@ -86,6 +86,7 @@
 #define I_SYMLINK	21
 #define I_VERSION	22
 #define I_PROGRESS	23
+#define I_REGET		24
 
 struct CMD {
 	const char *c;
@@ -102,6 +103,7 @@
 	{ "dir",	I_LS },
 	{ "exit",	I_QUIT },
 	{ "get",	I_GET },
+	{ "reget",	I_REGET },
 	{ "mget",	I_GET },
 	{ "help",	I_HELP },
 	{ "lcd",	I_LCHDIR },
@@ -437,7 +439,8 @@
 }
 
 static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag,
+	    int rflag)
 {
 	char *abs_src = NULL;
 	char *abs_dst = NULL;
@@ -489,7 +492,8 @@
 			abs_dst = tmp;
 
 		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
+		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag, rflag)
+		    == -1)
 			err = -1;
 		xfree(abs_dst);
 		abs_dst = NULL;
@@ -802,6 +806,7 @@
 	*path1 = *path2 = NULL;
 	switch (cmdnum) {
 	case I_GET:
+	case I_REGET:
 	case I_PUT:
 		if (parse_getput_flags(&cp, pflag))
 			return(-1);
@@ -935,7 +940,9 @@
 		err = -1;
 		break;
 	case I_GET:
-		err = process_get(conn, path1, path2, *pwd, pflag);
+	case I_REGET:
+		err = process_get(conn, path1, path2, *pwd, pflag,
+				  cmdnum == I_REGET);
 		break;
 	case I_PUT:
 		err = process_put(conn, path1, path2, *pwd, pflag);
diff -ur openssh-3.8.1p1-8.sarge.4/sftp-client.c openssh-3.8.1p1/sftp-client.c
--- openssh-3.8.1p1-8.sarge.4/sftp-client.c	2004-03-08 12:12:02.000000000 +0000
+++ openssh-3.8.1p1/sftp-client.c	2005-12-29 18:40:22.000000000 +0000
@@ -726,9 +726,15 @@
 	buffer_free(&msg);
 }
 
+/* Variables used for removing isolated fragments of the downloaded file
+ * in case of a signal or fatal error */
+static volatile int truncate_fd = -1;
+static volatile u_int32_t truncate_len_upper;
+static volatile u_int32_t truncate_len_lower;
+
 int
 do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
-    int pflag)
+    int pflag, int rflag)
 {
 	Attrib junk, *a;
 	Buffer msg;
@@ -744,10 +750,12 @@
 		u_int64_t offset;
 		TAILQ_ENTRY(request) tq;
 	};
-	TAILQ_HEAD(reqhead, request) requests;
+	TAILQ_HEAD(reqhead, request) requests, fragments;
+	u_int64_t contig_len;
 	struct request *req;
 
 	TAILQ_INIT(&requests);
+	TAILQ_INIT(&fragments);
 
 	a = do_stat(conn, remote_path, 0);
 	if (a == NULL)
@@ -790,7 +798,8 @@
 		return(-1);
 	}
 
-	local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
+	local_fd = open(local_path,
+	    O_WRONLY | O_CREAT | (rflag ? 0 : O_TRUNC),
 	    mode | S_IWRITE);
 	if (local_fd == -1) {
 		error("Couldn't open local file \"%s\" for writing: %s",
@@ -799,11 +808,32 @@
 		xfree(handle);
 		return(-1);
 	}
+	if (rflag) {
+		struct stat stat_buf;
+		if (fstat(local_fd, &stat_buf) == -1) {
+			error("Couldn't stat local file \"%s\": %s",
+			   local_path, strerror(errno));
+			close(local_fd);
+			buffer_free(&msg);
+			xfree(handle);
+			return(-1);
+		}
+		offset = stat_buf.st_size;
+	} else {
+		offset = 0;
+	}
 
 	/* Read from remote and write to local */
-	write_error = read_error = write_errno = num_req = offset = 0;
+	write_error = read_error = write_errno = num_req = 0;
 	max_req = 1;
-	progress_counter = 0;
+	progress_counter = contig_len = offset;
+
+	truncate_len_lower = contig_len;
+	truncate_len_upper = contig_len >> 32;
+	truncate_fd = local_fd;
+	signal(SIGTERM, cleanup_exit);
+	signal(SIGINT, cleanup_exit);
+	signal(SIGHUP, cleanup_exit);
 
 	if (showprogress && size != 0)
 		start_progress_meter(remote_path, size, &progress_counter);
@@ -873,9 +903,52 @@
 
 			if (len == req->len) {
 				TAILQ_REMOVE(&requests, req, tq);
-				xfree(req);
 				num_req--;
+			}
+
+			/* Update contig_len and/or fragments */
+			if (req->offset == contig_len) {
+				struct request *frag, *next_frag;
+				contig_len += len;
+				if (len == req->len)
+					xfree(req);
+				for (frag = TAILQ_FIRST(&fragments);
+				     frag && frag->offset == contig_len;
+				     frag = next_frag) {
+					next_frag = TAILQ_NEXT(frag, tq);
+					contig_len += frag->len;
+					TAILQ_REMOVE(&fragments, frag, tq);
+					xfree(frag);
+				}
+				truncate_len_lower = contig_len;
+				truncate_len_upper = contig_len >> 32;
 			} else {
+				struct request *frag, *new_frag;
+				if (len == req->len) {
+					new_frag = req;
+				} else {
+					/* Request will stay in the queue
+					 * so we need to duplicate it */
+					new_frag = xmalloc(sizeof(*req));
+					*new_frag = *req;
+					/* Set fragment len to actual len */
+					new_frag->len = len;
+				}
+				/* Insert the fragment at the appropriate
+				 * point in the list (ordered by offset) */
+				for (frag = TAILQ_FIRST(&fragments);
+				     frag && frag->offset < req->offset;
+				     frag = TAILQ_NEXT(frag, tq))
+					;
+				if (frag)
+					TAILQ_INSERT_BEFORE(frag, new_frag,
+							    tq);
+				else
+					TAILQ_INSERT_TAIL(&fragments, new_frag,
+							  tq);
+			}
+
+			if (len != req->len) {
 				/* Resend the request for the missing data */
 				debug3("Short data block, re-requesting "
 				    "%llu -> %llu (%2d)",
@@ -918,6 +991,24 @@
 	if (TAILQ_FIRST(&requests) != NULL)
 		fatal("Transfer complete, but requests still in queue");
 
+	if (contig_len != progress_counter) {
+		struct request *frag, *next_frag;
+		for (frag = TAILQ_FIRST(&fragments);
+		     frag != NULL;
+		     frag = next_frag) {
+			next_frag = TAILQ_NEXT(frag, tq);
+			TAILQ_REMOVE(&fragments, frag, tq);
+			xfree(frag);
+		}
+		if (ftruncate(local_fd, contig_len) == -1) {
+			error("Couldn't truncate \"%s\": %s"
+			      "; there may be gaps in it",
+			      local_path, strerror(errno));
+		}
+	}
+
+	truncate_fd = -1;
+
 	if (read_error) {
 		error("Couldn't read from remote file \"%s\" : %s",
 		    remote_path, fx2txt(status));
@@ -1133,3 +1224,13 @@
 	buffer_free(&msg);
 	return(status);
 }
+
+void
+cleanup_exit(int i)
+{
+	// XXX: error reporting?
+	ftruncate(truncate_fd,
+	    ((u_int64_t)truncate_len_upper << 32) | truncate_len_lower);
+
+	_exit(i);
+}
diff -ur openssh-3.8.1p1-8.sarge.4/sftp-client.h openssh-3.8.1p1/sftp-client.h
--- openssh-3.8.1p1-8.sarge.4/sftp-client.h	2004-02-17 06:08:00.000000000 +0000
+++ openssh-3.8.1p1/sftp-client.h	2005-12-29 17:45:43.000000000 +0000
@@ -86,9 +86,10 @@
 
 /*
  * Download 'remote_path' to 'local_path'. Preserve permissions and times
- * if 'pflag' is set
+ * if 'pflag' is set. If 'rflag' is set, assume the contents of the local
+ * file (if it exists) are a prefix of the remote file.
  */
-int do_download(struct sftp_conn *, char *, char *, int);
+int do_download(struct sftp_conn *, char *, char *, int, int);
 
 /*
  * Upload 'local_path' to 'remote_path'. Preserve permissions and times

Attachment: signature.asc
Description: This is a digitally signed message part


Reply to: