1 /* $OpenBSD: sftp-client.c,v 1.115 2014/04/21 14:36:16 logan Exp $ */
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 /* XXX: signed vs unsigned */
20 /* XXX: remove all logging, only return status codes */
21 /* XXX: copy between two remote sites */
25 #include <sys/types.h>
26 #include <sys/param.h>
27 #ifdef HAVE_SYS_STATVFS_H
28 #include <sys/statvfs.h>
30 #include "openbsd-compat/sys-queue.h"
31 #ifdef HAVE_SYS_STAT_H
32 # include <sys/stat.h>
34 #ifdef HAVE_SYS_TIME_H
35 # include <sys/time.h>
53 #include "progressmeter.h"
57 #include "sftp-common.h"
58 #include "sftp-client.h"
60 extern volatile sig_atomic_t interrupted
;
61 extern int showprogress
;
63 /* Minimum amount of data to read at a time */
64 #define MIN_READ_SIZE 512
66 /* Maximum depth to descend in directory trees */
67 #define MAX_DIR_DEPTH 64
72 u_int transfer_buflen
;
76 #define SFTP_EXT_POSIX_RENAME 0x00000001
77 #define SFTP_EXT_STATVFS 0x00000002
78 #define SFTP_EXT_FSTATVFS 0x00000004
79 #define SFTP_EXT_HARDLINK 0x00000008
80 #define SFTP_EXT_FSYNC 0x00000010
83 struct bwlimit bwlimit_in
, bwlimit_out
;
87 get_handle(struct sftp_conn
*conn
, u_int expected_id
, u_int
*len
,
88 const char *errfmt
, ...) __attribute__((format(printf
, 4, 5)));
92 sftpio(void *_bwlimit
, size_t amount
)
94 struct bwlimit
*bwlimit
= (struct bwlimit
*)_bwlimit
;
96 bandwidth_limit(bwlimit
, amount
);
101 send_msg(struct sftp_conn
*conn
, Buffer
*m
)
106 if (buffer_len(m
) > SFTP_MAX_MSG_LENGTH
)
107 fatal("Outbound message too long %u", buffer_len(m
));
109 /* Send length first */
110 put_u32(mlen
, buffer_len(m
));
111 iov
[0].iov_base
= mlen
;
112 iov
[0].iov_len
= sizeof(mlen
);
113 iov
[1].iov_base
= buffer_ptr(m
);
114 iov
[1].iov_len
= buffer_len(m
);
116 if (atomiciov6(writev
, conn
->fd_out
, iov
, 2,
117 conn
->limit_kbps
> 0 ? sftpio
: NULL
, &conn
->bwlimit_out
) !=
118 buffer_len(m
) + sizeof(mlen
))
119 fatal("Couldn't send packet: %s", strerror(errno
));
125 get_msg(struct sftp_conn
*conn
, Buffer
*m
)
129 buffer_append_space(m
, 4);
130 if (atomicio6(read
, conn
->fd_in
, buffer_ptr(m
), 4,
131 conn
->limit_kbps
> 0 ? sftpio
: NULL
, &conn
->bwlimit_in
) != 4) {
133 fatal("Connection closed");
135 fatal("Couldn't read packet: %s", strerror(errno
));
138 msg_len
= buffer_get_int(m
);
139 if (msg_len
> SFTP_MAX_MSG_LENGTH
)
140 fatal("Received message too long %u", msg_len
);
142 buffer_append_space(m
, msg_len
);
143 if (atomicio6(read
, conn
->fd_in
, buffer_ptr(m
), msg_len
,
144 conn
->limit_kbps
> 0 ? sftpio
: NULL
, &conn
->bwlimit_in
)
147 fatal("Connection closed");
149 fatal("Read packet: %s", strerror(errno
));
154 send_string_request(struct sftp_conn
*conn
, u_int id
, u_int code
, char *s
,
160 buffer_put_char(&msg
, code
);
161 buffer_put_int(&msg
, id
);
162 buffer_put_string(&msg
, s
, len
);
163 send_msg(conn
, &msg
);
164 debug3("Sent message fd %d T:%u I:%u", conn
->fd_out
, code
, id
);
169 send_string_attrs_request(struct sftp_conn
*conn
, u_int id
, u_int code
,
170 char *s
, u_int len
, Attrib
*a
)
175 buffer_put_char(&msg
, code
);
176 buffer_put_int(&msg
, id
);
177 buffer_put_string(&msg
, s
, len
);
178 encode_attrib(&msg
, a
);
179 send_msg(conn
, &msg
);
180 debug3("Sent message fd %d T:%u I:%u", conn
->fd_out
, code
, id
);
185 get_status(struct sftp_conn
*conn
, u_int expected_id
)
188 u_int type
, id
, status
;
192 type
= buffer_get_char(&msg
);
193 id
= buffer_get_int(&msg
);
195 if (id
!= expected_id
)
196 fatal("ID mismatch (%u != %u)", id
, expected_id
);
197 if (type
!= SSH2_FXP_STATUS
)
198 fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
199 SSH2_FXP_STATUS
, type
);
201 status
= buffer_get_int(&msg
);
204 debug3("SSH2_FXP_STATUS %u", status
);
210 get_handle(struct sftp_conn
*conn
, u_int expected_id
, u_int
*len
,
211 const char *errfmt
, ...)
215 char *handle
, errmsg
[256];
219 va_start(args
, errfmt
);
221 vsnprintf(errmsg
, sizeof(errmsg
), errfmt
, args
);
226 type
= buffer_get_char(&msg
);
227 id
= buffer_get_int(&msg
);
229 if (id
!= expected_id
)
230 fatal("%s: ID mismatch (%u != %u)",
231 errfmt
== NULL
? __func__
: errmsg
, id
, expected_id
);
232 if (type
== SSH2_FXP_STATUS
) {
233 status
= buffer_get_int(&msg
);
235 error("%s: %s", errmsg
, fx2txt(status
));
238 } else if (type
!= SSH2_FXP_HANDLE
)
239 fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
240 errfmt
== NULL
? __func__
: errmsg
, SSH2_FXP_HANDLE
, type
);
242 handle
= buffer_get_string(&msg
, len
);
249 get_decode_stat(struct sftp_conn
*conn
, u_int expected_id
, int quiet
)
258 type
= buffer_get_char(&msg
);
259 id
= buffer_get_int(&msg
);
261 debug3("Received stat reply T:%u I:%u", type
, id
);
262 if (id
!= expected_id
)
263 fatal("ID mismatch (%u != %u)", id
, expected_id
);
264 if (type
== SSH2_FXP_STATUS
) {
265 int status
= buffer_get_int(&msg
);
268 debug("Couldn't stat remote file: %s", fx2txt(status
));
270 error("Couldn't stat remote file: %s", fx2txt(status
));
273 } else if (type
!= SSH2_FXP_ATTRS
) {
274 fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
275 SSH2_FXP_ATTRS
, type
);
277 a
= decode_attrib(&msg
);
284 get_decode_statvfs(struct sftp_conn
*conn
, struct sftp_statvfs
*st
,
285 u_int expected_id
, int quiet
)
288 u_int type
, id
, flag
;
293 type
= buffer_get_char(&msg
);
294 id
= buffer_get_int(&msg
);
296 debug3("Received statvfs reply T:%u I:%u", type
, id
);
297 if (id
!= expected_id
)
298 fatal("ID mismatch (%u != %u)", id
, expected_id
);
299 if (type
== SSH2_FXP_STATUS
) {
300 int status
= buffer_get_int(&msg
);
303 debug("Couldn't statvfs: %s", fx2txt(status
));
305 error("Couldn't statvfs: %s", fx2txt(status
));
308 } else if (type
!= SSH2_FXP_EXTENDED_REPLY
) {
309 fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
310 SSH2_FXP_EXTENDED_REPLY
, type
);
313 memset(st
, 0, sizeof(*st
));
314 st
->f_bsize
= buffer_get_int64(&msg
);
315 st
->f_frsize
= buffer_get_int64(&msg
);
316 st
->f_blocks
= buffer_get_int64(&msg
);
317 st
->f_bfree
= buffer_get_int64(&msg
);
318 st
->f_bavail
= buffer_get_int64(&msg
);
319 st
->f_files
= buffer_get_int64(&msg
);
320 st
->f_ffree
= buffer_get_int64(&msg
);
321 st
->f_favail
= buffer_get_int64(&msg
);
322 st
->f_fsid
= buffer_get_int64(&msg
);
323 flag
= buffer_get_int64(&msg
);
324 st
->f_namemax
= buffer_get_int64(&msg
);
326 st
->f_flag
= (flag
& SSH2_FXE_STATVFS_ST_RDONLY
) ? ST_RDONLY
: 0;
327 st
->f_flag
|= (flag
& SSH2_FXE_STATVFS_ST_NOSUID
) ? ST_NOSUID
: 0;
335 do_init(int fd_in
, int fd_out
, u_int transfer_buflen
, u_int num_requests
,
336 u_int64_t limit_kbps
)
340 struct sftp_conn
*ret
;
342 ret
= xcalloc(1, sizeof(*ret
));
345 ret
->fd_out
= fd_out
;
346 ret
->transfer_buflen
= transfer_buflen
;
347 ret
->num_requests
= num_requests
;
352 buffer_put_char(&msg
, SSH2_FXP_INIT
);
353 buffer_put_int(&msg
, SSH2_FILEXFER_VERSION
);
360 /* Expecting a VERSION reply */
361 if ((type
= buffer_get_char(&msg
)) != SSH2_FXP_VERSION
) {
362 error("Invalid packet back from SSH2_FXP_INIT (type %u)",
367 ret
->version
= buffer_get_int(&msg
);
369 debug2("Remote version: %u", ret
->version
);
371 /* Check for extensions */
372 while (buffer_len(&msg
) > 0) {
373 char *name
= buffer_get_string(&msg
, NULL
);
374 char *value
= buffer_get_string(&msg
, NULL
);
377 if (strcmp(name
, "posix-rename@openssh.com") == 0 &&
378 strcmp(value
, "1") == 0) {
379 ret
->exts
|= SFTP_EXT_POSIX_RENAME
;
381 } else if (strcmp(name
, "statvfs@openssh.com") == 0 &&
382 strcmp(value
, "2") == 0) {
383 ret
->exts
|= SFTP_EXT_STATVFS
;
385 } else if (strcmp(name
, "fstatvfs@openssh.com") == 0 &&
386 strcmp(value
, "2") == 0) {
387 ret
->exts
|= SFTP_EXT_FSTATVFS
;
389 } else if (strcmp(name
, "hardlink@openssh.com") == 0 &&
390 strcmp(value
, "1") == 0) {
391 ret
->exts
|= SFTP_EXT_HARDLINK
;
393 } else if (strcmp(name
, "fsync@openssh.com") == 0 &&
394 strcmp(value
, "1") == 0) {
395 ret
->exts
|= SFTP_EXT_FSYNC
;
399 debug2("Server supports extension \"%s\" revision %s",
402 debug2("Unrecognised server extension \"%s\"", name
);
410 /* Some filexfer v.0 servers don't support large packets */
411 if (ret
->version
== 0)
412 ret
->transfer_buflen
= MIN(ret
->transfer_buflen
, 20480);
414 ret
->limit_kbps
= limit_kbps
;
415 if (ret
->limit_kbps
> 0) {
416 bandwidth_limit_init(&ret
->bwlimit_in
, ret
->limit_kbps
,
417 ret
->transfer_buflen
);
418 bandwidth_limit_init(&ret
->bwlimit_out
, ret
->limit_kbps
,
419 ret
->transfer_buflen
);
426 sftp_proto_version(struct sftp_conn
*conn
)
428 return conn
->version
;
432 do_close(struct sftp_conn
*conn
, char *handle
, u_int handle_len
)
440 buffer_put_char(&msg
, SSH2_FXP_CLOSE
);
441 buffer_put_int(&msg
, id
);
442 buffer_put_string(&msg
, handle
, handle_len
);
443 send_msg(conn
, &msg
);
444 debug3("Sent message SSH2_FXP_CLOSE I:%u", id
);
446 status
= get_status(conn
, id
);
447 if (status
!= SSH2_FX_OK
)
448 error("Couldn't close file: %s", fx2txt(status
));
457 do_lsreaddir(struct sftp_conn
*conn
, char *path
, int print_flag
,
461 u_int count
, type
, id
, handle_len
, i
, expected_id
, ents
= 0;
463 int status
= SSH2_FX_FAILURE
;
471 buffer_put_char(&msg
, SSH2_FXP_OPENDIR
);
472 buffer_put_int(&msg
, id
);
473 buffer_put_cstring(&msg
, path
);
474 send_msg(conn
, &msg
);
476 handle
= get_handle(conn
, id
, &handle_len
,
477 "remote readdir(\"%s\")", path
);
478 if (handle
== NULL
) {
485 *dir
= xcalloc(1, sizeof(**dir
));
489 for (; !interrupted
;) {
490 id
= expected_id
= conn
->msg_id
++;
492 debug3("Sending SSH2_FXP_READDIR I:%u", id
);
495 buffer_put_char(&msg
, SSH2_FXP_READDIR
);
496 buffer_put_int(&msg
, id
);
497 buffer_put_string(&msg
, handle
, handle_len
);
498 send_msg(conn
, &msg
);
504 type
= buffer_get_char(&msg
);
505 id
= buffer_get_int(&msg
);
507 debug3("Received reply T:%u I:%u", type
, id
);
509 if (id
!= expected_id
)
510 fatal("ID mismatch (%u != %u)", id
, expected_id
);
512 if (type
== SSH2_FXP_STATUS
) {
513 status
= buffer_get_int(&msg
);
514 debug3("Received SSH2_FXP_STATUS %d", status
);
515 if (status
== SSH2_FX_EOF
)
517 error("Couldn't read directory: %s", fx2txt(status
));
519 } else if (type
!= SSH2_FXP_NAME
)
520 fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
521 SSH2_FXP_NAME
, type
);
523 count
= buffer_get_int(&msg
);
526 debug3("Received %d SSH2_FXP_NAME responses", count
);
527 for (i
= 0; i
< count
; i
++) {
528 char *filename
, *longname
;
531 filename
= buffer_get_string(&msg
, NULL
);
532 longname
= buffer_get_string(&msg
, NULL
);
533 a
= decode_attrib(&msg
);
536 printf("%s\n", longname
);
539 * Directory entries should never contain '/'
540 * These can be used to attack recursive ops
541 * (e.g. send '../../../../etc/passwd')
543 if (strchr(filename
, '/') != NULL
) {
544 error("Server sent suspect path \"%s\" "
545 "during readdir of \"%s\"", filename
, path
);
547 *dir
= xrealloc(*dir
, ents
+ 2, sizeof(**dir
));
548 (*dir
)[ents
] = xcalloc(1, sizeof(***dir
));
549 (*dir
)[ents
]->filename
= xstrdup(filename
);
550 (*dir
)[ents
]->longname
= xstrdup(longname
);
551 memcpy(&(*dir
)[ents
]->a
, a
, sizeof(*a
));
552 (*dir
)[++ents
] = NULL
;
562 do_close(conn
, handle
, handle_len
);
565 if (status
!= 0 && dir
!= NULL
) {
566 /* Don't return results on error */
567 free_sftp_dirents(*dir
);
569 } else if (interrupted
&& dir
!= NULL
&& *dir
!= NULL
) {
570 /* Don't return partial matches on interrupt */
571 free_sftp_dirents(*dir
);
572 *dir
= xcalloc(1, sizeof(**dir
));
580 do_readdir(struct sftp_conn
*conn
, char *path
, SFTP_DIRENT
***dir
)
582 return(do_lsreaddir(conn
, path
, 0, dir
));
585 void free_sftp_dirents(SFTP_DIRENT
**s
)
591 for (i
= 0; s
[i
]; i
++) {
592 free(s
[i
]->filename
);
593 free(s
[i
]->longname
);
600 do_rm(struct sftp_conn
*conn
, char *path
)
604 debug2("Sending SSH2_FXP_REMOVE \"%s\"", path
);
607 send_string_request(conn
, id
, SSH2_FXP_REMOVE
, path
, strlen(path
));
608 status
= get_status(conn
, id
);
609 if (status
!= SSH2_FX_OK
)
610 error("Couldn't delete file: %s", fx2txt(status
));
615 do_mkdir(struct sftp_conn
*conn
, char *path
, Attrib
*a
, int print_flag
)
620 send_string_attrs_request(conn
, id
, SSH2_FXP_MKDIR
, path
,
623 status
= get_status(conn
, id
);
624 if (status
!= SSH2_FX_OK
&& print_flag
)
625 error("Couldn't create directory: %s", fx2txt(status
));
631 do_rmdir(struct sftp_conn
*conn
, char *path
)
636 send_string_request(conn
, id
, SSH2_FXP_RMDIR
, path
,
639 status
= get_status(conn
, id
);
640 if (status
!= SSH2_FX_OK
)
641 error("Couldn't remove directory: %s", fx2txt(status
));
647 do_stat(struct sftp_conn
*conn
, char *path
, int quiet
)
653 send_string_request(conn
, id
,
654 conn
->version
== 0 ? SSH2_FXP_STAT_VERSION_0
: SSH2_FXP_STAT
,
657 return(get_decode_stat(conn
, id
, quiet
));
661 do_lstat(struct sftp_conn
*conn
, char *path
, int quiet
)
665 if (conn
->version
== 0) {
667 debug("Server version does not support lstat operation");
669 logit("Server version does not support lstat operation");
670 return(do_stat(conn
, path
, quiet
));
674 send_string_request(conn
, id
, SSH2_FXP_LSTAT
, path
,
677 return(get_decode_stat(conn
, id
, quiet
));
682 do_fstat(struct sftp_conn
*conn
, char *handle
, u_int handle_len
, int quiet
)
687 send_string_request(conn
, id
, SSH2_FXP_FSTAT
, handle
,
690 return(get_decode_stat(conn
, id
, quiet
));
695 do_setstat(struct sftp_conn
*conn
, char *path
, Attrib
*a
)
700 send_string_attrs_request(conn
, id
, SSH2_FXP_SETSTAT
, path
,
703 status
= get_status(conn
, id
);
704 if (status
!= SSH2_FX_OK
)
705 error("Couldn't setstat on \"%s\": %s", path
,
712 do_fsetstat(struct sftp_conn
*conn
, char *handle
, u_int handle_len
,
718 send_string_attrs_request(conn
, id
, SSH2_FXP_FSETSTAT
, handle
,
721 status
= get_status(conn
, id
);
722 if (status
!= SSH2_FX_OK
)
723 error("Couldn't fsetstat: %s", fx2txt(status
));
729 do_realpath(struct sftp_conn
*conn
, char *path
)
732 u_int type
, expected_id
, count
, id
;
733 char *filename
, *longname
;
736 expected_id
= id
= conn
->msg_id
++;
737 send_string_request(conn
, id
, SSH2_FXP_REALPATH
, path
,
743 type
= buffer_get_char(&msg
);
744 id
= buffer_get_int(&msg
);
746 if (id
!= expected_id
)
747 fatal("ID mismatch (%u != %u)", id
, expected_id
);
749 if (type
== SSH2_FXP_STATUS
) {
750 u_int status
= buffer_get_int(&msg
);
752 error("Couldn't canonicalize: %s", fx2txt(status
));
755 } else if (type
!= SSH2_FXP_NAME
)
756 fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
757 SSH2_FXP_NAME
, type
);
759 count
= buffer_get_int(&msg
);
761 fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count
);
763 filename
= buffer_get_string(&msg
, NULL
);
764 longname
= buffer_get_string(&msg
, NULL
);
765 a
= decode_attrib(&msg
);
767 debug3("SSH_FXP_REALPATH %s -> %s size %lu", path
, filename
,
768 (unsigned long)a
->size
);
778 do_rename(struct sftp_conn
*conn
, char *oldpath
, char *newpath
,
783 int use_ext
= (conn
->exts
& SFTP_EXT_POSIX_RENAME
) && !force_legacy
;
787 /* Send rename request */
790 buffer_put_char(&msg
, SSH2_FXP_EXTENDED
);
791 buffer_put_int(&msg
, id
);
792 buffer_put_cstring(&msg
, "posix-rename@openssh.com");
794 buffer_put_char(&msg
, SSH2_FXP_RENAME
);
795 buffer_put_int(&msg
, id
);
797 buffer_put_cstring(&msg
, oldpath
);
798 buffer_put_cstring(&msg
, newpath
);
799 send_msg(conn
, &msg
);
800 debug3("Sent message %s \"%s\" -> \"%s\"",
801 use_ext
? "posix-rename@openssh.com" : "SSH2_FXP_RENAME",
805 status
= get_status(conn
, id
);
806 if (status
!= SSH2_FX_OK
)
807 error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath
,
808 newpath
, fx2txt(status
));
814 do_hardlink(struct sftp_conn
*conn
, char *oldpath
, char *newpath
)
819 if ((conn
->exts
& SFTP_EXT_HARDLINK
) == 0) {
820 error("Server does not support hardlink@openssh.com extension");
826 /* Send link request */
828 buffer_put_char(&msg
, SSH2_FXP_EXTENDED
);
829 buffer_put_int(&msg
, id
);
830 buffer_put_cstring(&msg
, "hardlink@openssh.com");
831 buffer_put_cstring(&msg
, oldpath
);
832 buffer_put_cstring(&msg
, newpath
);
833 send_msg(conn
, &msg
);
834 debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
838 status
= get_status(conn
, id
);
839 if (status
!= SSH2_FX_OK
)
840 error("Couldn't link file \"%s\" to \"%s\": %s", oldpath
,
841 newpath
, fx2txt(status
));
847 do_symlink(struct sftp_conn
*conn
, char *oldpath
, char *newpath
)
852 if (conn
->version
< 3) {
853 error("This server does not support the symlink operation");
854 return(SSH2_FX_OP_UNSUPPORTED
);
859 /* Send symlink request */
861 buffer_put_char(&msg
, SSH2_FXP_SYMLINK
);
862 buffer_put_int(&msg
, id
);
863 buffer_put_cstring(&msg
, oldpath
);
864 buffer_put_cstring(&msg
, newpath
);
865 send_msg(conn
, &msg
);
866 debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath
,
870 status
= get_status(conn
, id
);
871 if (status
!= SSH2_FX_OK
)
872 error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath
,
873 newpath
, fx2txt(status
));
879 do_fsync(struct sftp_conn
*conn
, char *handle
, u_int handle_len
)
884 /* Silently return if the extension is not supported */
885 if ((conn
->exts
& SFTP_EXT_FSYNC
) == 0)
890 /* Send fsync request */
893 buffer_put_char(&msg
, SSH2_FXP_EXTENDED
);
894 buffer_put_int(&msg
, id
);
895 buffer_put_cstring(&msg
, "fsync@openssh.com");
896 buffer_put_string(&msg
, handle
, handle_len
);
897 send_msg(conn
, &msg
);
898 debug3("Sent message fsync@openssh.com I:%u", id
);
901 status
= get_status(conn
, id
);
902 if (status
!= SSH2_FX_OK
)
903 error("Couldn't sync file: %s", fx2txt(status
));
910 do_readlink(struct sftp_conn
*conn
, char *path
)
913 u_int type
, expected_id
, count
, id
;
914 char *filename
, *longname
;
917 expected_id
= id
= conn
->msg_id
++;
918 send_string_request(conn
, id
, SSH2_FXP_READLINK
, path
, strlen(path
));
923 type
= buffer_get_char(&msg
);
924 id
= buffer_get_int(&msg
);
926 if (id
!= expected_id
)
927 fatal("ID mismatch (%u != %u)", id
, expected_id
);
929 if (type
== SSH2_FXP_STATUS
) {
930 u_int status
= buffer_get_int(&msg
);
932 error("Couldn't readlink: %s", fx2txt(status
));
935 } else if (type
!= SSH2_FXP_NAME
)
936 fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
937 SSH2_FXP_NAME
, type
);
939 count
= buffer_get_int(&msg
);
941 fatal("Got multiple names (%d) from SSH_FXP_READLINK", count
);
943 filename
= buffer_get_string(&msg
, NULL
);
944 longname
= buffer_get_string(&msg
, NULL
);
945 a
= decode_attrib(&msg
);
947 debug3("SSH_FXP_READLINK %s -> %s", path
, filename
);
958 do_statvfs(struct sftp_conn
*conn
, const char *path
, struct sftp_statvfs
*st
,
964 if ((conn
->exts
& SFTP_EXT_STATVFS
) == 0) {
965 error("Server does not support statvfs@openssh.com extension");
973 buffer_put_char(&msg
, SSH2_FXP_EXTENDED
);
974 buffer_put_int(&msg
, id
);
975 buffer_put_cstring(&msg
, "statvfs@openssh.com");
976 buffer_put_cstring(&msg
, path
);
977 send_msg(conn
, &msg
);
980 return get_decode_statvfs(conn
, st
, id
, quiet
);
985 do_fstatvfs(struct sftp_conn
*conn
, const char *handle
, u_int handle_len
,
986 struct sftp_statvfs
*st
, int quiet
)
991 if ((conn
->exts
& SFTP_EXT_FSTATVFS
) == 0) {
992 error("Server does not support fstatvfs@openssh.com extension");
1000 buffer_put_char(&msg
, SSH2_FXP_EXTENDED
);
1001 buffer_put_int(&msg
, id
);
1002 buffer_put_cstring(&msg
, "fstatvfs@openssh.com");
1003 buffer_put_string(&msg
, handle
, handle_len
);
1004 send_msg(conn
, &msg
);
1007 return get_decode_statvfs(conn
, st
, id
, quiet
);
1012 send_read_request(struct sftp_conn
*conn
, u_int id
, u_int64_t offset
,
1013 u_int len
, char *handle
, u_int handle_len
)
1019 buffer_put_char(&msg
, SSH2_FXP_READ
);
1020 buffer_put_int(&msg
, id
);
1021 buffer_put_string(&msg
, handle
, handle_len
);
1022 buffer_put_int64(&msg
, offset
);
1023 buffer_put_int(&msg
, len
);
1024 send_msg(conn
, &msg
);
1029 do_download(struct sftp_conn
*conn
, char *remote_path
, char *local_path
,
1030 Attrib
*a
, int preserve_flag
, int resume_flag
, int fsync_flag
)
1035 int local_fd
= -1, status
= 0, write_error
;
1036 int read_error
, write_errno
, reordered
= 0;
1037 u_int64_t offset
= 0, size
, highwater
;
1038 u_int handle_len
, mode
, type
, id
, buflen
, num_req
, max_req
;
1039 off_t progress_counter
;
1045 TAILQ_ENTRY(request
) tq
;
1047 TAILQ_HEAD(reqhead
, request
) requests
;
1048 struct request
*req
;
1050 TAILQ_INIT(&requests
);
1052 if (a
== NULL
&& (a
= do_stat(conn
, remote_path
, 0)) == NULL
)
1055 /* Do not preserve set[ug]id here, as we do not preserve ownership */
1056 if (a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
)
1057 mode
= a
->perm
& 0777;
1061 if ((a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
) &&
1062 (!S_ISREG(a
->perm
))) {
1063 error("Cannot download non-regular file: %s", remote_path
);
1067 if (a
->flags
& SSH2_FILEXFER_ATTR_SIZE
)
1072 buflen
= conn
->transfer_buflen
;
1075 /* Send open request */
1076 id
= conn
->msg_id
++;
1077 buffer_put_char(&msg
, SSH2_FXP_OPEN
);
1078 buffer_put_int(&msg
, id
);
1079 buffer_put_cstring(&msg
, remote_path
);
1080 buffer_put_int(&msg
, SSH2_FXF_READ
);
1081 attrib_clear(&junk
); /* Send empty attributes */
1082 encode_attrib(&msg
, &junk
);
1083 send_msg(conn
, &msg
);
1084 debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id
, remote_path
);
1086 handle
= get_handle(conn
, id
, &handle_len
,
1087 "remote open(\"%s\")", remote_path
);
1088 if (handle
== NULL
) {
1093 local_fd
= open(local_path
,
1094 O_WRONLY
| O_CREAT
| (resume_flag
? 0 : O_TRUNC
), mode
| S_IWUSR
);
1095 if (local_fd
== -1) {
1096 error("Couldn't open local file \"%s\" for writing: %s",
1097 local_path
, strerror(errno
));
1100 offset
= highwater
= 0;
1102 if (fstat(local_fd
, &st
) == -1) {
1103 error("Unable to stat local file \"%s\": %s",
1104 local_path
, strerror(errno
));
1107 if (st
.st_size
< 0) {
1108 error("\"%s\" has negative size", local_path
);
1111 if ((u_int64_t
)st
.st_size
> size
) {
1112 error("Unable to resume download of \"%s\": "
1113 "local file is larger than remote", local_path
);
1115 do_close(conn
, handle
, handle_len
);
1122 offset
= highwater
= st
.st_size
;
1125 /* Read from remote and write to local */
1126 write_error
= read_error
= write_errno
= num_req
= 0;
1128 progress_counter
= offset
;
1130 if (showprogress
&& size
!= 0)
1131 start_progress_meter(remote_path
, size
, &progress_counter
);
1133 while (num_req
> 0 || max_req
> 0) {
1138 * Simulate EOF on interrupt: stop sending new requests and
1139 * allow outstanding requests to drain gracefully
1142 if (num_req
== 0) /* If we haven't started yet... */
1147 /* Send some more requests */
1148 while (num_req
< max_req
) {
1149 debug3("Request range %llu -> %llu (%d/%d)",
1150 (unsigned long long)offset
,
1151 (unsigned long long)offset
+ buflen
- 1,
1153 req
= xcalloc(1, sizeof(*req
));
1154 req
->id
= conn
->msg_id
++;
1156 req
->offset
= offset
;
1159 TAILQ_INSERT_TAIL(&requests
, req
, tq
);
1160 send_read_request(conn
, req
->id
, req
->offset
,
1161 req
->len
, handle
, handle_len
);
1165 get_msg(conn
, &msg
);
1166 type
= buffer_get_char(&msg
);
1167 id
= buffer_get_int(&msg
);
1168 debug3("Received reply T:%u I:%u R:%d", type
, id
, max_req
);
1170 /* Find the request in our queue */
1171 for (req
= TAILQ_FIRST(&requests
);
1172 req
!= NULL
&& req
->id
!= id
;
1173 req
= TAILQ_NEXT(req
, tq
))
1176 fatal("Unexpected reply %u", id
);
1179 case SSH2_FXP_STATUS
:
1180 status
= buffer_get_int(&msg
);
1181 if (status
!= SSH2_FX_EOF
)
1184 TAILQ_REMOVE(&requests
, req
, tq
);
1189 data
= buffer_get_string(&msg
, &len
);
1190 debug3("Received data %llu -> %llu",
1191 (unsigned long long)req
->offset
,
1192 (unsigned long long)req
->offset
+ len
- 1);
1194 fatal("Received more data than asked for "
1195 "%u > %u", len
, req
->len
);
1196 if ((lseek(local_fd
, req
->offset
, SEEK_SET
) == -1 ||
1197 atomicio(vwrite
, local_fd
, data
, len
) != len
) &&
1199 write_errno
= errno
;
1203 else if (!reordered
&& req
->offset
<= highwater
)
1204 highwater
= req
->offset
+ len
;
1205 else if (!reordered
&& req
->offset
> highwater
)
1207 progress_counter
+= len
;
1210 if (len
== req
->len
) {
1211 TAILQ_REMOVE(&requests
, req
, tq
);
1215 /* Resend the request for the missing data */
1216 debug3("Short data block, re-requesting "
1217 "%llu -> %llu (%2d)",
1218 (unsigned long long)req
->offset
+ len
,
1219 (unsigned long long)req
->offset
+
1220 req
->len
- 1, num_req
);
1221 req
->id
= conn
->msg_id
++;
1224 send_read_request(conn
, req
->id
,
1225 req
->offset
, req
->len
, handle
, handle_len
);
1226 /* Reduce the request size */
1228 buflen
= MAX(MIN_READ_SIZE
, len
);
1230 if (max_req
> 0) { /* max_req = 0 iff EOF received */
1231 if (size
> 0 && offset
> size
) {
1232 /* Only one request at a time
1233 * after the expected EOF */
1234 debug3("Finish at %llu (%2d)",
1235 (unsigned long long)offset
,
1238 } else if (max_req
<= conn
->num_requests
) {
1244 fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
1245 SSH2_FXP_DATA
, type
);
1249 if (showprogress
&& size
)
1250 stop_progress_meter();
1253 if (TAILQ_FIRST(&requests
) != NULL
)
1254 fatal("Transfer complete, but requests still in queue");
1255 /* Truncate at highest contiguous point to avoid holes on interrupt */
1256 if (read_error
|| write_error
|| interrupted
) {
1257 if (reordered
&& resume_flag
) {
1258 error("Unable to resume download of \"%s\": "
1259 "server reordered requests", local_path
);
1261 debug("truncating at %llu", (unsigned long long)highwater
);
1262 ftruncate(local_fd
, highwater
);
1265 error("Couldn't read from remote file \"%s\" : %s",
1266 remote_path
, fx2txt(status
));
1268 do_close(conn
, handle
, handle_len
);
1269 } else if (write_error
) {
1270 error("Couldn't write to \"%s\": %s", local_path
,
1271 strerror(write_errno
));
1273 do_close(conn
, handle
, handle_len
);
1275 status
= do_close(conn
, handle
, handle_len
);
1276 if (interrupted
|| status
!= SSH2_FX_OK
)
1278 /* Override umask and utimes if asked */
1280 if (preserve_flag
&& fchmod(local_fd
, mode
) == -1)
1282 if (preserve_flag
&& chmod(local_path
, mode
) == -1)
1283 #endif /* HAVE_FCHMOD */
1284 error("Couldn't set mode on \"%s\": %s", local_path
,
1286 if (preserve_flag
&&
1287 (a
->flags
& SSH2_FILEXFER_ATTR_ACMODTIME
)) {
1288 struct timeval tv
[2];
1289 tv
[0].tv_sec
= a
->atime
;
1290 tv
[1].tv_sec
= a
->mtime
;
1291 tv
[0].tv_usec
= tv
[1].tv_usec
= 0;
1292 if (utimes(local_path
, tv
) == -1)
1293 error("Can't set times on \"%s\": %s",
1294 local_path
, strerror(errno
));
1297 debug("syncing \"%s\"", local_path
);
1298 if (fsync(local_fd
) == -1)
1299 error("Couldn't sync file \"%s\": %s",
1300 local_path
, strerror(errno
));
1311 download_dir_internal(struct sftp_conn
*conn
, char *src
, char *dst
, int depth
,
1312 Attrib
*dirattrib
, int preserve_flag
, int print_flag
, int resume_flag
,
1316 SFTP_DIRENT
**dir_entries
;
1317 char *filename
, *new_src
, *new_dst
;
1320 if (depth
>= MAX_DIR_DEPTH
) {
1321 error("Maximum directory depth exceeded: %d levels", depth
);
1325 if (dirattrib
== NULL
&&
1326 (dirattrib
= do_stat(conn
, src
, 1)) == NULL
) {
1327 error("Unable to stat remote directory \"%s\"", src
);
1330 if (!S_ISDIR(dirattrib
->perm
)) {
1331 error("\"%s\" is not a directory", src
);
1335 printf("Retrieving %s\n", src
);
1337 if (dirattrib
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
)
1338 mode
= dirattrib
->perm
& 01777;
1340 debug("Server did not send permissions for "
1341 "directory \"%s\"", dst
);
1344 if (mkdir(dst
, mode
) == -1 && errno
!= EEXIST
) {
1345 error("mkdir %s: %s", dst
, strerror(errno
));
1349 if (do_readdir(conn
, src
, &dir_entries
) == -1) {
1350 error("%s: Failed to get directory contents", src
);
1354 for (i
= 0; dir_entries
[i
] != NULL
&& !interrupted
; i
++) {
1355 filename
= dir_entries
[i
]->filename
;
1357 new_dst
= path_append(dst
, filename
);
1358 new_src
= path_append(src
, filename
);
1360 if (S_ISDIR(dir_entries
[i
]->a
.perm
)) {
1361 if (strcmp(filename
, ".") == 0 ||
1362 strcmp(filename
, "..") == 0)
1364 if (download_dir_internal(conn
, new_src
, new_dst
,
1365 depth
+ 1, &(dir_entries
[i
]->a
), preserve_flag
,
1366 print_flag
, resume_flag
, fsync_flag
) == -1)
1368 } else if (S_ISREG(dir_entries
[i
]->a
.perm
) ) {
1369 if (do_download(conn
, new_src
, new_dst
,
1370 &(dir_entries
[i
]->a
), preserve_flag
,
1371 resume_flag
, fsync_flag
) == -1) {
1372 error("Download of file %s to %s failed",
1377 logit("%s: not a regular file\n", new_src
);
1383 if (preserve_flag
) {
1384 if (dirattrib
->flags
& SSH2_FILEXFER_ATTR_ACMODTIME
) {
1385 struct timeval tv
[2];
1386 tv
[0].tv_sec
= dirattrib
->atime
;
1387 tv
[1].tv_sec
= dirattrib
->mtime
;
1388 tv
[0].tv_usec
= tv
[1].tv_usec
= 0;
1389 if (utimes(dst
, tv
) == -1)
1390 error("Can't set times on \"%s\": %s",
1391 dst
, strerror(errno
));
1393 debug("Server did not send times for directory "
1397 free_sftp_dirents(dir_entries
);
1403 download_dir(struct sftp_conn
*conn
, char *src
, char *dst
,
1404 Attrib
*dirattrib
, int preserve_flag
, int print_flag
,
1405 int resume_flag
, int fsync_flag
)
1410 if ((src_canon
= do_realpath(conn
, src
)) == NULL
) {
1411 error("Unable to canonicalize path \"%s\"", src
);
1415 ret
= download_dir_internal(conn
, src_canon
, dst
, 0,
1416 dirattrib
, preserve_flag
, print_flag
, resume_flag
, fsync_flag
);
1422 do_upload(struct sftp_conn
*conn
, char *local_path
, char *remote_path
,
1423 int preserve_flag
, int resume
, int fsync_flag
)
1426 int status
= SSH2_FX_OK
;
1427 u_int handle_len
, id
, type
;
1428 off_t offset
, progress_counter
;
1429 char *handle
, *data
;
1432 Attrib a
, *c
= NULL
;
1435 struct outstanding_ack
{
1439 TAILQ_ENTRY(outstanding_ack
) tq
;
1441 TAILQ_HEAD(ackhead
, outstanding_ack
) acks
;
1442 struct outstanding_ack
*ack
= NULL
;
1446 if ((local_fd
= open(local_path
, O_RDONLY
, 0)) == -1) {
1447 error("Couldn't open local file \"%s\" for reading: %s",
1448 local_path
, strerror(errno
));
1451 if (fstat(local_fd
, &sb
) == -1) {
1452 error("Couldn't fstat local file \"%s\": %s",
1453 local_path
, strerror(errno
));
1457 if (!S_ISREG(sb
.st_mode
)) {
1458 error("%s is not a regular file", local_path
);
1462 stat_to_attrib(&sb
, &a
);
1464 a
.flags
&= ~SSH2_FILEXFER_ATTR_SIZE
;
1465 a
.flags
&= ~SSH2_FILEXFER_ATTR_UIDGID
;
1468 a
.flags
&= ~SSH2_FILEXFER_ATTR_ACMODTIME
;
1471 /* Get remote file size if it exists */
1472 if ((c
= do_stat(conn
, remote_path
, 0)) == NULL
) {
1477 if ((off_t
)c
->size
>= sb
.st_size
) {
1478 error("destination file bigger or same size as "
1484 if (lseek(local_fd
, (off_t
)c
->size
, SEEK_SET
) == -1) {
1492 /* Send open request */
1493 id
= conn
->msg_id
++;
1494 buffer_put_char(&msg
, SSH2_FXP_OPEN
);
1495 buffer_put_int(&msg
, id
);
1496 buffer_put_cstring(&msg
, remote_path
);
1497 buffer_put_int(&msg
, SSH2_FXF_WRITE
|SSH2_FXF_CREAT
|
1498 (resume
? SSH2_FXF_APPEND
: SSH2_FXF_TRUNC
));
1499 encode_attrib(&msg
, &a
);
1500 send_msg(conn
, &msg
);
1501 debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id
, remote_path
);
1505 handle
= get_handle(conn
, id
, &handle_len
,
1506 "remote open(\"%s\")", remote_path
);
1507 if (handle
== NULL
) {
1513 startid
= ackid
= id
+ 1;
1514 data
= xmalloc(conn
->transfer_buflen
);
1516 /* Read from local and write to remote */
1517 offset
= progress_counter
= (resume
? c
->size
: 0);
1519 start_progress_meter(local_path
, sb
.st_size
,
1526 * Can't use atomicio here because it returns 0 on EOF,
1527 * thus losing the last block of the file.
1528 * Simulate an EOF on interrupt, allowing ACKs from the
1531 if (interrupted
|| status
!= SSH2_FX_OK
)
1534 len
= read(local_fd
, data
, conn
->transfer_buflen
);
1535 while ((len
== -1) &&
1536 (errno
== EINTR
|| errno
== EAGAIN
|| errno
== EWOULDBLOCK
));
1539 fatal("Couldn't read from \"%s\": %s", local_path
,
1543 ack
= xcalloc(1, sizeof(*ack
));
1545 ack
->offset
= offset
;
1547 TAILQ_INSERT_TAIL(&acks
, ack
, tq
);
1550 buffer_put_char(&msg
, SSH2_FXP_WRITE
);
1551 buffer_put_int(&msg
, ack
->id
);
1552 buffer_put_string(&msg
, handle
, handle_len
);
1553 buffer_put_int64(&msg
, offset
);
1554 buffer_put_string(&msg
, data
, len
);
1555 send_msg(conn
, &msg
);
1556 debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1557 id
, (unsigned long long)offset
, len
);
1558 } else if (TAILQ_FIRST(&acks
) == NULL
)
1562 fatal("Unexpected ACK %u", id
);
1564 if (id
== startid
|| len
== 0 ||
1565 id
- ackid
>= conn
->num_requests
) {
1569 get_msg(conn
, &msg
);
1570 type
= buffer_get_char(&msg
);
1571 r_id
= buffer_get_int(&msg
);
1573 if (type
!= SSH2_FXP_STATUS
)
1574 fatal("Expected SSH2_FXP_STATUS(%d) packet, "
1575 "got %d", SSH2_FXP_STATUS
, type
);
1577 status
= buffer_get_int(&msg
);
1578 debug3("SSH2_FXP_STATUS %d", status
);
1580 /* Find the request in our queue */
1581 for (ack
= TAILQ_FIRST(&acks
);
1582 ack
!= NULL
&& ack
->id
!= r_id
;
1583 ack
= TAILQ_NEXT(ack
, tq
))
1586 fatal("Can't find request for ID %u", r_id
);
1587 TAILQ_REMOVE(&acks
, ack
, tq
);
1588 debug3("In write loop, ack for %u %u bytes at %lld",
1589 ack
->id
, ack
->len
, (long long)ack
->offset
);
1591 progress_counter
+= ack
->len
;
1596 fatal("%s: offset < 0", __func__
);
1601 stop_progress_meter();
1604 if (status
!= SSH2_FX_OK
) {
1605 error("Couldn't write to remote file \"%s\": %s",
1606 remote_path
, fx2txt(status
));
1610 if (close(local_fd
) == -1) {
1611 error("Couldn't close local file \"%s\": %s", local_path
,
1616 /* Override umask and utimes if asked */
1618 do_fsetstat(conn
, handle
, handle_len
, &a
);
1621 (void)do_fsync(conn
, handle
, handle_len
);
1623 if (do_close(conn
, handle
, handle_len
) != SSH2_FX_OK
)
1631 upload_dir_internal(struct sftp_conn
*conn
, char *src
, char *dst
, int depth
,
1632 int preserve_flag
, int print_flag
, int resume
, int fsync_flag
)
1634 int ret
= 0, status
;
1637 char *filename
, *new_src
, *new_dst
;
1641 if (depth
>= MAX_DIR_DEPTH
) {
1642 error("Maximum directory depth exceeded: %d levels", depth
);
1646 if (stat(src
, &sb
) == -1) {
1647 error("Couldn't stat directory \"%s\": %s",
1648 src
, strerror(errno
));
1651 if (!S_ISDIR(sb
.st_mode
)) {
1652 error("\"%s\" is not a directory", src
);
1656 printf("Entering %s\n", src
);
1659 stat_to_attrib(&sb
, &a
);
1660 a
.flags
&= ~SSH2_FILEXFER_ATTR_SIZE
;
1661 a
.flags
&= ~SSH2_FILEXFER_ATTR_UIDGID
;
1664 a
.flags
&= ~SSH2_FILEXFER_ATTR_ACMODTIME
;
1666 status
= do_mkdir(conn
, dst
, &a
, 0);
1668 * we lack a portable status for errno EEXIST,
1669 * so if we get a SSH2_FX_FAILURE back we must check
1670 * if it was created successfully.
1672 if (status
!= SSH2_FX_OK
) {
1673 if (status
!= SSH2_FX_FAILURE
)
1675 if (do_stat(conn
, dst
, 0) == NULL
)
1679 if ((dirp
= opendir(src
)) == NULL
) {
1680 error("Failed to open dir \"%s\": %s", src
, strerror(errno
));
1684 while (((dp
= readdir(dirp
)) != NULL
) && !interrupted
) {
1687 filename
= dp
->d_name
;
1688 new_dst
= path_append(dst
, filename
);
1689 new_src
= path_append(src
, filename
);
1691 if (lstat(new_src
, &sb
) == -1) {
1692 logit("%s: lstat failed: %s", filename
,
1695 } else if (S_ISDIR(sb
.st_mode
)) {
1696 if (strcmp(filename
, ".") == 0 ||
1697 strcmp(filename
, "..") == 0)
1700 if (upload_dir_internal(conn
, new_src
, new_dst
,
1701 depth
+ 1, preserve_flag
, print_flag
, resume
,
1704 } else if (S_ISREG(sb
.st_mode
)) {
1705 if (do_upload(conn
, new_src
, new_dst
,
1706 preserve_flag
, resume
, fsync_flag
) == -1) {
1707 error("Uploading of file %s to %s failed!",
1712 logit("%s: not a regular file\n", filename
);
1717 do_setstat(conn
, dst
, &a
);
1719 (void) closedir(dirp
);
1724 upload_dir(struct sftp_conn
*conn
, char *src
, char *dst
, int preserve_flag
,
1725 int print_flag
, int resume
, int fsync_flag
)
1730 if ((dst_canon
= do_realpath(conn
, dst
)) == NULL
) {
1731 error("Unable to canonicalize path \"%s\"", dst
);
1735 ret
= upload_dir_internal(conn
, src
, dst_canon
, 0, preserve_flag
,
1736 print_flag
, resume
, fsync_flag
);
1743 path_append(char *p1
, char *p2
)
1746 size_t len
= strlen(p1
) + strlen(p2
) + 2;
1749 strlcpy(ret
, p1
, len
);
1750 if (p1
[0] != '\0' && p1
[strlen(p1
) - 1] != '/')
1751 strlcat(ret
, "/", len
);
1752 strlcat(ret
, p2
, len
);