- logan@cvs.openbsd.org 2014/04/21 14:36:16
     [sftp-client.c sftp-client.h sftp.c]
     Implement sftp upload resume support.
     OK from djm@, with input from guenther@, mlarkin@ and
     okan@
diff --git a/ChangeLog b/ChangeLog
index ff9c3cb..011e8f9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,11 @@
      [dns.c dns.h ssh-keygen.c]
      Add support for SSHFP DNS records for ED25519 key types.
      OK from djm@
+   - logan@cvs.openbsd.org 2014/04/21 14:36:16
+     [sftp-client.c sftp-client.h sftp.c]
+     Implement sftp upload resume support.
+     OK from djm@, with input from guenther@, mlarkin@ and
+     okan@
 
 20140430
  - (dtucker) [defines.h] Define __GNUC_PREREQ__ macro if we don't already
diff --git a/sftp-client.c b/sftp-client.c
index 2f5907c..990b58d 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.114 2014/01/31 16:39:19 tedu Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.115 2014/04/21 14:36:16 logan Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -1420,7 +1420,7 @@
 
 int
 do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
-    int preserve_flag, int fsync_flag)
+    int preserve_flag, int resume, int fsync_flag)
 {
 	int local_fd;
 	int status = SSH2_FX_OK;
@@ -1429,7 +1429,7 @@
 	char *handle, *data;
 	Buffer msg;
 	struct stat sb;
-	Attrib a;
+	Attrib a, *c = NULL;
 	u_int32_t startid;
 	u_int32_t ackid;
 	struct outstanding_ack {
@@ -1467,6 +1467,26 @@
 	if (!preserve_flag)
 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
 
+	if (resume) {
+		/* Get remote file size if it exists */
+		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
+			close(local_fd);                
+			return -1;
+		}
+
+		if ((off_t)c->size >= sb.st_size) {
+			error("destination file bigger or same size as "
+			      "source file");
+			close(local_fd);
+			return -1;
+		}
+
+		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
+			close(local_fd);
+			return -1;
+		}
+	}
+
 	buffer_init(&msg);
 
 	/* Send open request */
@@ -1474,7 +1494,8 @@
 	buffer_put_char(&msg, SSH2_FXP_OPEN);
 	buffer_put_int(&msg, id);
 	buffer_put_cstring(&msg, remote_path);
-	buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
+	buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
+		      (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC));
 	encode_attrib(&msg, &a);
 	send_msg(conn, &msg);
 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
@@ -1493,7 +1514,7 @@
 	data = xmalloc(conn->transfer_buflen);
 
 	/* Read from local and write to remote */
-	offset = progress_counter = 0;
+	offset = progress_counter = (resume ? c->size : 0);
 	if (showprogress)
 		start_progress_meter(local_path, sb.st_size,
 		    &progress_counter);
@@ -1608,7 +1629,7 @@
 
 static int
 upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, int depth,
-    int preserve_flag, int print_flag, int fsync_flag)
+    int preserve_flag, int print_flag, int resume, int fsync_flag)
 {
 	int ret = 0, status;
 	DIR *dirp;
@@ -1677,12 +1698,12 @@
 				continue;
 
 			if (upload_dir_internal(conn, new_src, new_dst,
-			    depth + 1, preserve_flag, print_flag,
+			    depth + 1, preserve_flag, print_flag, resume,
 			    fsync_flag) == -1)
 				ret = -1;
 		} else if (S_ISREG(sb.st_mode)) {
 			if (do_upload(conn, new_src, new_dst,
-			    preserve_flag, fsync_flag) == -1) {
+			    preserve_flag, resume, fsync_flag) == -1) {
 				error("Uploading of file %s to %s failed!",
 				    new_src, new_dst);
 				ret = -1;
@@ -1701,7 +1722,7 @@
 
 int
 upload_dir(struct sftp_conn *conn, char *src, char *dst, int preserve_flag,
-    int print_flag, int fsync_flag)
+    int print_flag, int resume, int fsync_flag)
 {
 	char *dst_canon;
 	int ret;
@@ -1712,7 +1733,7 @@
 	}
 
 	ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
-	    print_flag, fsync_flag);
+	    print_flag, resume, fsync_flag);
 
 	free(dst_canon);
 	return ret;
diff --git a/sftp-client.h b/sftp-client.h
index ba92ad0..967840b 100644
--- a/sftp-client.h
+++ b/sftp-client.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.h,v 1.24 2013/10/17 00:30:13 djm Exp $ */
+/* $OpenBSD: sftp-client.h,v 1.25 2014/04/21 14:36:16 logan Exp $ */
 
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@@ -120,13 +120,13 @@
  * Upload 'local_path' to 'remote_path'. Preserve permissions and times
  * if 'pflag' is set
  */
-int do_upload(struct sftp_conn *, char *, char *, int, int);
+int do_upload(struct sftp_conn *, char *, char *, int, int, int);
 
 /*
  * Recursively upload 'local_directory' to 'remote_directory'. Preserve 
  * times if 'pflag' is set
  */
