diff --git a/Recipe b/Recipe index 77eb2aef..bd2962f8 100644 --- a/Recipe +++ b/Recipe @@ -279,7 +279,7 @@ WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc UXSSH = SSH uxnoise uxagentc uxgss uxshare # SFTP implementation (pscp, psftp). -SFTP = sftp sftpcommon logging cmdline +SFTP = psftpcommon sftp sftpcommon logging cmdline # Miscellaneous objects appearing in all the utilities, or all the # network ones, or the Unix or Windows subsets of those in turn. diff --git a/pscp.c b/pscp.c index 230f15d1..f39d2d05 100644 --- a/pscp.c +++ b/pscp.c @@ -599,21 +599,24 @@ size_t sftp_sendbuffer(void) /* ---------------------------------------------------------------------- * sftp-based replacement for the hacky `pscp -ls'. */ -static int sftp_ls_compare(const void *av, const void *bv) +void list_directory_from_sftp_warn_unsorted(void) { - const struct fxp_name *a = (const struct fxp_name *) av; - const struct fxp_name *b = (const struct fxp_name *) bv; - return strcmp(a->filename, b->filename); + fprintf(stderr, + "Directory is too large to sort; writing file names unsorted\n"); } + +void list_directory_from_sftp_print(struct fxp_name *name) +{ + with_stripctrl(san, name->longname) + printf("%s\n", san); +} + void scp_sftp_listdir(const char *dirname) { struct fxp_handle *dirh; struct fxp_names *names; - struct fxp_name *ournames; struct sftp_packet *pktin; struct sftp_request *req; - size_t nnames, namesize; - int i; if (!fxp_init()) { tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); @@ -631,8 +634,8 @@ void scp_sftp_listdir(const char *dirname) tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); errs++; } else { - nnames = namesize = 0; - ournames = NULL; + struct list_directory_from_sftp_ctx *ctx = + list_directory_from_sftp_new(); while (1) { @@ -651,33 +654,17 @@ void scp_sftp_listdir(const char *dirname) break; } - sgrowarrayn(ournames, namesize, nnames, names->nnames); + for (size_t i = 0; i < names->nnames; i++) + list_directory_from_sftp_feed(ctx, &names->names[i]); - for (i = 0; i < names->nnames; i++) - ournames[nnames++] = names->names[i]; - names->nnames = 0; /* prevent free_names */ fxp_free_names(names); } req = fxp_close_send(dirh); pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - /* - * Now we have our filenames. Sort them by actual file - * name, and then output the longname parts. - */ - if (nnames > 0) - qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); - - /* - * And print them. - */ - for (i = 0; i < nnames; i++) { - with_stripctrl(san, ournames[i].longname) - printf("%s\n", san); - } - - sfree(ournames); + list_directory_from_sftp_finish(ctx); + list_directory_from_sftp_free(ctx); } } diff --git a/psftp.c b/psftp.c index 073701d1..6215eba9 100644 --- a/psftp.c +++ b/psftp.c @@ -211,20 +211,6 @@ char *canonify(const char *name) } } -/* - * qsort comparison routine for fxp_name structures. Sorts by real - * file name. - */ -static int sftp_name_compare(const void *av, const void *bv) -{ - const struct fxp_name *const *a = (const struct fxp_name *const *) av; - const struct fxp_name *const *b = (const struct fxp_name *const *) bv; - return strcmp((*a)->filename, (*b)->filename); -} - -/* - * Likewise, but for a bare char *. - */ static int bare_name_compare(const void *av, const void *bv) { const char **a = (const char **) av; @@ -1024,6 +1010,17 @@ int sftp_cmd_close(struct sftp_command *cmd) return 0; } +void list_directory_from_sftp_warn_unsorted(void) +{ + printf("Directory is too large to sort; writing file names unsorted\n"); +} + +void list_directory_from_sftp_print(struct fxp_name *name) +{ + with_stripctrl(san, name->longname) + printf("%s\n", san); +} + /* * List a directory. If no arguments are given, list pwd; otherwise * list the directory given in words[1]. @@ -1032,8 +1029,6 @@ int sftp_cmd_ls(struct sftp_command *cmd) { struct fxp_handle *dirh; struct fxp_names *names; - struct fxp_name **ournames; - size_t nnames, namesize; const char *dir; char *cdir, *unwcdir, *wildcard; struct sftp_packet *pktin; @@ -1091,8 +1086,8 @@ int sftp_cmd_ls(struct sftp_command *cmd) sfree(unwcdir); return 0; } else { - nnames = namesize = 0; - ournames = NULL; + struct list_directory_from_sftp_ctx *ctx = + list_directory_from_sftp_new(); while (1) { @@ -1111,34 +1106,19 @@ int sftp_cmd_ls(struct sftp_command *cmd) break; } - sgrowarrayn(ournames, namesize, nnames, names->nnames); - for (size_t i = 0; i < names->nnames; i++) if (!wildcard || wc_match(wildcard, names->names[i].filename)) - ournames[nnames++] = fxp_dup_name(&names->names[i]); + list_directory_from_sftp_feed(ctx, &names->names[i]); fxp_free_names(names); } + req = fxp_close_send(dirh); pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - /* - * Now we have our filenames. Sort them by actual file - * name, and then output the longname parts. - */ - if (nnames > 0) - qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); - - /* - * And print them. - */ - for (size_t i = 0; i < nnames; i++) { - with_stripctrl(san, ournames[i]->longname) - printf("%s\n", san); - fxp_free_name(ournames[i]); - } - sfree(ournames); + list_directory_from_sftp_finish(ctx); + list_directory_from_sftp_free(ctx); } sfree(cdir); diff --git a/psftp.h b/psftp.h index c9f2d650..db9f5b71 100644 --- a/psftp.h +++ b/psftp.h @@ -1,6 +1,6 @@ /* - * psftp.h: interface between psftp.c / scp.c and each - * platform-specific SFTP module. + * psftp.h: interface between psftp.c / pscp.c, psftpcommon.c, and + * each platform-specific SFTP module. */ #ifndef PUTTY_PSFTP_H @@ -198,4 +198,30 @@ char *dir_file_cat(const char *dir, const char *file); */ char *stripslashes(const char *str, bool local); +/* ---------------------------------------------------------------------- + * In psftpcommon.c + */ + +/* + * qsort comparison routine for fxp_name structures. Sorts by real + * file name. + */ +int sftp_name_compare(const void *av, const void *bv); + +/* + * Shared code for outputting a directory listing in response to a + * stream of name structures from FXP_READDIR operations. Used by + * psftp's ls command and pscp -ls. + */ +struct list_directory_from_sftp_ctx; +struct fxp_name; /* in sftp.h */ +struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void); +void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx, + struct fxp_name *name); +void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx); +void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx); +/* Callbacks provided by the tool front end */ +void list_directory_from_sftp_warn_unsorted(void); +void list_directory_from_sftp_print(struct fxp_name *name); + #endif /* PUTTY_PSFTP_H */ diff --git a/psftpcommon.c b/psftpcommon.c new file mode 100644 index 00000000..21f3737e --- /dev/null +++ b/psftpcommon.c @@ -0,0 +1,102 @@ +/* + * psftpcommon.c: front-end functionality shared between both file + * transfer tools across platforms. (As opposed to sftpcommon.c, which + * has *protocol*-level common code.) + */ + +#include +#include + +#include "putty.h" +#include "sftp.h" +#include "psftp.h" + +#define MAX_NAMES_MEMORY ((size_t)8 << 20) + +/* + * qsort comparison routine for fxp_name structures. Sorts by real + * file name. + */ +int sftp_name_compare(const void *av, const void *bv) +{ + const struct fxp_name *const *a = (const struct fxp_name *const *) av; + const struct fxp_name *const *b = (const struct fxp_name *const *) bv; + return strcmp((*a)->filename, (*b)->filename); +} + +struct list_directory_from_sftp_ctx { + size_t nnames, namesize, total_memory; + struct fxp_name **names; + bool sorting; +}; + +struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void) +{ + struct list_directory_from_sftp_ctx *ctx = + snew(struct list_directory_from_sftp_ctx); + memset(ctx, 0, sizeof(*ctx)); + ctx->sorting = true; + return ctx; +} + +void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx) +{ + for (size_t i = 0; i < ctx->nnames; i++) + fxp_free_name(ctx->names[i]); + sfree(ctx->names); + sfree(ctx); +} + +void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx, + struct fxp_name *name) +{ + if (ctx->sorting) { + /* + * Accumulate these filenames into an array that we'll sort - + * unless the array gets _really_ big, in which case, to avoid + * consuming all the client's memory, we fall back to + * outputting the directory listing unsorted. + */ + size_t this_name_memory = + sizeof(*ctx->names) + sizeof(**ctx->names) + + strlen(name->filename) + + strlen(name->longname); + + if (MAX_NAMES_MEMORY - ctx->total_memory < this_name_memory) { + list_directory_from_sftp_warn_unsorted(); + + /* Output all the previously stored names. */ + for (size_t i = 0; i < ctx->nnames; i++) { + list_directory_from_sftp_print(ctx->names[i]); + fxp_free_name(ctx->names[i]); + } + + /* Don't store further names in that array. */ + sfree(ctx->names); + ctx->names = NULL; + ctx->nnames = 0; + ctx->namesize = 0; + ctx->sorting = false; + + /* And don't forget to output the name passed in this + * actual function call. */ + list_directory_from_sftp_print(name); + } else { + sgrowarray(ctx->names, ctx->namesize, ctx->nnames); + ctx->names[ctx->nnames++] = fxp_dup_name(name); + ctx->total_memory += this_name_memory; + } + } else { + list_directory_from_sftp_print(name); + } +} + +void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx) +{ + if (ctx->nnames > 0) { + assert(ctx->sorting); + qsort(ctx->names, ctx->nnames, sizeof(*ctx->names), sftp_name_compare); + for (size_t i = 0; i < ctx->nnames; i++) + list_directory_from_sftp_print(ctx->names[i]); + } +}