#include "ssh.h"
#include "sftp.h"
#include "storage.h"
+#include "int64.h"
static int list = 0;
static int verbose = 0;
static int prev_stats_len = 0;
static int scp_unsafe_mode = 0;
static int errs = 0;
-static int gui_mode = 0;
static int try_scp = 1;
static int try_sftp = 1;
static int main_cmd_is_sftp = 0;
static int fallback_cmd_is_sftp = 0;
static int using_sftp = 0;
+static int uploading = 0;
static Backend *back;
static void *backhandle;
-static Config cfg;
+static Conf *conf;
+int sent_eof = FALSE;
-static void source(char *src);
-static void rsource(char *src);
-static void sink(char *targ, char *src);
+static void source(const char *src);
+static void rsource(const char *src);
+static void sink(const char *targ, const char *src);
+
+const char *const appname = "PSCP";
/*
* The maximum amount of queued data we accept before we stop and
*/
#define MAX_SCP_BUFSIZE 16384
-void ldisc_send(void *handle, char *buf, int len, int interactive)
-{
- /*
- * This is only here because of the calls to ldisc_send(NULL,
- * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
- * as an ldisc. So if we get called with any real data, I want
- * to know about it.
- */
- assert(len == 0);
-}
+void ldisc_echoedit_update(void *handle) { }
-static void tell_char(FILE * stream, char c)
+static void tell_char(FILE *stream, char c)
{
- if (!gui_mode)
- fputc(c, stream);
- else
- gui_send_char(stream == stderr, c);
+ fputc(c, stream);
}
-static void tell_str(FILE * stream, char *str)
+static void tell_str(FILE *stream, const char *str)
{
unsigned int i;
tell_char(stream, str[i]);
}
-static void tell_user(FILE * stream, char *fmt, ...)
+static void tell_user(FILE *stream, const char *fmt, ...)
{
char *str, *str2;
va_list ap;
/*
* Print an error message and perform a fatal exit.
*/
-void fatalbox(char *fmt, ...)
+void fatalbox(const char *fmt, ...)
{
char *str, *str2;
va_list ap;
sfree(str2);
errs++;
- if (gui_mode)
- gui_send_errcount(list, errs);
-
cleanup_exit(1);
}
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
{
char *str, *str2;
va_list ap;
sfree(str2);
errs++;
- if (gui_mode)
- gui_send_errcount(list, errs);
-
cleanup_exit(1);
}
-void connection_fatal(void *frontend, char *fmt, ...)
+void nonfatal(const char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Error: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ tell_str(stderr, str2);
+ sfree(str2);
+ errs++;
+}
+void connection_fatal(void *frontend, const char *fmt, ...)
{
char *str, *str2;
va_list ap;
sfree(str2);
errs++;
- if (gui_mode)
- gui_send_errcount(list, errs);
-
cleanup_exit(1);
}
*/
if (is_stderr) {
if (len > 0)
- fwrite(data, 1, len, stderr);
+ if (fwrite(data, 1, len, stderr) < len)
+ /* oh well */;
return 0;
}
- /*
- * If this is before the real session begins, just return.
- */
- if (!outptr)
- return 0;
-
if ((outlen > 0) && (len > 0)) {
unsigned used = outlen;
if (used > len)
return 0;
}
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * No "untrusted" output should get here (the way the code is
+ * currently, it's all diverted by FLAG_STDERR).
+ */
+ assert(!"Unexpected call to from_backend_untrusted()");
+ return 0; /* not reached */
+}
+int from_backend_eof(void *frontend)
+{
+ /*
+ * We usually expect to be the party deciding when to close the
+ * connection, so if we see EOF before we sent it ourselves, we
+ * should panic. The exception is if we're using old-style scp and
+ * downloading rather than uploading.
+ */
+ if ((using_sftp || uploading) && !sent_eof) {
+ connection_fatal(frontend,
+ "Received unexpected end-of-file from server");
+ }
+ return FALSE;
+}
static int ssh_scp_recv(unsigned char *buf, int len)
{
outptr = buf;
}
while (outlen > 0) {
- if (ssh_sftp_loop_iteration() < 0)
+ if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)
return 0; /* doom */
}
static void ssh_scp_init(void)
{
while (!back->sendok(backhandle)) {
- if (ssh_sftp_loop_iteration() < 0)
+ if (back->exitcode(backhandle) >= 0) {
+ errs++;
+ return;
+ }
+ if (ssh_sftp_loop_iteration() < 0) {
+ errs++;
return; /* doom */
+ }
}
/* Work out which backend we ended up using. */
/*
* Print an error message and exit after closing the SSH link.
*/
-static void bump(char *fmt, ...)
+static void bump(const char *fmt, ...)
{
char *str, *str2;
va_list ap;
sfree(str2);
errs++;
- if (back != NULL && back->socket(backhandle) != NULL) {
+ if (back != NULL && back->connected(backhandle)) {
char ch;
back->special(backhandle, TS_EOF);
+ sent_eof = TRUE;
ssh_scp_recv((unsigned char *) &ch, 1);
}
- if (gui_mode)
- gui_send_errcount(list, errs);
-
cleanup_exit(1);
}
+/*
+ * Wait for the reply to a single SFTP request. Parallels the same
+ * function in psftp.c (but isn't centralised into sftp.c because the
+ * latter module handles SFTP only and shouldn't assume that SFTP is
+ * the only thing going on by calling connection_fatal).
+ */
+struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *rreq;
+
+ sftp_register(req);
+ pktin = sftp_recv();
+ if (pktin == NULL)
+ connection_fatal(NULL, "did not receive SFTP response packet "
+ "from server");
+ rreq = sftp_find_request(pktin);
+ if (rreq != req)
+ connection_fatal(NULL, "unable to understand SFTP response packet "
+ "from server: %s", fxp_error());
+ return pktin;
+}
+
/*
* Open an SSH connection to user@host and execute cmd.
*/
bump("Empty host name");
/*
- * Remove fiddly bits of address: remove a colon suffix, and
- * the square brackets around an IPv6 literal address.
+ * Remove a colon suffix.
*/
- if (host[0] == '[') {
- host++;
- host[strcspn(host, "]")] = '\0';
- } else {
- host[strcspn(host, ":")] = '\0';
- }
+ host[host_strcspn(host, ":")] = '\0';
/*
* If we haven't loaded session details already (e.g., from -load),
*/
if (!loaded_session) {
/* Try to load settings for `host' into a temporary config */
- Config cfg2;
- cfg2.host[0] = '\0';
- do_defaults(host, &cfg2);
- if (cfg2.host[0] != '\0') {
+ Conf *conf2 = conf_new();
+ conf_set_str(conf2, CONF_host, "");
+ do_defaults(host, conf2);
+ if (conf_get_str(conf2, CONF_host)[0] != '\0') {
/* Settings present and include hostname */
/* Re-load data into the real config. */
- do_defaults(host, &cfg);
+ do_defaults(host, conf);
} else {
/* Session doesn't exist or mention a hostname. */
/* Use `host' as a bare hostname. */
- strncpy(cfg.host, host, sizeof(cfg.host) - 1);
- cfg.host[sizeof(cfg.host) - 1] = '\0';
+ conf_set_str(conf, CONF_host, host);
}
+ conf_free(conf2);
} else {
/* Patch in hostname `host' to session details. */
- strncpy(cfg.host, host, sizeof(cfg.host) - 1);
- cfg.host[sizeof(cfg.host) - 1] = '\0';
+ conf_set_str(conf, CONF_host, host);
}
/*
* Force use of SSH. (If they got the protocol wrong we assume the
* port is useless too.)
*/
- if (cfg.protocol != PROT_SSH) {
- cfg.protocol = PROT_SSH;
- cfg.port = 22;
+ if (conf_get_int(conf, CONF_protocol) != PROT_SSH) {
+ conf_set_int(conf, CONF_protocol, PROT_SSH);
+ conf_set_int(conf, CONF_port, 22);
}
/*
* Enact command-line overrides.
*/
- cmdline_run_saved(&cfg);
+ cmdline_run_saved(conf);
/*
- * Trim leading whitespace off the hostname if it's there.
+ * Muck about with the hostname in various ways.
*/
{
- int space = strspn(cfg.host, " \t");
- memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
- }
+ char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
+ char *host = hostbuf;
+ char *p, *q;
- /* See if host is of the form user@host */
- if (cfg.host[0] != '\0') {
- char *atsign = strrchr(cfg.host, '@');
- /* Make sure we're not overflowing the user field */
- if (atsign) {
- if (atsign - cfg.host < sizeof cfg.username) {
- strncpy(cfg.username, cfg.host, atsign - cfg.host);
- cfg.username[atsign - cfg.host] = '\0';
+ /*
+ * Trim leading whitespace.
+ */
+ host += strspn(host, " \t");
+
+ /*
+ * See if host is of the form user@host, and separate out
+ * the username if so.
+ */
+ if (host[0] != '\0') {
+ char *atsign = strrchr(host, '@');
+ if (atsign) {
+ *atsign = '\0';
+ conf_set_str(conf, CONF_username, host);
+ host = atsign + 1;
}
- memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
}
- }
- /*
- * Remove any remaining whitespace from the hostname.
- */
- {
- int p1 = 0, p2 = 0;
- while (cfg.host[p2] != '\0') {
- if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
- cfg.host[p1] = cfg.host[p2];
- p1++;
- }
- p2++;
+ /*
+ * Remove any remaining whitespace.
+ */
+ p = hostbuf;
+ q = host;
+ while (*q) {
+ if (*q != ' ' && *q != '\t')
+ *p++ = *q;
+ q++;
}
- cfg.host[p1] = '\0';
+ *p = '\0';
+
+ conf_set_str(conf, CONF_host, hostbuf);
+ sfree(hostbuf);
}
/* Set username */
if (user != NULL && user[0] != '\0') {
- strncpy(cfg.username, user, sizeof(cfg.username) - 1);
- cfg.username[sizeof(cfg.username) - 1] = '\0';
- } else if (cfg.username[0] == '\0') {
+ conf_set_str(conf, CONF_username, user);
+ } else if (conf_get_str(conf, CONF_username)[0] == '\0') {
user = get_username();
if (!user)
bump("Empty user name");
else {
if (verbose)
tell_user(stderr, "Guessing user name: %s", user);
- strncpy(cfg.username, user, sizeof(cfg.username) - 1);
- cfg.username[sizeof(cfg.username) - 1] = '\0';
+ conf_set_str(conf, CONF_username, user);
sfree(user);
}
}
* things like SCP and SFTP: agent forwarding, port forwarding,
* X forwarding.
*/
- cfg.x11_forward = 0;
- cfg.agentfwd = 0;
- cfg.portfwd[0] = cfg.portfwd[1] = '\0';
+ conf_set_int(conf, CONF_x11_forward, 0);
+ conf_set_int(conf, CONF_agentfwd, 0);
+ conf_set_int(conf, CONF_ssh_simple, TRUE);
+ {
+ char *key;
+ while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL)
+ conf_del_str_str(conf, CONF_portfwd, key);
+ }
/*
* Set up main and possibly fallback command depending on
* Attempt to start the SFTP subsystem as a first choice,
* falling back to the provided scp command if that fails.
*/
- cfg.remote_cmd_ptr2 = NULL;
+ conf_set_str(conf, CONF_remote_cmd2, "");
if (try_sftp) {
/* First choice is SFTP subsystem. */
main_cmd_is_sftp = 1;
- strcpy(cfg.remote_cmd, "sftp");
- cfg.ssh_subsys = TRUE;
+ conf_set_str(conf, CONF_remote_cmd, "sftp");
+ conf_set_int(conf, CONF_ssh_subsys, TRUE);
if (try_scp) {
/* Fallback is to use the provided scp command. */
fallback_cmd_is_sftp = 0;
- cfg.remote_cmd_ptr2 = cmd;
- cfg.ssh_subsys2 = FALSE;
+ conf_set_str(conf, CONF_remote_cmd2, cmd);
+ conf_set_int(conf, CONF_ssh_subsys2, FALSE);
} else {
/* Since we're not going to try SCP, we may as well try
* harder to find an SFTP server, since in the current
* implementation we have a spare slot. */
fallback_cmd_is_sftp = 1;
/* see psftp.c for full explanation of this kludge */
- cfg.remote_cmd_ptr2 =
- "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
- "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
- "exec sftp-server";
- cfg.ssh_subsys2 = FALSE;
+ conf_set_str(conf, CONF_remote_cmd2,
+ "test -x /usr/lib/sftp-server &&"
+ " exec /usr/lib/sftp-server\n"
+ "test -x /usr/local/lib/sftp-server &&"
+ " exec /usr/local/lib/sftp-server\n"
+ "exec sftp-server");
+ conf_set_int(conf, CONF_ssh_subsys2, FALSE);
}
} else {
/* Don't try SFTP at all; just try the scp command. */
main_cmd_is_sftp = 0;
- cfg.remote_cmd_ptr = cmd;
- cfg.ssh_subsys = FALSE;
+ conf_set_str(conf, CONF_remote_cmd, cmd);
+ conf_set_int(conf, CONF_ssh_subsys, FALSE);
}
- cfg.nopty = TRUE;
+ conf_set_int(conf, CONF_nopty, TRUE);
back = &ssh_backend;
- err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
- 0, cfg.tcp_keepalives);
+ logctx = log_init(NULL, conf);
+ console_provide_logctx(logctx);
+
+ platform_psftp_pre_conn_setup();
+
+ err = back->init(NULL, &backhandle, conf,
+ conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port),
+ &realhost, 0,
+ conf_get_int(conf, CONF_tcp_keepalives));
if (err != NULL)
bump("ssh_init: %s", err);
- logctx = log_init(NULL, &cfg);
back->provide_logctx(backhandle, logctx);
- console_provide_logctx(logctx);
ssh_scp_init();
- if (verbose && realhost != NULL)
- tell_user(stderr, "Connected to %s\n", realhost);
+ if (verbose && realhost != NULL && errs == 0)
+ tell_user(stderr, "Connected to %s", realhost);
sfree(realhost);
}
/*
* Update statistic information about current file.
*/
-static void print_stats(char *name, unsigned long size, unsigned long done,
+static void print_stats(const char *name, uint64 size, uint64 done,
time_t start, time_t now)
{
float ratebs;
int pct;
int len;
int elap;
+ double donedbl;
+ double sizedbl;
elap = (unsigned long) difftime(now, start);
if (now > start)
- ratebs = (float) done / elap;
+ ratebs = (float) (uint64_to_double(done) / elap);
else
- ratebs = (float) done;
+ ratebs = (float) uint64_to_double(done);
if (ratebs < 1.0)
- eta = size - done;
- else
- eta = (unsigned long) ((size - done) / ratebs);
+ eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done)));
+ else {
+ eta = (unsigned long)
+ ((uint64_to_double(uint64_subtract(size, done)) / ratebs));
+ }
+
etastr = dupprintf("%02ld:%02ld:%02ld",
eta / 3600, (eta % 3600) / 60, eta % 60);
- pct = (int) (100 * (done * 1.0 / size));
+ donedbl = uint64_to_double(done);
+ sizedbl = uint64_to_double(size);
+ pct = (int) (100 * (donedbl * 1.0 / sizedbl));
- if (gui_mode) {
- gui_update_stats(name, size, pct, elap, done, eta,
- (unsigned long) ratebs);
- } else {
- len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
- name, done / 1024, ratebs / 1024.0, etastr, pct);
+ {
+ char donekb[40];
+ /* divide by 1024 to provide kB */
+ uint64_decimal(uint64_shift_right(done, 10), donekb);
+ len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%",
+ name,
+ donekb, ratebs / 1024.0, etastr, pct);
if (len < prev_stats_len)
printf("%*s", prev_stats_len - len, "");
prev_stats_len = len;
- if (done == size)
+ if (uint64_compare(done, size) == 0)
printf("\n");
fflush(stdout);
*/
static char *colon(char *str)
{
- /* Check and process IPv6 literal addresses
- * (eg: 'jeroen@[2001:db8::1]:myfile.txt') */
- char *ipv6 = strchr(str, '[');
- if (ipv6) {
- str = strchr(str, ']');
- if (str) {
- /* Terminate on the closing bracket */
- *str++ = '\0';
- return (str);
- }
- return (NULL);
- }
-
/* We ignore a leading colon, since the hostname cannot be
empty. We also ignore a colon as second character because
of filenames like f:myfile.txt. */
- if (str[0] == '\0' || str[0] == ':' || str[1] == ':')
+ if (str[0] == '\0' || str[0] == ':' ||
+ (str[0] != '[' && str[1] == ':'))
return (NULL);
- while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
- str++;
+ str += host_strcspn(str, ":/\\");
if (*str == ':')
return (str);
else
return (NULL);
}
-/*
- * Return a pointer to the portion of str that comes after the last
- * slash (or backslash or colon, if `local' is TRUE).
- */
-static char *stripslashes(char *str, int local)
-{
- char *p;
-
- if (local) {
- p = strchr(str, ':');
- if (p) str = p+1;
- }
-
- p = strrchr(str, '/');
- if (p) str = p+1;
-
- if (local) {
- p = strrchr(str, '\\');
- if (p) str = p+1;
- }
-
- return str;
-}
-
/*
* Determine whether a string is entirely composed of dots.
*/
} while (p < sizeof(rbuf) && ch != '\n');
rbuf[p - 1] = '\0';
if (resp == 1)
- tell_user(stderr, "%s\n", rbuf);
+ tell_user(stderr, "%s", rbuf);
else
bump("%s", rbuf);
errs++;
back->send(backhandle, buf, len);
return 1;
}
+int sftp_sendbuffer(void)
+{
+ return back->sendbuffer(backhandle);
+}
/* ----------------------------------------------------------------------
* sftp-based replacement for the hacky `pscp -ls'.
const struct fxp_name *b = (const struct fxp_name *) bv;
return strcmp(a->filename, b->filename);
}
-void scp_sftp_listdir(char *dirname)
+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, *rreq;
+ struct sftp_request *req;
int nnames, namesize;
int i;
printf("Listing directory %s\n", dirname);
- sftp_register(req = fxp_opendir_send(dirname));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- dirh = fxp_opendir_recv(pktin, rreq);
+ req = fxp_opendir_send(dirname);
+ pktin = sftp_wait_for_reply(req);
+ dirh = fxp_opendir_recv(pktin, req);
if (dirh == NULL) {
printf("Unable to open %s: %s\n", dirname, fxp_error());
while (1) {
- sftp_register(req = fxp_readdir_send(dirh));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- names = fxp_readdir_recv(pktin, rreq);
+ req = fxp_readdir_send(dirh);
+ pktin = sftp_wait_for_reply(req);
+ names = fxp_readdir_recv(pktin, req);
if (names == NULL) {
if (fxp_error_type() == SSH_FX_EOF)
names->nnames = 0; /* prevent free_names */
fxp_free_names(names);
}
- sftp_register(req = fxp_close_send(dirh));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- fxp_close_recv(pktin, rreq);
+ 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.
*/
- qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
+ if (nnames > 0)
+ qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
/*
* And print them.
*/
for (i = 0; i < nnames; i++)
printf("%s\n", ournames[i].longname);
+
+ sfree(ournames);
}
}
static struct fxp_xfer *scp_sftp_xfer;
static uint64 scp_sftp_fileoffset;
-int scp_source_setup(char *target, int shouldbedir)
+int scp_source_setup(const char *target, int shouldbedir)
{
if (using_sftp) {
/*
* directory.
*/
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
struct fxp_attrs attrs;
int ret;
return 1;
}
- sftp_register(req = fxp_stat_send(target));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- ret = fxp_stat_recv(pktin, rreq, &attrs);
+ req = fxp_stat_send(target);
+ pktin = sftp_wait_for_reply(req);
+ ret = fxp_stat_recv(pktin, req, &attrs);
if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
scp_sftp_targetisdir = 0;
}
}
-int scp_send_filename(char *name, unsigned long size, int modes)
+int scp_send_filename(const char *name, uint64 size, int permissions)
{
if (using_sftp) {
char *fullname;
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
+ struct fxp_attrs attrs;
if (scp_sftp_targetisdir) {
fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
fullname = dupstr(scp_sftp_remotepath);
}
- sftp_register(req = fxp_open_send(fullname, SSH_FXF_WRITE |
- SSH_FXF_CREAT | SSH_FXF_TRUNC));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- scp_sftp_filehandle = fxp_open_recv(pktin, rreq);
+ attrs.flags = 0;
+ PUT_PERMISSIONS(attrs, permissions);
+
+ req = fxp_open_send(fullname,
+ SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC,
+ &attrs);
+ pktin = sftp_wait_for_reply(req);
+ scp_sftp_filehandle = fxp_open_recv(pktin, req);
if (!scp_sftp_filehandle) {
tell_user(stderr, "pscp: unable to open %s: %s",
fullname, fxp_error());
+ sfree(fullname);
errs++;
return 1;
}
return 0;
} else {
char buf[40];
- sprintf(buf, "C%04o %lu ", modes, size);
+ char sizestr[40];
+ uint64_decimal(size, sizestr);
+ if (permissions < 0)
+ permissions = 0644;
+ sprintf(buf, "C%04o %s ", (int)(permissions & 07777), sizestr);
back->send(backhandle, buf, strlen(buf));
back->send(backhandle, name, strlen(name));
back->send(backhandle, "\n", 1);
while (!xfer_upload_ready(scp_sftp_xfer)) {
pktin = sftp_recv();
ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
- if (!ret) {
- tell_user(stderr, "error while writing: %s\n", fxp_error());
+ if (ret <= 0) {
+ tell_user(stderr, "error while writing: %s", fxp_error());
+ if (ret == INT_MIN) /* pktin not even freed */
+ sfree(pktin);
errs++;
return 1;
}
if (using_sftp) {
struct fxp_attrs attrs;
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
int ret;
while (!xfer_done(scp_sftp_xfer)) {
pktin = sftp_recv();
- xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+ ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+ if (ret <= 0) {
+ tell_user(stderr, "error while writing: %s", fxp_error());
+ if (ret == INT_MIN) /* pktin not even freed */
+ sfree(pktin);
+ errs++;
+ return 1;
+ }
}
xfer_cleanup(scp_sftp_xfer);
attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
attrs.atime = scp_sftp_atime;
attrs.mtime = scp_sftp_mtime;
- sftp_register(req = fxp_fsetstat_send(scp_sftp_filehandle, attrs));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- ret = fxp_fsetstat_recv(pktin, rreq);
+ req = fxp_fsetstat_send(scp_sftp_filehandle, attrs);
+ pktin = sftp_wait_for_reply(req);
+ ret = fxp_fsetstat_recv(pktin, req);
if (!ret) {
- tell_user(stderr, "unable to set file times: %s\n", fxp_error());
+ tell_user(stderr, "unable to set file times: %s", fxp_error());
errs++;
}
}
- sftp_register(req = fxp_close_send(scp_sftp_filehandle));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- fxp_close_recv(pktin, rreq);
+ req = fxp_close_send(scp_sftp_filehandle);
+ pktin = sftp_wait_for_reply(req);
+ fxp_close_recv(pktin, req);
scp_has_times = 0;
return 0;
} else {
scp_sftp_remotepath = data;
}
-int scp_send_dirname(char *name, int modes)
+int scp_send_dirname(const char *name, int modes)
{
if (using_sftp) {
char *fullname;
char const *err;
struct fxp_attrs attrs;
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
int ret;
if (scp_sftp_targetisdir) {
* exists and is a directory we will assume we were either
* successful or it didn't matter.
*/
- sftp_register(req = fxp_mkdir_send(fullname));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- ret = fxp_mkdir_recv(pktin, rreq);
+ req = fxp_mkdir_send(fullname);
+ pktin = sftp_wait_for_reply(req);
+ ret = fxp_mkdir_recv(pktin, req);
if (!ret)
err = fxp_error();
else
err = "server reported no error";
- sftp_register(req = fxp_stat_send(fullname));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- ret = fxp_stat_recv(pktin, rreq, &attrs);
+ req = fxp_stat_send(fullname);
+ pktin = sftp_wait_for_reply(req);
+ ret = fxp_stat_recv(pktin, req, &attrs);
if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
!(attrs.permissions & 0040000)) {
tell_user(stderr, "unable to create directory %s: %s",
fullname, err);
+ sfree(fullname);
errs++;
return 1;
}
* right at the start, whereas scp_sink_init is called to
* initialise every level of recursion in the protocol.
*/
-int scp_sink_setup(char *source, int preserve, int recursive)
+int scp_sink_setup(const char *source, int preserve, int recursive)
{
if (using_sftp) {
char *newsource;
if (!wc_unescape(newsource, source)) {
/* Yes, here we go; it's a wildcard. Bah. */
char *dupsource, *lastpart, *dirpart, *wildcard;
+
+ sfree(newsource);
+
dupsource = dupstr(source);
lastpart = stripslashes(dupsource, 0);
wildcard = dupstr(lastpart);
int action; /* FILE, DIR, ENDDIR */
char *buf; /* will need freeing after use */
char *name; /* filename or dirname (not ENDDIR) */
- int mode; /* access mode (not ENDDIR) */
- unsigned long size; /* file size (not ENDDIR) */
+ long permissions; /* access permissions (not ENDDIR) */
+ uint64 size; /* file size (not ENDDIR) */
int settime; /* 1 if atime and mtime are filled */
unsigned long atime, mtime; /* access times for the file */
};
int must_free_fname;
struct fxp_attrs attrs;
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
int ret;
if (!scp_sftp_dirstack_head) {
* Now we have a filename. Stat it, and see if it's a file
* or a directory.
*/
- sftp_register(req = fxp_stat_send(fname));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- ret = fxp_stat_recv(pktin, rreq, &attrs);
+ req = fxp_stat_send(fname);
+ pktin = sftp_wait_for_reply(req);
+ ret = fxp_stat_recv(pktin, req, &attrs);
if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
tell_user(stderr, "unable to identify %s: %s", fname,
ret ? "file type not supplied" : fxp_error());
+ if (must_free_fname) sfree(fname);
errs++;
return 1;
}
* list), we must push the other (target,namelist) pair
* on a stack.
*/
- sftp_register(req = fxp_opendir_send(fname));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- dirhandle = fxp_opendir_recv(pktin, rreq);
+ req = fxp_opendir_send(fname);
+ pktin = sftp_wait_for_reply(req);
+ dirhandle = fxp_opendir_recv(pktin, req);
if (!dirhandle) {
- tell_user(stderr, "scp: unable to open directory %s: %s",
+ tell_user(stderr, "pscp: unable to open directory %s: %s",
fname, fxp_error());
if (must_free_fname) sfree(fname);
errs++;
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);
+ req = fxp_readdir_send(dirhandle);
+ pktin = sftp_wait_for_reply(req);
+ names = fxp_readdir_recv(pktin, req);
if (names == NULL) {
if (fxp_error_type() == SSH_FX_EOF)
break;
- tell_user(stderr, "scp: reading directory %s: %s\n",
+ tell_user(stderr, "pscp: reading directory %s: %s",
fname, fxp_error());
+
+ req = fxp_close_send(dirhandle);
+ pktin = sftp_wait_for_reply(req);
+ fxp_close_recv(pktin, req);
+
if (must_free_fname) sfree(fname);
sfree(ournames);
errs++;
*/
} else if (!vet_filename(names->names[i].filename)) {
tell_user(stderr, "ignoring potentially dangerous server-"
- "supplied filename '%s'\n",
+ "supplied filename '%s'",
names->names[i].filename);
} else
ournames[nnames++] = names->names[i];
names->nnames = 0; /* prevent free_names */
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);
+ req = fxp_close_send(dirhandle);
+ pktin = sftp_wait_for_reply(req);
+ fxp_close_recv(pktin, req);
newitem = snew(struct scp_sftp_dirstack);
newitem->next = scp_sftp_dirstack_head;
act->action = SCP_SINK_DIR;
act->buf = dupstr(stripslashes(fname, 0));
act->name = act->buf;
- act->size = 0; /* duhh, it's a directory */
- act->mode = 07777 & attrs.permissions;
+ act->size = uint64_make(0,0); /* duhh, it's a directory */
+ act->permissions = 07777 & attrs.permissions;
if (scp_sftp_preserve &&
(attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
act->atime = attrs.atime;
act->buf = dupstr(stripslashes(fname, 0));
act->name = act->buf;
if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
- if (uint64_compare(attrs.size,
- uint64_make(0, ULONG_MAX)) > 0) {
- act->size = ULONG_MAX; /* *boggle* */
- } else
- act->size = attrs.size.lo;
+ act->size = attrs.size;
} else
- act->size = ULONG_MAX; /* no idea */
- act->mode = 07777 & attrs.permissions;
+ act->size = uint64_make(ULONG_MAX,ULONG_MAX); /* no idea */
+ act->permissions = 07777 & attrs.permissions;
if (scp_sftp_preserve &&
(attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
act->atime = attrs.atime;
act->buf[i - 1] = '\0';
switch (action) {
case '\01': /* error */
- tell_user(stderr, "%s\n", act->buf);
+ tell_user(stderr, "%s", act->buf);
errs++;
continue; /* go round again */
case '\02': /* fatal error */
* If we get here, we must have seen SCP_SINK_FILE or
* SCP_SINK_DIR.
*/
- if (sscanf(act->buf, "%o %lu %n", &act->mode, &act->size, &i) != 2)
- bump("Protocol error: Illegal file descriptor format");
- act->name = act->buf + i;
- return 0;
+ {
+ char sizestr[40];
+
+ if (sscanf(act->buf, "%lo %39s %n", &act->permissions,
+ sizestr, &i) != 2)
+ bump("Protocol error: Illegal file descriptor format");
+ act->size = uint64_from_decimal(sizestr);
+ act->name = act->buf + i;
+ return 0;
+ }
}
}
{
if (using_sftp) {
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
- sftp_register(req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- scp_sftp_filehandle = fxp_open_recv(pktin, rreq);
+ req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ, NULL);
+ pktin = sftp_wait_for_reply(req);
+ scp_sftp_filehandle = fxp_open_recv(pktin, req);
if (!scp_sftp_filehandle) {
tell_user(stderr, "pscp: unable to open %s: %s",
xfer_download_queue(scp_sftp_xfer);
pktin = sftp_recv();
ret = xfer_download_gotpkt(scp_sftp_xfer, pktin);
-
- if (ret < 0) {
+ if (ret <= 0) {
tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+ if (ret == INT_MIN) /* pktin not even freed */
+ sfree(pktin);
errs++;
return -1;
}
{
if (using_sftp) {
struct sftp_packet *pktin;
- struct sftp_request *req, *rreq;
+ struct sftp_request *req;
/*
* Ensure that xfer_done() will work correctly, so we can
xfer_set_error(scp_sftp_xfer);
while (!xfer_done(scp_sftp_xfer)) {
void *vbuf;
- int len;
+ int ret, len;
pktin = sftp_recv();
- xfer_download_gotpkt(scp_sftp_xfer, pktin);
+ ret = xfer_download_gotpkt(scp_sftp_xfer, pktin);
+ if (ret <= 0) {
+ tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+ if (ret == INT_MIN) /* pktin not even freed */
+ sfree(pktin);
+ errs++;
+ return -1;
+ }
if (xfer_download_data(scp_sftp_xfer, &vbuf, &len))
sfree(vbuf);
}
xfer_cleanup(scp_sftp_xfer);
- sftp_register(req = fxp_close_send(scp_sftp_filehandle));
- rreq = sftp_find_request(pktin = sftp_recv());
- assert(rreq == req);
- fxp_close_recv(pktin, rreq);
+ req = fxp_close_send(scp_sftp_filehandle);
+ pktin = sftp_wait_for_reply(req);
+ fxp_close_recv(pktin, req);
return 0;
} else {
back->send(backhandle, "", 1);
va_start(ap, fmt);
errs++;
str = dupvprintf(fmt, ap);
- str2 = dupcat("scp: ", str, "\n", NULL);
+ str2 = dupcat("pscp: ", str, "\n", NULL);
sfree(str);
scp_send_errmsg(str2);
tell_user(stderr, "%s", str2);
/*
* Execute the source part of the SCP protocol.
*/
-static void source(char *src)
+static void source(const char *src)
{
- unsigned long size;
+ uint64 size;
unsigned long mtime, atime;
- char *last;
+ long permissions;
+ const char *last;
RFile *f;
int attr;
- unsigned long i;
- unsigned long stat_bytes;
+ uint64 i;
+ uint64 stat_bytes;
time_t stat_starttime, stat_lasttime;
attr = file_type(src);
/*
* Avoid . and .. directories.
*/
- char *p;
+ const char *p;
p = strrchr(src, '/');
if (!p)
p = strrchr(src, '\\');
if (last == src && strchr(src, ':') != NULL)
last = strchr(src, ':') + 1;
- f = open_existing_file(src, &size, &mtime, &atime);
+ f = open_existing_file(src, &size, &mtime, &atime, &permissions);
if (f == NULL) {
run_err("%s: Cannot open file", src);
return;
}
if (preserve) {
- if (scp_send_filetimes(mtime, atime))
+ if (scp_send_filetimes(mtime, atime)) {
+ close_rfile(f);
return;
+ }
}
- if (verbose)
- tell_user(stderr, "Sending file %s, size=%lu", last, size);
- if (scp_send_filename(last, size, 0644))
+ if (verbose) {
+ char sizestr[40];
+ uint64_decimal(size, sizestr);
+ tell_user(stderr, "Sending file %s, size=%s", last, sizestr);
+ }
+ if (scp_send_filename(last, size, permissions)) {
+ close_rfile(f);
return;
+ }
- stat_bytes = 0;
+ stat_bytes = uint64_make(0,0);
stat_starttime = time(NULL);
stat_lasttime = 0;
- for (i = 0; i < size; i += 4096) {
- char transbuf[4096];
- int j, k = 4096;
+#define PSCP_SEND_BLOCK 4096
+ for (i = uint64_make(0,0);
+ uint64_compare(i,size) < 0;
+ i = uint64_add32(i,PSCP_SEND_BLOCK)) {
+ char transbuf[PSCP_SEND_BLOCK];
+ int j, k = PSCP_SEND_BLOCK;
- if (i + k > size)
- k = size - i;
+ if (uint64_compare(uint64_add32(i, k),size) > 0) /* i + k > size */
+ k = (uint64_subtract(size, i)).lo; /* k = size - i; */
if ((j = read_from_file(f, transbuf, k)) != k) {
if (statistics)
printf("\n");
bump("%s: Network error occurred", src);
if (statistics) {
- stat_bytes += k;
- if (time(NULL) != stat_lasttime || i + k == size) {
+ stat_bytes = uint64_add32(stat_bytes, k);
+ if (time(NULL) != stat_lasttime ||
+ (uint64_compare(uint64_add32(i, k), size) == 0)) {
stat_lasttime = time(NULL);
print_stats(last, size, stat_bytes,
stat_starttime, stat_lasttime);
/*
* Recursively send the contents of a directory.
*/
-static void rsource(char *src)
+static void rsource(const char *src)
{
- char *last;
+ const char *last;
char *save_target;
DirHandle *dir;
/*
* Execute the sink part of the SCP protocol.
*/
-static void sink(char *targ, char *src)
+static void sink(const char *targ, const char *src)
{
char *destfname;
int targisdir = 0;
int exists;
int attr;
WFile *f;
- unsigned long received;
+ uint64 received;
int wrerror = 0;
- unsigned long stat_bytes;
+ uint64 stat_bytes;
time_t stat_starttime, stat_lasttime;
char *stat_name;
if (act.action == SCP_SINK_DIR) {
if (exists && attr != FILE_TYPE_DIRECTORY) {
run_err("%s: Not a directory", destfname);
+ sfree(destfname);
continue;
}
if (!exists) {
if (!create_directory(destfname)) {
run_err("%s: Cannot create directory", destfname);
+ sfree(destfname);
continue;
}
}
sink(destfname, NULL);
/* can we set the timestamp for directories ? */
+ sfree(destfname);
continue;
}
- f = open_new_file(destfname);
+ f = open_new_file(destfname, act.permissions);
if (f == NULL) {
run_err("%s: Cannot create file", destfname);
+ sfree(destfname);
continue;
}
- if (scp_accept_filexfer())
+ if (scp_accept_filexfer()) {
+ sfree(destfname);
+ close_wfile(f);
return;
+ }
- stat_bytes = 0;
+ stat_bytes = uint64_make(0, 0);
stat_starttime = time(NULL);
stat_lasttime = 0;
stat_name = stripslashes(destfname, 1);
- received = 0;
- while (received < act.size) {
- char transbuf[4096];
- unsigned long blksize;
+ received = uint64_make(0, 0);
+ while (uint64_compare(received,act.size) < 0) {
+ char transbuf[32768];
+ uint64 blksize;
int read;
- blksize = 4096;
- if (blksize > (act.size - received))
- blksize = act.size - received;
- read = scp_recv_filedata(transbuf, (int)blksize);
+ blksize = uint64_make(0, 32768);
+ if (uint64_compare(blksize,uint64_subtract(act.size,received)) > 0)
+ blksize = uint64_subtract(act.size,received);
+ read = scp_recv_filedata(transbuf, (int)blksize.lo);
if (read <= 0)
bump("Lost connection");
if (wrerror)
continue;
}
if (statistics) {
- stat_bytes += read;
+ stat_bytes = uint64_add32(stat_bytes,read);
if (time(NULL) > stat_lasttime ||
- received + read == act.size) {
+ uint64_compare(uint64_add32(received, read), act.size) == 0) {
stat_lasttime = time(NULL);
print_stats(stat_name, act.size, stat_bytes,
stat_starttime, stat_lasttime);
}
}
- received += read;
+ received = uint64_add32(received, read);
}
if (act.settime) {
set_file_times(f, act.mtime, act.atime);
close_wfile(f);
if (wrerror) {
run_err("%s: Write error", destfname);
+ sfree(destfname);
continue;
}
(void) scp_finish_filerecv();
*/
static void toremote(int argc, char *argv[])
{
- char *src, *targ, *host, *user;
+ char *src, *wtarg, *host, *user;
+ const char *targ;
char *cmd;
int i, wc_type;
- targ = argv[argc - 1];
+ uploading = 1;
+
+ wtarg = argv[argc - 1];
/* Separate host from filename */
- host = targ;
- targ = colon(targ);
- if (targ == NULL)
- bump("targ == NULL in toremote()");
- *targ++ = '\0';
- if (*targ == '\0')
- targ = ".";
+ host = wtarg;
+ wtarg = colon(wtarg);
+ if (wtarg == NULL)
+ bump("wtarg == NULL in toremote()");
+ *wtarg++ = '\0';
/* Substitute "." for empty target */
+ if (*wtarg == '\0')
+ targ = ".";
+ else
+ targ = wtarg;
/* Separate host and username */
user = host;
*/
static void tolocal(int argc, char *argv[])
{
- char *src, *targ, *host, *user;
+ char *wsrc, *host, *user;
+ const char *src, *targ;
char *cmd;
+ uploading = 0;
+
if (argc != 2)
bump("More than one remote source not supported");
- src = argv[0];
+ wsrc = argv[0];
targ = argv[1];
/* Separate host from filename */
- host = src;
- src = colon(src);
- if (src == NULL)
+ host = wsrc;
+ wsrc = colon(wsrc);
+ if (wsrc == NULL)
bump("Local to local copy not supported");
- *src++ = '\0';
- if (*src == '\0')
- src = ".";
+ *wsrc++ = '\0';
/* Substitute "." for empty filename */
+ if (*wsrc == '\0')
+ src = ".";
+ else
+ src = wsrc;
/* Separate username and hostname */
user = host;
*/
static void get_dir_list(int argc, char *argv[])
{
- char *src, *host, *user;
- char *cmd, *p, *q;
+ char *wsrc, *host, *user;
+ const char *src;
+ char *cmd, *p;
+ const char *q;
char c;
- src = argv[0];
+ wsrc = argv[0];
/* Separate host from filename */
- host = src;
- src = colon(src);
- if (src == NULL)
- bump("Local to local copy not supported");
- *src++ = '\0';
- if (*src == '\0')
- src = ".";
+ host = wsrc;
+ wsrc = colon(wsrc);
+ if (wsrc == NULL)
+ bump("Local file listing not supported");
+ *wsrc++ = '\0';
/* Substitute "." for empty filename */
+ if (*wsrc == '\0')
+ src = ".";
+ else
+ src = wsrc;
/* Separate username and hostname */
user = host;
printf(" -1 -2 force use of particular SSH protocol version\n");
printf(" -4 -6 force use of IPv4 or IPv6\n");
printf(" -C enable compression\n");
- printf(" -i key private key file for authentication\n");
+ printf(" -i key private key file for user authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -hostkey aa:bb:cc:...\n");
+ printf(" manually specify a host key (may be repeated)\n");
printf(" -batch disable all interactive prompts\n");
+ printf(" -proxycmd command\n");
+ printf(" use 'command' as local proxy\n");
printf(" -unsafe allow server-side wildcards (DANGEROUS)\n");
printf(" -sftp force use of SFTP protocol\n");
printf(" -scp force use of SCP protocol\n");
+ printf(" -sshlog file\n");
+ printf(" -sshrawlog file\n");
+ printf(" log protocol details to a file\n");
#if 0
/*
* -gui is an internal option, used by GUI front ends to get
void version(void)
{
- printf("pscp: %s\n", ver);
+ char *buildinfo_text = buildinfo("\n");
+ printf("pscp: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
cleanup_exit(1);
}
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
{
va_list ap;
fprintf(stderr, "pscp: ");
exit(1);
}
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = FALSE;
+
/*
* Main program. (Called `psftp_main' because it gets called from
* *sftp.c; bit silly, I know, but it had to be called _something_.)
#endif
;
cmdline_tooltype = TOOLTYPE_FILETRANSFER;
- ssh_get_line = &console_get_line;
sk_init();
/* Load Default Settings before doing anything else. */
- do_defaults(NULL, &cfg);
+ conf = conf_new();
+ do_defaults(NULL, conf);
loaded_session = FALSE;
for (i = 1; i < argc; i++) {
int ret;
if (argv[i][0] != '-')
break;
- ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);
+ ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, conf);
if (ret == -2) {
cmdline_error("option \"%s\" requires an argument", argv[i]);
} else if (ret == 2) {
preserve = 1;
} else if (strcmp(argv[i], "-q") == 0) {
statistics = 0;
- } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0) {
+ } else if (strcmp(argv[i], "-h") == 0 ||
+ strcmp(argv[i], "-?") == 0 ||
+ strcmp(argv[i], "--help") == 0) {
usage();
- } else if (strcmp(argv[i], "-V") == 0) {
+ } else if (strcmp(argv[i], "-V") == 0 ||
+ strcmp(argv[i], "--version") == 0) {
version();
- } else if (strcmp(argv[i], "-gui") == 0 && i + 1 < argc) {
- gui_enable(argv[++i]);
- gui_mode = 1;
- console_batch_mode = TRUE;
} else if (strcmp(argv[i], "-ls") == 0) {
list = 1;
} else if (strcmp(argv[i], "-batch") == 0) {
tolocal(argc, argv);
}
- if (back != NULL && back->socket(backhandle) != NULL) {
+ if (back != NULL && back->connected(backhandle)) {
char ch;
back->special(backhandle, TS_EOF);
+ sent_eof = TRUE;
ssh_scp_recv((unsigned char *) &ch, 1);
}
random_save_seed();
- if (gui_mode)
- gui_send_errcount(list, errs);
-
cmdline_cleanup();
console_provide_logctx(NULL);
back->free(backhandle);