1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Support for recursive file transfer in PSFTP.

[originally from svn r4990]
[this svn revision also touched putty-wishlist]
This commit is contained in:
Simon Tatham 2004-12-16 16:37:37 +00:00
parent d67bc798aa
commit bee5812a49
2 changed files with 585 additions and 219 deletions

View File

@ -252,6 +252,17 @@ specify the local file name after the remote one:
This will fetch the file on the server called \c{myfile.dat}, but
will save it to your local machine under the name \c{newname.dat}.
To fetch an entire directory recursively, you can use the \c{-r}
option:
\c get -r mydir
\c get -r mydir newname
(If you want to fetch a file whose name starts with a hyphen, you
may have to use the \c{--} special argument, which stops \c{get}
from interpreting anything as a switch after it. For example,
\cq{get -- -silly-name-}.)
\S{psftp-cmd-put} The \c{put} command: send a file to the server
To upload a file to the server from your local PC, you use the
@ -269,6 +280,17 @@ specify the remote file name after the local one:
This will send the local file called \c{myfile.dat}, but will store
it on the server under the name \c{newname.dat}.
To send an entire directory recursively, you can use the \c{-r}
option:
\c put -r mydir
\c put -r mydir newname
(If you want to send a file whose name starts with a hyphen, you may
have to use the \c{--} special argument, which stops \c{put} from
interpreting anything as a switch after it. For example, \cq{put --
-silly-name-}.)
\S{psftp-cmd-regetput} The \c{reget} and \c{reput} commands:
resuming file transfers

782
psftp.c
View File