-int upload_dir(struct sftp_conn *, char *, char *, int, int, int);
+int upload_dir(struct sftp_conn *, char *, char *, int, int, int, int);
 
 /* Concatenate paths, taking care of slashes. Caller must free result. */
 char *path_append(char *, char *);
diff --git a/sftp.c b/sftp.c
index ad1f8c8..e74bed5 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.158 2013/11/20 20:54:10 deraadt Exp $ */
+/* $OpenBSD: sftp.c,v 1.159 2014/04/21 14:36:16 logan Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -88,7 +88,7 @@
 /* When this option is set, we always recursively download/upload directories */
 int global_rflag = 0;
 
-/* When this option is set, we resume download if possible */
+/* When this option is set, we resume download or upload if possible */
 int global_aflag = 0;
 
 /* When this option is set, the file transfers will always preserve times */
@@ -159,6 +159,7 @@
 	I_VERSION,
 	I_PROGRESS,
 	I_REGET,
+	I_REPUT
 };
 
 struct CMD {
@@ -201,6 +202,7 @@
 	{ "quit",	I_QUIT,		NOARGS	},
 	{ "reget",	I_REGET,	REMOTE	},
 	{ "rename",	I_RENAME,	REMOTE	},
+	{ "reput",      I_REPUT,        LOCAL   },
 	{ "rm",		I_RM,		REMOTE	},
 	{ "rmdir",	I_RMDIR,	REMOTE	},
 	{ "symlink",	I_SYMLINK,	REMOTE	},
@@ -250,6 +252,7 @@
 	    "exit                               Quit sftp\n"
 	    "get [-Ppr] remote [local]          Download file\n"
 	    "reget remote [local]		Resume download file\n"
+	    "reput [local] remote               Resume upload file\n"
 	    "help                               Display this help text\n"
 	    "lcd path                           Change local directory to 'path'\n"
 	    "lls [ls-options [path]]            Display local directory listing\n"
@@ -660,7 +663,7 @@
 
 static int
 process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
-    int pflag, int rflag, int fflag)
+    int pflag, int rflag, int resume, int fflag)
 {
 	char *tmp_dst = NULL;
 	char *abs_dst = NULL;
@@ -723,16 +726,20 @@
 		}
 		free(tmp);
 
-		if (!quiet)
+                resume |= global_aflag;
+		if (!quiet && resume)
+			printf("Resuming upload of  %s to %s\n", g.gl_pathv[i], 
+				abs_dst);
+		else if (!quiet && !resume)
 			printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
 		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
 			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
-			    pflag || global_pflag, 1,
+			    pflag || global_pflag, 1, resume,
 			    fflag || global_fflag) == -1)
 				err = -1;
 		} else {
 			if (do_upload(conn, g.gl_pathv[i], abs_dst,
-			    pflag || global_pflag,
+			    pflag || global_pflag, resume,
 			    fflag || global_fflag) == -1)
 				err = -1;
 		}
@@ -1186,8 +1193,9 @@
 }
 
 static int
-parse_args(const char **cpp, int *ignore_errors, int *aflag, int *fflag,
-    int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag,
+parse_args(const char **cpp, int *ignore_errors, int *aflag,
+	  int *fflag, int *hflag, int *iflag, int *lflag, int *pflag, 
+	  int *rflag, int *sflag,
     unsigned long *n_arg, char **path1, char **path2)
 {
 	const char *cmd, *cp = *cpp;
@@ -1239,6 +1247,7 @@
 	switch (cmdnum) {
 	case I_GET:
 	case I_REGET:
+	case I_REPUT:
 	case I_PUT:
 		if ((optidx = parse_getput_flags(cmd, argv, argc,
 		    aflag, fflag, pflag, rflag)) == -1)
@@ -1256,11 +1265,6 @@
 			/* Destination is not globbed */
 			undo_glob_escape(*path2);
 		}
-		if (*aflag && cmdnum == I_PUT) {
-			/* XXX implement resume for uploads */
-			error("Resume is not supported for uploads");
-			return -1;
-		}
 		break;
 	case I_LINK:
 		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
@@ -1382,7 +1386,8 @@
     int err_abort)
 {
 	char *path1, *path2, *tmp;
-	int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, iflag = 0;
+	int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, 
+	iflag = 0;
 	int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
 	int cmdnum, i;
 	unsigned long n_arg = 0;
@@ -1415,9 +1420,12 @@
 		err = process_get(conn, path1, path2, *pwd, pflag,
 		    rflag, aflag, fflag);
 		break;
+	case I_REPUT:
+		aflag = 1;
+		/* FALLTHROUGH */
 	case I_PUT:
 		err = process_put(conn, path1, path2, *pwd, pflag,
-		    rflag, fflag);
+		    rflag, aflag, fflag);
 		break;
 	case I_RENAME:
 		path1 = make_absolute(path1, *pwd);