diff options
author | Damien Miller <djm@mindrot.org> | 2013-07-25 11:56:52 +1000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2013-07-25 11:56:52 +1000 |
commit | 0d032419ee6e1968fc1cb187af63bf3b77b506ea (patch) | |
tree | ce2788365040e9ea188bd60c8bec87d410814017 /sftp-client.c | |
parent | 98e27dcf581647b5bbe9780e8f59685d942d8ea3 (diff) |
- djm@cvs.openbsd.org 2013/07/25 00:56:52
[sftp-client.c sftp-client.h sftp.1 sftp.c]
sftp support for resuming partial downloads; patch mostly by Loganaden
Velvindron/AfriNIC with some tweaks by me; feedback and ok dtucker@
Diffstat (limited to 'sftp-client.c')
-rw-r--r-- | sftp-client.c | 75 |
1 files changed, 52 insertions, 23 deletions
diff --git a/sftp-client.c b/sftp-client.c index ab035c713..cb4efd3ea 100644 --- a/sftp-client.c +++ b/sftp-client.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-client.c,v 1.100 2013/06/01 22:34:50 dtucker Exp $ */ | 1 | /* $OpenBSD: sftp-client.c,v 1.101 2013/07/25 00:56:51 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -112,7 +112,7 @@ send_msg(struct sftp_conn *conn, Buffer *m) | |||
112 | iov[1].iov_len = buffer_len(m); | 112 | iov[1].iov_len = buffer_len(m); |
113 | 113 | ||
114 | if (atomiciov6(writev, conn->fd_out, iov, 2, | 114 | if (atomiciov6(writev, conn->fd_out, iov, 2, |
115 | conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != | 115 | conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != |
116 | buffer_len(m) + sizeof(mlen)) | 116 | buffer_len(m) + sizeof(mlen)) |
117 | fatal("Couldn't send packet: %s", strerror(errno)); | 117 | fatal("Couldn't send packet: %s", strerror(errno)); |
118 | 118 | ||
@@ -988,16 +988,17 @@ send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, | |||
988 | 988 | ||
989 | int | 989 | int |
990 | do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | 990 | do_download(struct sftp_conn *conn, char *remote_path, char *local_path, |
991 | Attrib *a, int pflag) | 991 | Attrib *a, int pflag, int resume) |
992 | { | 992 | { |
993 | Attrib junk; | 993 | Attrib junk; |
994 | Buffer msg; | 994 | Buffer msg; |
995 | char *handle; | 995 | char *handle; |
996 | int local_fd, status = 0, write_error; | 996 | int local_fd = -1, status = 0, write_error; |
997 | int read_error, write_errno; | 997 | int read_error, write_errno, reordered = 0; |
998 | u_int64_t offset, size; | 998 | u_int64_t offset = 0, size, highwater; |
999 | u_int handle_len, mode, type, id, buflen, num_req, max_req; | 999 | u_int handle_len, mode, type, id, buflen, num_req, max_req; |
1000 | off_t progress_counter; | 1000 | off_t progress_counter; |
1001 | struct stat st; | ||
1001 | struct request { | 1002 | struct request { |
1002 | u_int id; | 1003 | u_int id; |
1003 | u_int len; | 1004 | u_int len; |
@@ -1050,21 +1051,36 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1050 | return(-1); | 1051 | return(-1); |
1051 | } | 1052 | } |
1052 | 1053 | ||
1053 | local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, | 1054 | local_fd = open(local_path, O_WRONLY | O_CREAT | (resume ? : O_TRUNC), |
1054 | mode | S_IWUSR); | 1055 | mode | S_IWUSR); |
1055 | if (local_fd == -1) { | 1056 | if (local_fd == -1) { |
1056 | error("Couldn't open local file \"%s\" for writing: %s", | 1057 | error("Couldn't open local file \"%s\" for writing: %s", |
1057 | local_path, strerror(errno)); | 1058 | local_path, strerror(errno)); |
1058 | do_close(conn, handle, handle_len); | 1059 | goto fail; |
1059 | buffer_free(&msg); | 1060 | } |
1060 | free(handle); | 1061 | offset = highwater = 0; |
1061 | return(-1); | 1062 | if (resume) { |
1063 | if (fstat(local_fd, &st) == -1) { | ||
1064 | error("Unable to stat local file \"%s\": %s", | ||
1065 | local_path, strerror(errno)); | ||
1066 | goto fail; | ||
1067 | } | ||
1068 | if ((size_t)st.st_size > size) { | ||
1069 | error("Unable to resume download of \"%s\": " | ||
1070 | "local file is larger than remote", local_path); | ||
1071 | fail: | ||
1072 | do_close(conn, handle, handle_len); | ||
1073 | buffer_free(&msg); | ||
1074 | free(handle); | ||
1075 | return -1; | ||
1076 | } | ||
1077 | offset = highwater = st.st_size; | ||
1062 | } | 1078 | } |
1063 | 1079 | ||
1064 | /* Read from remote and write to local */ | 1080 | /* Read from remote and write to local */ |
1065 | write_error = read_error = write_errno = num_req = offset = 0; | 1081 | write_error = read_error = write_errno = num_req = 0; |
1066 | max_req = 1; | 1082 | max_req = 1; |
1067 | progress_counter = 0; | 1083 | progress_counter = offset; |
1068 | 1084 | ||
1069 | if (showprogress && size != 0) | 1085 | if (showprogress && size != 0) |
1070 | start_progress_meter(remote_path, size, &progress_counter); | 1086 | start_progress_meter(remote_path, size, &progress_counter); |
@@ -1139,6 +1155,10 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1139 | write_error = 1; | 1155 | write_error = 1; |
1140 | max_req = 0; | 1156 | max_req = 0; |
1141 | } | 1157 | } |
1158 | else if (!reordered && req->offset <= highwater) | ||
1159 | highwater = req->offset + len; | ||
1160 | else if (!reordered && req->offset > highwater) | ||
1161 | reordered = 1; | ||
1142 | progress_counter += len; | 1162 | progress_counter += len; |
1143 | free(data); | 1163 | free(data); |
1144 | 1164 | ||
@@ -1187,7 +1207,15 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1187 | /* Sanity check */ | 1207 | /* Sanity check */ |
1188 | if (TAILQ_FIRST(&requests) != NULL) | 1208 | if (TAILQ_FIRST(&requests) != NULL) |
1189 | fatal("Transfer complete, but requests still in queue"); | 1209 | fatal("Transfer complete, but requests still in queue"); |
1190 | 1210 | /* Truncate at highest contiguous point to avoid holes on interrupt */ | |
1211 | if (read_error || write_error || interrupted) { | ||
1212 | if (reordered && resume) { | ||
1213 | error("Unable to resume download of \"%s\": " | ||
1214 | "server reordered requests", local_path); | ||
1215 | } | ||
1216 | debug("truncating at %llu", (unsigned long long)highwater); | ||
1217 | ftruncate(local_fd, highwater); | ||
1218 | } | ||
1191 | if (read_error) { | 1219 | if (read_error) { |
1192 | error("Couldn't read from remote file \"%s\" : %s", | 1220 | error("Couldn't read from remote file \"%s\" : %s", |
1193 | remote_path, fx2txt(status)); | 1221 | remote_path, fx2txt(status)); |
@@ -1199,7 +1227,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1199 | do_close(conn, handle, handle_len); | 1227 | do_close(conn, handle, handle_len); |
1200 | } else { | 1228 | } else { |
1201 | status = do_close(conn, handle, handle_len); | 1229 | status = do_close(conn, handle, handle_len); |
1202 | 1230 | if (interrupted) | |
1231 | status = -1; | ||
1203 | /* Override umask and utimes if asked */ | 1232 | /* Override umask and utimes if asked */ |
1204 | #ifdef HAVE_FCHMOD | 1233 | #ifdef HAVE_FCHMOD |
1205 | if (pflag && fchmod(local_fd, mode) == -1) | 1234 | if (pflag && fchmod(local_fd, mode) == -1) |
@@ -1227,7 +1256,7 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1227 | 1256 | ||
1228 | static int | 1257 | static int |
1229 | download_dir_internal(struct sftp_conn *conn, char *src, char *dst, | 1258 | download_dir_internal(struct sftp_conn *conn, char *src, char *dst, |
1230 | Attrib *dirattrib, int pflag, int printflag, int depth) | 1259 | Attrib *dirattrib, int pflag, int printflag, int depth, int resume) |
1231 | { | 1260 | { |
1232 | int i, ret = 0; | 1261 | int i, ret = 0; |
1233 | SFTP_DIRENT **dir_entries; | 1262 | SFTP_DIRENT **dir_entries; |
@@ -1280,11 +1309,11 @@ download_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1280 | continue; | 1309 | continue; |
1281 | if (download_dir_internal(conn, new_src, new_dst, | 1310 | if (download_dir_internal(conn, new_src, new_dst, |
1282 | &(dir_entries[i]->a), pflag, printflag, | 1311 | &(dir_entries[i]->a), pflag, printflag, |
1283 | depth + 1) == -1) | 1312 | depth + 1, resume) == -1) |
1284 | ret = -1; | 1313 | ret = -1; |
1285 | } else if (S_ISREG(dir_entries[i]->a.perm) ) { | 1314 | } else if (S_ISREG(dir_entries[i]->a.perm) ) { |
1286 | if (do_download(conn, new_src, new_dst, | 1315 | if (do_download(conn, new_src, new_dst, |
1287 | &(dir_entries[i]->a), pflag) == -1) { | 1316 | &(dir_entries[i]->a), pflag, resume) == -1) { |
1288 | error("Download of file %s to %s failed", | 1317 | error("Download of file %s to %s failed", |
1289 | new_src, new_dst); | 1318 | new_src, new_dst); |
1290 | ret = -1; | 1319 | ret = -1; |
@@ -1317,7 +1346,7 @@ download_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1317 | 1346 | ||
1318 | int | 1347 | int |
1319 | download_dir(struct sftp_conn *conn, char *src, char *dst, | 1348 | download_dir(struct sftp_conn *conn, char *src, char *dst, |
1320 | Attrib *dirattrib, int pflag, int printflag) | 1349 | Attrib *dirattrib, int pflag, int printflag, int resume) |
1321 | { | 1350 | { |
1322 | char *src_canon; | 1351 | char *src_canon; |
1323 | int ret; | 1352 | int ret; |
@@ -1328,7 +1357,7 @@ download_dir(struct sftp_conn *conn, char *src, char *dst, | |||
1328 | } | 1357 | } |
1329 | 1358 | ||
1330 | ret = download_dir_internal(conn, src_canon, dst, | 1359 | ret = download_dir_internal(conn, src_canon, dst, |
1331 | dirattrib, pflag, printflag, 0); | 1360 | dirattrib, pflag, printflag, 0, resume); |
1332 | free(src_canon); | 1361 | free(src_canon); |
1333 | return ret; | 1362 | return ret; |
1334 | } | 1363 | } |
@@ -1553,7 +1582,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1553 | a.perm &= 01777; | 1582 | a.perm &= 01777; |
1554 | if (!pflag) | 1583 | if (!pflag) |
1555 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; | 1584 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
1556 | 1585 | ||
1557 | status = do_mkdir(conn, dst, &a, 0); | 1586 | status = do_mkdir(conn, dst, &a, 0); |
1558 | /* | 1587 | /* |
1559 | * we lack a portable status for errno EEXIST, | 1588 | * we lack a portable status for errno EEXIST, |
@@ -1563,7 +1592,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1563 | if (status != SSH2_FX_OK) { | 1592 | if (status != SSH2_FX_OK) { |
1564 | if (status != SSH2_FX_FAILURE) | 1593 | if (status != SSH2_FX_FAILURE) |
1565 | return -1; | 1594 | return -1; |
1566 | if (do_stat(conn, dst, 0) == NULL) | 1595 | if (do_stat(conn, dst, 0) == NULL) |
1567 | return -1; | 1596 | return -1; |
1568 | } | 1597 | } |
1569 | 1598 | ||
@@ -1571,7 +1600,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1571 | error("Failed to open dir \"%s\": %s", src, strerror(errno)); | 1600 | error("Failed to open dir \"%s\": %s", src, strerror(errno)); |
1572 | return -1; | 1601 | return -1; |
1573 | } | 1602 | } |
1574 | 1603 | ||
1575 | while (((dp = readdir(dirp)) != NULL) && !interrupted) { | 1604 | while (((dp = readdir(dirp)) != NULL) && !interrupted) { |
1576 | if (dp->d_ino == 0) | 1605 | if (dp->d_ino == 0) |
1577 | continue; | 1606 | continue; |