@ -40,6 +40,14 @@ static Config cfg;
* Higher-level helper functions used in commands.
*/
/*
* Determine whether a string is entirely composed of dots.
*/
static int is_dots(char *str)
{
return str[strspn(str, ".")] == '\0';
}
/*
* Attempt to canonify a pathname starting from the pwd. If
* canonification fails, at least fall back to returning a _valid_
@ -168,6 +176,513 @@ static char *stripslashes(char *str, int local)
return str;
}
/*
* 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;
const char **b = (const char **) bv;
return strcmp(*a, *b);
}
/* ----------------------------------------------------------------------
* The meat of the `get' and `put' commands.
*/
int sftp_get_file(char *fname, char *outfname, int recurse, int restart)
{
struct fxp_handle *fh;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
struct fxp_xfer *xfer;
uint64 offset;
FILE *fp;
int ret;
/*
* In recursive mode, see if we're dealing with a directory.
* (If we're not in recursive mode, we need not even check: the
* subsequent FXP_OPEN will return a usable error message.)
*/
if (recurse) {
struct fxp_attrs attrs;
int result;
sftp_register(req = fxp_stat_send(fname));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
result = fxp_stat_recv(pktin, rreq, &attrs);
if (result &&
(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
(attrs.permissions & 0040000)) {
struct fxp_handle *dirhandle;
int nnames, namesize;
struct fxp_name **ournames;
struct fxp_names *names;
int i;
/*
* First, attempt to create the destination directory,
* unless it already exists.
*/
if (file_type(outfname) != FILE_TYPE_DIRECTORY &&
!create_directory(outfname)) {
printf("%s: Cannot create directory\n", outfname);
return 0;
}
/*
* Now get the list of filenames in the remote
* directory.
*/
sftp_register(req = fxp_opendir_send(fname));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
dirhandle = fxp_opendir_recv(pktin, rreq);
if (!dirhandle) {
printf("%s: unable to open directory: %s\n",
fname, fxp_error());
return 0;
}
nnames = namesize = 0;
ournames = NULL;
while (1) {
int i;
sftp_register(req = fxp_readdir_send(dirhandle));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
names = fxp_readdir_recv(pktin, rreq);
if (names == NULL) {
if (fxp_error_type() == SSH_FX_EOF)
break;
printf("%s: reading directory: %s\n", fname, fxp_error());
sfree(ournames);
return 0;
}
if (names->nnames == 0) {
fxp_free_names(names);
break;
}
if (nnames + names->nnames >= namesize) {
namesize += names->nnames + 128;
ournames = sresize(ournames, namesize, struct fxp_name *);
}
for (i = 0; i < names->nnames; i++)
if (!is_dots(names->names[i].filename))
ournames[nnames++] = fxp_dup_name(&names->names[i]);
fxp_free_names(names);
}
sftp_register(req = fxp_close_send(dirhandle));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
/*
* Sort the names into a clear order. This ought to
* make things more predictable when we're doing a
* reget of the same directory, just in case two
* readdirs on the same remote directory return a
* different order.
*/
qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
/*
* If we're in restart mode, find the last filename on
* this list that already exists. We may have to do a
* reget on _that_ file, but shouldn't have to do
* anything on the previous files.
*
* If none of them exists, of course, we start at 0.
*/
i = 0;
while (i < nnames) {
char *nextoutfname;
int ret;
nextoutfname = dir_file_cat(outfname, ournames[i]->filename);
ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT);
sfree(nextoutfname);
if (ret)
break;
i++;
}
if (i > 0)
i--;
/*
* Now we're ready to recurse. Starting at ournames[i]
* and continuing on to the end of the list, we
* construct a new source and target file name, and
* call sftp_get_file again.
*/
for (; i < nnames; i++) {
char *nextfname, *nextoutfname;
int ret;
nextfname = dupcat(fname, "/", ournames[i]->filename, NULL);
nextoutfname = dir_file_cat(outfname, ournames[i]->filename);
ret = sftp_get_file(nextfname, nextoutfname, recurse, restart);
restart = FALSE; /* after first partial file, do full */
sfree(nextoutfname);
sfree(nextfname);
if (!ret) {
for (i = 0; i < nnames; i++) {
fxp_free_name(ournames[i]);
}
sfree(ournames);
return 0;
}
}
/*
* Done this recursion level. Free everything.
*/
for (i = 0; i < nnames; i++) {
fxp_free_name(ournames[i]);
}
sfree(ournames);
return 1;
}
}
sftp_register(req = fxp_open_send(fname, SSH_FXF_READ));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fh = fxp_open_recv(pktin, rreq);
if (!fh) {
printf("%s: %s\n", fname, fxp_error());
return 0;
}
if (restart) {
fp = fopen(outfname, "rb+");
} else {
fp = fopen(outfname, "wb");
}
if (!fp) {
printf("local: unable to open %s\n", outfname);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
return 0;
}
if (restart) {
long posn;
fseek(fp, 0L, SEEK_END);
posn = ftell(fp);
printf("reget: restarting at file position %ld\n", posn);
offset = uint64_make(0, posn);
} else {
offset = uint64_make(0, 0);
}
printf("remote:%s => local:%s\n", fname, outfname);
/*
* FIXME: we can use FXP_FSTAT here to get the file size, and
* thus put up a progress bar.
*/
ret = 1;
xfer = xfer_download_init(fh, offset);
while (!xfer_done(xfer)) {
void *vbuf;
int ret, len;
int wpos, wlen;
xfer_download_queue(xfer);
pktin = sftp_recv();
ret = xfer_download_gotpkt(xfer, pktin);
if (ret < 0) {
printf("error while reading: %s\n", fxp_error());
ret = 0;
}
while (xfer_download_data(xfer, &vbuf, &len)) {
unsigned char *buf = (unsigned char *)vbuf;
wpos = 0;
while (wpos < len) {
wlen = fwrite(buf + wpos, 1, len - wpos, fp);
if (wlen <= 0) {
printf("error while writing local file\n");
ret = 0;
xfer_set_error(xfer);
}
wpos += wlen;
}
if (wpos < len) { /* we had an error */
ret = 0;
xfer_set_error(xfer);
}
sfree(vbuf);
}
}
xfer_cleanup(xfer);
fclose(fp);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
return ret;
}
int sftp_put_file(char *fname, char *outfname, int recurse, int restart)
{
struct fxp_handle *fh;
struct fxp_xfer *xfer;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
uint64 offset;
FILE *fp;
int ret, err, eof;
/*
* In recursive mode, see if we're dealing with a directory.
* (If we're not in recursive mode, we need not even check: the
* subsequent fopen will return an error message.)
*/
if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) {
struct fxp_attrs attrs;
int result;
int nnames, namesize;
char *name, **ournames;
DirHandle *dh;
int i;
/*
* First, attempt to create the destination directory,
* unless it already exists.
*/
sftp_register(req = fxp_stat_send(outfname));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
result = fxp_stat_recv(pktin, rreq, &attrs);
if (!result ||
!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
!(attrs.permissions & 0040000)) {
sftp_register(req = fxp_mkdir_send(outfname));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
result = fxp_mkdir_recv(pktin, rreq);
if (!result) {
printf("%s: create directory: %s\n", outfname, fxp_error());
return 0;
}
}
/*
* Now get the list of filenames in the local directory.
*/
dh = open_directory(fname);
if (!dh) {
printf("%s: unable to open directory\n", fname);
return 0;
}
nnames = namesize = 0;
ournames = NULL;
while ((name = read_filename(dh)) != NULL) {
if (nnames >= namesize) {
namesize += 128;
ournames = sresize(ournames, namesize, char *);
}
ournames[nnames++] = name;
}
close_directory(dh);
/*
* Sort the names into a clear order. This ought to make
* things more predictable when we're doing a reput of the
* same directory, just in case two readdirs on the same
* local directory return a different order.
*/
qsort(ournames, nnames, sizeof(*ournames), bare_name_compare);
/*
* If we're in restart mode, find the last filename on this
* list that already exists. We may have to do a reput on
* _that_ file, but shouldn't have to do anything on the
* previous files.
*
* If none of them exists, of course, we start at 0.
*/
i = 0;
while (i < nnames) {
char *nextoutfname;
nextoutfname = dupcat(outfname, "/", ournames[i], NULL);
sftp_register(req = fxp_stat_send(nextoutfname));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
result = fxp_stat_recv(pktin, rreq, &attrs);
sfree(nextoutfname);
if (!result)
break;
i++;
}
if (i > 0)
i--;
/*
* Now we're ready to recurse. Starting at ournames[i]
* and continuing on to the end of the list, we
* construct a new source and target file name, and
* call sftp_put_file again.
*/
for (; i < nnames; i++) {
char *nextfname, *nextoutfname;
int ret;
nextfname = dir_file_cat(fname, ournames[i]);
nextoutfname = dupcat(outfname, "/", ournames[i], NULL);
ret = sftp_put_file(nextfname, nextoutfname, recurse, restart);
restart = FALSE; /* after first partial file, do full */
sfree(nextoutfname);
sfree(nextfname);
if (!ret) {
for (i = 0; i < nnames; i++) {
sfree(ournames[i]);
}
sfree(ournames);
return 0;
}
}
/*
* Done this recursion level. Free everything.
*/
for (i = 0; i < nnames; i++) {
sfree(ournames[i]);
}
sfree(ournames);
return 1;
}
fp = fopen(fname, "rb");
if (!fp) {
printf("local: unable to open %s\n", fname);
return 0;
}
if (restart) {
sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE));
} else {
sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE |
SSH_FXF_CREAT | SSH_FXF_TRUNC));
}
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fh = fxp_open_recv(pktin, rreq);
if (!fh) {
printf("%s: %s\n", outfname, fxp_error());
return 0;
}
if (restart) {
char decbuf[30];
struct fxp_attrs attrs;
int ret;
sftp_register(req = fxp_fstat_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
ret = fxp_fstat_recv(pktin, rreq, &attrs);
if (!ret) {
printf("read size of %s: %s\n", outfname, fxp_error());
return 0;
}
if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
printf("read size of %s: size was not given\n", outfname);
return 0;
}
offset = attrs.size;
uint64_decimal(offset, decbuf);
printf("reput: restarting at file position %s\n", decbuf);
if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
printf("reput: remote file is larger than we can deal with\n");
return 0;
}
if (fseek(fp, offset.lo, SEEK_SET) != 0)
fseek(fp, 0, SEEK_END); /* *shrug* */
} else {
offset = uint64_make(0, 0);
}
printf("local:%s => remote:%s\n", fname, outfname);
/*
* FIXME: we can use FXP_FSTAT here to get the file size, and
* thus put up a progress bar.
*/
ret = 1;
xfer = xfer_upload_init(fh, offset);
err = eof = 0;
while ((!err && !eof) || !xfer_done(xfer)) {
char buffer[4096];
int len, ret;
while (xfer_upload_ready(xfer) && !err && !eof) {
len = fread(buffer, 1, sizeof(buffer), fp);
if (len == -1) {
printf("error while reading local file\n");
err = 1;
} else if (len == 0) {
eof = 1;
} else {
xfer_upload_data(xfer, buffer, len);
}
}
if (!xfer_done(xfer)) {
pktin = sftp_recv();
ret = xfer_upload_gotpkt(xfer, pktin);
if (!ret) {
printf("error while writing: %s\n", fxp_error());
err = 1;
}
}
}
xfer_cleanup(xfer);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
fclose(fp);
return ret;
}
/* ----------------------------------------------------------------------
* Actual sftp commands.
*/
@ -197,12 +712,6 @@ int sftp_cmd_quit(struct sftp_command *cmd)
* List a directory. If no arguments are given, list pwd; otherwise
* list the directory given in words[1].
*/
static int sftp_ls_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);
}
int sftp_cmd_ls(struct sftp_command *cmd)
{
struct fxp_handle *dirh;
@ -280,7 +789,7 @@ int sftp_cmd_ls(struct sftp_command *cmd)
* Now we have our filenames. Sort them by actual file
* name, and then output the longname parts.
*/
qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
/*
* And print them.
@ -368,124 +877,46 @@ int sftp_cmd_pwd(struct sftp_command *cmd)
*/
int sftp_general_get(struct sftp_command *cmd, int restart)
{
struct fxp_handle *fh;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
struct fxp_xfer *xfer;
char *fname, *outfname;
uint64 offset;
FILE *fp;
int ret;
char *fname, *origfname, *outfname;
int i, ret;
int recurse = FALSE;
if (back == NULL) {
printf("psftp: not connected to a host; use \"open host.name\"\n");
return 0;
}
if (cmd->nwords < 2) {
i = 1;
while (i < cmd->nwords && cmd->words[i][0] == '-') {
if (!strcmp(cmd->words[i], "--")) {
/* finish processing options */
i++;
break;
} else if (!strcmp(cmd->words[i], "-r")) {
recurse = TRUE;
} else {
printf("get: unrecognised option '%s'\n", cmd->words[i]);
return 0;
}
i++;
}
if (i >= cmd->nwords) {
printf("get: expects a filename\n");
return 0;
}
fname = canonify(cmd->words[1]);
origfname = cmd->words[i++];
fname = canonify(origfname);
if (!fname) {
printf("%s: %s\n", cmd->words[1], fxp_error());
return 0;
}
outfname = (cmd->nwords == 2 ?
stripslashes(cmd->words[1], 0) : cmd->words[2]);
sftp_register(req = fxp_open_send(fname, SSH_FXF_READ));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fh = fxp_open_recv(pktin, rreq);
if (!fh) {
printf("%s: %s\n", fname, fxp_error());
sfree(fname);
printf("%s: %s\n", origfname, fxp_error());
return 0;
}
if (restart) {
fp = fopen(outfname, "rb+");
} else {
fp = fopen(outfname, "wb");
}
outfname = (i >= cmd->nwords ?
stripslashes(origfname, 0) : cmd->words[i++]);
if (!fp) {
printf("local: unable to open %s\n", outfname);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
sfree(fname);
return 0;
}
if (restart) {
long posn;
fseek(fp, 0L, SEEK_END);
posn = ftell(fp);
printf("reget: restarting at file position %ld\n", posn);
offset = uint64_make(0, posn);
} else {
offset = uint64_make(0, 0);
}
printf("remote:%s => local:%s\n", fname, outfname);
/*
* FIXME: we can use FXP_FSTAT here to get the file size, and
* thus put up a progress bar.
*/
ret = 1;
xfer = xfer_download_init(fh, offset);
while (!xfer_done(xfer)) {
void *vbuf;
int ret, len;
int wpos, wlen;
xfer_download_queue(xfer);
pktin = sftp_recv();
ret = xfer_download_gotpkt(xfer, pktin);
if (ret < 0) {
printf("error while reading: %s\n", fxp_error());
ret = 0;
}
while (xfer_download_data(xfer, &vbuf, &len)) {
unsigned char *buf = (unsigned char *)vbuf;
wpos = 0;
while (wpos < len) {
wlen = fwrite(buf + wpos, 1, len - wpos, fp);
if (wlen <= 0) {
printf("error while writing local file\n");
ret = 0;
xfer_set_error(xfer);
}
wpos += wlen;
}
if (wpos < len) { /* we had an error */
ret = 0;
xfer_set_error(xfer);
}
sfree(vbuf);
}
}
xfer_cleanup(xfer);
fclose(fp);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
ret = sftp_get_file(fname, outfname, recurse, restart);
sfree(fname);
@ -508,133 +939,46 @@ int sftp_cmd_reget(struct sftp_command *cmd)
*/
int sftp_general_put(struct sftp_command *cmd, int restart)
{
struct fxp_handle *fh;
struct fxp_xfer *xfer;
char *fname, *origoutfname, *outfname;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
uint64 offset;
FILE *fp;
int ret, err, eof;
int i, ret;
int recurse = FALSE;
if (back == NULL) {
printf("psftp: not connected to a host; use \"open host.name\"\n");
return 0;
}
if (cmd->nwords < 2) {
i = 1;
while (i < cmd->nwords && cmd->words[i][0] == '-') {
if (!strcmp(cmd->words[i], "--")) {
/* finish processing options */
i++;
break;
} else if (!strcmp(cmd->words[i], "-r")) {
recurse = TRUE;
} else {
printf("put: unrecognised option '%s'\n", cmd->words[i]);
return 0;
}
i++;
}
if (i >= cmd->nwords) {
printf("put: expects a filename\n");
return 0;
}
fname = cmd->words[1];
origoutfname = (cmd->nwords == 2 ?
stripslashes(cmd->words[1], 1) : cmd->words[2]);
fname = cmd->words[i++];
origoutfname = (i >= cmd->nwords ?
stripslashes(fname, 1) : cmd->words[i++]);
outfname = canonify(origoutfname);
if (!outfname) {
printf("%s: %s\n", origoutfname, fxp_error());
return 0;
}
fp = fopen(fname, "rb");
if (!fp) {
printf("local: unable to open %s\n", fname);
sfree(outfname);
return 0;
}
if (restart) {
sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE));
} else {
sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE |
SSH_FXF_CREAT | SSH_FXF_TRUNC));
}
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fh = fxp_open_recv(pktin, rreq);
ret = sftp_put_file(fname, outfname, recurse, restart);
if (!fh) {
printf("%s: %s\n", outfname, fxp_error());
sfree(outfname);
return 0;
}
if (restart) {
char decbuf[30];
struct fxp_attrs attrs;
int ret;
sftp_register(req = fxp_fstat_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
ret = fxp_fstat_recv(pktin, rreq, &attrs);
if (!ret) {
printf("read size of %s: %s\n", outfname, fxp_error());
sfree(outfname);
return 0;
}
if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
printf("read size of %s: size was not given\n", outfname);
sfree(outfname);
return 0;
}
offset = attrs.size;
uint64_decimal(offset, decbuf);
printf("reput: restarting at file position %s\n", decbuf);
if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
printf("reput: remote file is larger than we can deal with\n");
sfree(outfname);
return 0;
}
if (fseek(fp, offset.lo, SEEK_SET) != 0)
fseek(fp, 0, SEEK_END); /* *shrug* */
} else {
offset = uint64_make(0, 0);
}
printf("local:%s => remote:%s\n", fname, outfname);
/*
* FIXME: we can use FXP_FSTAT here to get the file size, and
* thus put up a progress bar.
*/
ret = 1;
xfer = xfer_upload_init(fh, offset);
err = eof = 0;
while ((!err && !eof) || !xfer_done(xfer)) {
char buffer[4096];
int len, ret;
while (xfer_upload_ready(xfer) && !err && !eof) {
len = fread(buffer, 1, sizeof(buffer), fp);
if (len == -1) {
printf("error while reading local file\n");
err = 1;
} else if (len == 0) {
eof = 1;
} else {
xfer_upload_data(xfer, buffer, len);
}
}
if (!xfer_done(xfer)) {
pktin = sftp_recv();
ret = xfer_upload_gotpkt(xfer, pktin);
if (!ret) {
printf("error while writing: %s\n", fxp_error());
err = 1;
}
}
}
xfer_cleanup(xfer);
sftp_register(req = fxp_close_send(fh));
rreq = sftp_find_request(pktin = sftp_recv());
assert(rreq == req);
fxp_close_recv(pktin, rreq);
fclose(fp);
sfree(outfname);
return ret;