diff options
author | Colin Watson <cjwatson@debian.org> | 2013-09-14 23:42:11 +0100 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2013-09-14 23:42:11 +0100 |
commit | 327155e6824b3ee13837bdde04e4eb47e147ff46 (patch) | |
tree | 8f8743122403c7a2e6ed919156711fb1520c657f /sftp-client.c | |
parent | 0334ce32304e9ba2a10ee5ca49ca6e8ff3ba6cf4 (diff) | |
parent | 74e339b8f8936bc0d985e053a076d0c9b5e9ea51 (diff) |
* New upstream release (http://www.openssh.com/txt/release-6.3).
- sftp(1): add support for resuming partial downloads using the "reget"
command and on the sftp commandline or on the "get" commandline using
the "-a" (append) option (closes: #158590).
- ssh(1): add an "IgnoreUnknown" configuration option to selectively
suppress errors arising from unknown configuration directives (closes:
#436052).
- sftp(1): update progressmeter when data is acknowledged, not when it's
sent (partially addresses #708372).
- ssh(1): do not fatally exit when attempting to cleanup multiplexing-
created channels that are incompletely opened (closes: #651357).
Diffstat (limited to 'sftp-client.c')
-rw-r--r-- | sftp-client.c | 135 |
1 files changed, 83 insertions, 52 deletions
diff --git a/sftp-client.c b/sftp-client.c index 85f2bd444..f4f1970b6 100644 --- a/sftp-client.c +++ b/sftp-client.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-client.c,v 1.97 2012/07/02 12:13:26 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 | ||
@@ -394,8 +394,8 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, | |||
394 | } else { | 394 | } else { |
395 | debug2("Unrecognised server extension \"%s\"", name); | 395 | debug2("Unrecognised server extension \"%s\"", name); |
396 | } | 396 | } |
397 | xfree(name); | 397 | free(name); |
398 | xfree(value); | 398 | free(value); |
399 | } | 399 | } |
400 | 400 | ||
401 | buffer_free(&msg); | 401 | buffer_free(&msg); |
@@ -509,7 +509,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, | |||
509 | error("Couldn't read directory: %s", | 509 | error("Couldn't read directory: %s", |
510 | fx2txt(status)); | 510 | fx2txt(status)); |
511 | do_close(conn, handle, handle_len); | 511 | do_close(conn, handle, handle_len); |
512 | xfree(handle); | 512 | free(handle); |
513 | buffer_free(&msg); | 513 | buffer_free(&msg); |
514 | return(status); | 514 | return(status); |
515 | } | 515 | } |
@@ -552,14 +552,14 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, | |||
552 | (*dir)[++ents] = NULL; | 552 | (*dir)[++ents] = NULL; |
553 | } | 553 | } |
554 | next: | 554 | next: |
555 | xfree(filename); | 555 | free(filename); |
556 | xfree(longname); | 556 | free(longname); |
557 | } | 557 | } |
558 | } | 558 | } |
559 | 559 | ||
560 | buffer_free(&msg); | 560 | buffer_free(&msg); |
561 | do_close(conn, handle, handle_len); | 561 | do_close(conn, handle, handle_len); |
562 | xfree(handle); | 562 | free(handle); |
563 | 563 | ||
564 | /* Don't return partial matches on interrupt */ | 564 | /* Don't return partial matches on interrupt */ |
565 | if (interrupted && dir != NULL && *dir != NULL) { | 565 | if (interrupted && dir != NULL && *dir != NULL) { |
@@ -582,11 +582,11 @@ void free_sftp_dirents(SFTP_DIRENT **s) | |||
582 | int i; | 582 | int i; |
583 | 583 | ||
584 | for (i = 0; s[i]; i++) { | 584 | for (i = 0; s[i]; i++) { |
585 | xfree(s[i]->filename); | 585 | free(s[i]->filename); |
586 | xfree(s[i]->longname); | 586 | free(s[i]->longname); |
587 | xfree(s[i]); | 587 | free(s[i]); |
588 | } | 588 | } |
589 | xfree(s); | 589 | free(s); |
590 | } | 590 | } |
591 | 591 | ||
592 | int | 592 | int |
@@ -760,7 +760,7 @@ do_realpath(struct sftp_conn *conn, char *path) | |||
760 | debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, | 760 | debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, |
761 | (unsigned long)a->size); | 761 | (unsigned long)a->size); |
762 | 762 | ||
763 | xfree(longname); | 763 | free(longname); |
764 | 764 | ||
765 | buffer_free(&msg); | 765 | buffer_free(&msg); |
766 | 766 | ||
@@ -907,7 +907,7 @@ do_readlink(struct sftp_conn *conn, char *path) | |||
907 | 907 | ||
908 | debug3("SSH_FXP_READLINK %s -> %s", path, filename); | 908 | debug3("SSH_FXP_READLINK %s -> %s", path, filename); |
909 | 909 | ||
910 | xfree(longname); | 910 | free(longname); |
911 | 911 | ||
912 | buffer_free(&msg); | 912 | buffer_free(&msg); |
913 | 913 | ||
@@ -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 ? 0 : O_TRUNC), |
1054 | mode | S_IWRITE); | 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 | xfree(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); |
@@ -1121,7 +1137,7 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1121 | read_error = 1; | 1137 | read_error = 1; |
1122 | max_req = 0; | 1138 | max_req = 0; |
1123 | TAILQ_REMOVE(&requests, req, tq); | 1139 | TAILQ_REMOVE(&requests, req, tq); |
1124 | xfree(req); | 1140 | free(req); |
1125 | num_req--; | 1141 | num_req--; |
1126 | break; | 1142 | break; |
1127 | case SSH2_FXP_DATA: | 1143 | case SSH2_FXP_DATA: |
@@ -1139,12 +1155,16 @@ 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 | xfree(data); | 1163 | free(data); |
1144 | 1164 | ||
1145 | if (len == req->len) { | 1165 | if (len == req->len) { |
1146 | TAILQ_REMOVE(&requests, req, tq); | 1166 | TAILQ_REMOVE(&requests, req, tq); |
1147 | xfree(req); | 1167 | free(req); |
1148 | num_req--; | 1168 | num_req--; |
1149 | } else { | 1169 | } else { |
1150 | /* Resend the request for the missing data */ | 1170 | /* Resend the request for the missing data */ |
@@ -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) |
@@ -1220,14 +1249,14 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1220 | } | 1249 | } |
1221 | close(local_fd); | 1250 | close(local_fd); |
1222 | buffer_free(&msg); | 1251 | buffer_free(&msg); |
1223 | xfree(handle); | 1252 | free(handle); |
1224 | 1253 | ||
1225 | return(status); | 1254 | return(status); |
1226 | } | 1255 | } |
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; |
@@ -1292,8 +1321,8 @@ download_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1292 | } else | 1321 | } else |
1293 | logit("%s: not a regular file\n", new_src); | 1322 | logit("%s: not a regular file\n", new_src); |
1294 | 1323 | ||
1295 | xfree(new_dst); | 1324 | free(new_dst); |
1296 | xfree(new_src); | 1325 | free(new_src); |
1297 | } | 1326 | } |
1298 | 1327 | ||
1299 | if (pflag) { | 1328 | if (pflag) { |
@@ -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,8 +1357,8 @@ 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 | xfree(src_canon); | 1361 | free(src_canon); |
1333 | return ret; | 1362 | return ret; |
1334 | } | 1363 | } |
1335 | 1364 | ||
@@ -1340,7 +1369,7 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1340 | int local_fd; | 1369 | int local_fd; |
1341 | int status = SSH2_FX_OK; | 1370 | int status = SSH2_FX_OK; |
1342 | u_int handle_len, id, type; | 1371 | u_int handle_len, id, type; |
1343 | off_t offset; | 1372 | off_t offset, progress_counter; |
1344 | char *handle, *data; | 1373 | char *handle, *data; |
1345 | Buffer msg; | 1374 | Buffer msg; |
1346 | struct stat sb; | 1375 | struct stat sb; |
@@ -1408,9 +1437,10 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1408 | data = xmalloc(conn->transfer_buflen); | 1437 | data = xmalloc(conn->transfer_buflen); |
1409 | 1438 | ||
1410 | /* Read from local and write to remote */ | 1439 | /* Read from local and write to remote */ |
1411 | offset = 0; | 1440 | offset = progress_counter = 0; |
1412 | if (showprogress) | 1441 | if (showprogress) |
1413 | start_progress_meter(local_path, sb.st_size, &offset); | 1442 | start_progress_meter(local_path, sb.st_size, |
1443 | &progress_counter); | ||
1414 | 1444 | ||
1415 | for (;;) { | 1445 | for (;;) { |
1416 | int len; | 1446 | int len; |
@@ -1481,7 +1511,8 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1481 | debug3("In write loop, ack for %u %u bytes at %lld", | 1511 | debug3("In write loop, ack for %u %u bytes at %lld", |
1482 | ack->id, ack->len, (long long)ack->offset); | 1512 | ack->id, ack->len, (long long)ack->offset); |
1483 | ++ackid; | 1513 | ++ackid; |
1484 | xfree(ack); | 1514 | progress_counter += ack->len; |
1515 | free(ack); | ||
1485 | } | 1516 | } |
1486 | offset += len; | 1517 | offset += len; |
1487 | if (offset < 0) | 1518 | if (offset < 0) |
@@ -1491,7 +1522,7 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1491 | 1522 | ||
1492 | if (showprogress) | 1523 | if (showprogress) |
1493 | stop_progress_meter(); | 1524 | stop_progress_meter(); |
1494 | xfree(data); | 1525 | free(data); |
1495 | 1526 | ||
1496 | if (status != SSH2_FX_OK) { | 1527 | if (status != SSH2_FX_OK) { |
1497 | error("Couldn't write to remote file \"%s\": %s", | 1528 | error("Couldn't write to remote file \"%s\": %s", |
@@ -1511,7 +1542,7 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1511 | 1542 | ||
1512 | if (do_close(conn, handle, handle_len) != SSH2_FX_OK) | 1543 | if (do_close(conn, handle, handle_len) != SSH2_FX_OK) |
1513 | status = -1; | 1544 | status = -1; |
1514 | xfree(handle); | 1545 | free(handle); |
1515 | 1546 | ||
1516 | return status; | 1547 | return status; |
1517 | } | 1548 | } |
@@ -1551,7 +1582,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1551 | a.perm &= 01777; | 1582 | a.perm &= 01777; |
1552 | if (!pflag) | 1583 | if (!pflag) |
1553 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; | 1584 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
1554 | 1585 | ||
1555 | status = do_mkdir(conn, dst, &a, 0); | 1586 | status = do_mkdir(conn, dst, &a, 0); |
1556 | /* | 1587 | /* |
1557 | * we lack a portable status for errno EEXIST, | 1588 | * we lack a portable status for errno EEXIST, |
@@ -1561,7 +1592,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1561 | if (status != SSH2_FX_OK) { | 1592 | if (status != SSH2_FX_OK) { |
1562 | if (status != SSH2_FX_FAILURE) | 1593 | if (status != SSH2_FX_FAILURE) |
1563 | return -1; | 1594 | return -1; |
1564 | if (do_stat(conn, dst, 0) == NULL) | 1595 | if (do_stat(conn, dst, 0) == NULL) |
1565 | return -1; | 1596 | return -1; |
1566 | } | 1597 | } |
1567 | 1598 | ||
@@ -1569,7 +1600,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1569 | error("Failed to open dir \"%s\": %s", src, strerror(errno)); | 1600 | error("Failed to open dir \"%s\": %s", src, strerror(errno)); |
1570 | return -1; | 1601 | return -1; |
1571 | } | 1602 | } |
1572 | 1603 | ||
1573 | while (((dp = readdir(dirp)) != NULL) && !interrupted) { | 1604 | while (((dp = readdir(dirp)) != NULL) && !interrupted) { |
1574 | if (dp->d_ino == 0) | 1605 | if (dp->d_ino == 0) |
1575 | continue; | 1606 | continue; |
@@ -1597,8 +1628,8 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, | |||
1597 | } | 1628 | } |
1598 | } else | 1629 | } else |
1599 | logit("%s: not a regular file\n", filename); | 1630 | logit("%s: not a regular file\n", filename); |
1600 | xfree(new_dst); | 1631 | free(new_dst); |
1601 | xfree(new_src); | 1632 | free(new_src); |
1602 | } | 1633 | } |
1603 | 1634 | ||
1604 | do_setstat(conn, dst, &a); | 1635 | do_setstat(conn, dst, &a); |
@@ -1620,7 +1651,7 @@ upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag, | |||
1620 | } | 1651 | } |
1621 | 1652 | ||
1622 | ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0); | 1653 | ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0); |
1623 | xfree(dst_canon); | 1654 | free(dst_canon); |
1624 | return ret; | 1655 | return ret; |
1625 | } | 1656 | } |
1626 | 1657 | ||