Home | History | Annotate | Line # | Download | only in dist
sftp.c revision 1.5
      1  1.5      adam /*	$NetBSD: sftp.c,v 1.5 2010/11/22 09:53:01 adam Exp $	*/
      2  1.4      adam /* $OpenBSD: sftp.c,v 1.125 2010/06/18 00:58:39 djm Exp $ */
      3  1.1  christos /*
      4  1.1  christos  * Copyright (c) 2001-2004 Damien Miller <djm (at) openbsd.org>
      5  1.1  christos  *
      6  1.1  christos  * Permission to use, copy, modify, and distribute this software for any
      7  1.1  christos  * purpose with or without fee is hereby granted, provided that the above
      8  1.1  christos  * copyright notice and this permission notice appear in all copies.
      9  1.1  christos  *
     10  1.1  christos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  1.1  christos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  1.1  christos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  1.1  christos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  1.1  christos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  1.1  christos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  1.1  christos  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  1.1  christos  */
     18  1.1  christos 
     19  1.2  christos #include "includes.h"
     20  1.5      adam __RCSID("$NetBSD: sftp.c,v 1.5 2010/11/22 09:53:01 adam Exp $");
     21  1.1  christos #include <sys/types.h>
     22  1.1  christos #include <sys/ioctl.h>
     23  1.1  christos #include <sys/wait.h>
     24  1.1  christos #include <sys/stat.h>
     25  1.1  christos #include <sys/socket.h>
     26  1.1  christos #include <sys/param.h>
     27  1.1  christos #include <sys/statvfs.h>
     28  1.1  christos 
     29  1.1  christos #include <ctype.h>
     30  1.1  christos #include <errno.h>
     31  1.1  christos #include <glob.h>
     32  1.1  christos #include <histedit.h>
     33  1.1  christos #include <paths.h>
     34  1.4      adam #include <libgen.h>
     35  1.1  christos #include <signal.h>
     36  1.1  christos #include <stdlib.h>
     37  1.1  christos #include <stdio.h>
     38  1.1  christos #include <string.h>
     39  1.1  christos #include <unistd.h>
     40  1.1  christos #include <util.h>
     41  1.1  christos #include <stdarg.h>
     42  1.1  christos 
     43  1.1  christos #include "xmalloc.h"
     44  1.1  christos #include "log.h"
     45  1.1  christos #include "pathnames.h"
     46  1.1  christos #include "misc.h"
     47  1.1  christos 
     48  1.1  christos #include "sftp.h"
     49  1.1  christos #include "buffer.h"
     50  1.1  christos #include "sftp-common.h"
     51  1.1  christos #include "sftp-client.h"
     52  1.2  christos #include "fmt_scaled.h"
     53  1.1  christos 
     54  1.4      adam #define DEFAULT_COPY_BUFLEN	32768	/* Size of buffer for up/download */
     55  1.4      adam #define DEFAULT_NUM_REQUESTS	256	/* # concurrent outstanding requests */
     56  1.4      adam 
     57  1.1  christos /* File to read commands from */
     58  1.1  christos FILE* infile;
     59  1.1  christos 
     60  1.1  christos /* Are we in batchfile mode? */
     61  1.1  christos int batchmode = 0;
     62  1.1  christos 
     63  1.1  christos /* PID of ssh transport process */
     64  1.1  christos static pid_t sshpid = -1;
     65  1.1  christos 
     66  1.1  christos /* This is set to 0 if the progressmeter is not desired. */
     67  1.1  christos int showprogress = 1;
     68  1.1  christos 
     69  1.4      adam /* When this option is set, we always recursively download/upload directories */
     70  1.4      adam int global_rflag = 0;
     71  1.4      adam 
     72  1.4      adam /* When this option is set, the file transfers will always preserve times */
     73  1.4      adam int global_pflag = 0;
     74  1.4      adam 
     75  1.1  christos /* SIGINT received during command processing */
     76  1.1  christos volatile sig_atomic_t interrupted = 0;
     77  1.1  christos 
     78  1.1  christos /* I wish qsort() took a separate ctx for the comparison function...*/
     79  1.1  christos int sort_flag;
     80  1.1  christos 
     81  1.4      adam /* Context used for commandline completion */
     82  1.4      adam struct complete_ctx {
     83  1.4      adam 	struct sftp_conn *conn;
     84  1.4      adam 	char **remote_pathp;
     85  1.4      adam };
     86  1.4      adam 
     87  1.1  christos int remote_glob(struct sftp_conn *, const char *, int,
     88  1.1  christos     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
     89  1.1  christos 
     90  1.1  christos /* Separators for interactive commands */
     91  1.1  christos #define WHITESPACE " \t\r\n"
     92  1.1  christos 
     93  1.1  christos /* ls flags */
     94  1.4      adam #define LS_LONG_VIEW	0x0001	/* Full view ala ls -l */
     95  1.4      adam #define LS_SHORT_VIEW	0x0002	/* Single row view ala ls -1 */
     96  1.4      adam #define LS_NUMERIC_VIEW	0x0004	/* Long view with numeric uid/gid */
     97  1.4      adam #define LS_NAME_SORT	0x0008	/* Sort by name (default) */
     98  1.4      adam #define LS_TIME_SORT	0x0010	/* Sort by mtime */
     99  1.4      adam #define LS_SIZE_SORT	0x0020	/* Sort by file size */
    100  1.4      adam #define LS_REVERSE_SORT	0x0040	/* Reverse sort order */
    101  1.4      adam #define LS_SHOW_ALL	0x0080	/* Don't skip filenames starting with '.' */
    102  1.4      adam #define LS_SI_UNITS	0x0100	/* Display sizes as K, M, G, etc. */
    103  1.1  christos 
    104  1.4      adam #define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
    105  1.1  christos #define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
    106  1.1  christos 
    107  1.1  christos /* Commands for interactive mode */
    108  1.1  christos #define I_CHDIR		1
    109  1.1  christos #define I_CHGRP		2
    110  1.1  christos #define I_CHMOD		3
    111  1.1  christos #define I_CHOWN		4
    112  1.1  christos #define I_DF		24
    113  1.1  christos #define I_GET		5
    114  1.1  christos #define I_HELP		6
    115  1.1  christos #define I_LCHDIR	7
    116  1.1  christos #define I_LLS		8
    117  1.1  christos #define I_LMKDIR	9
    118  1.1  christos #define I_LPWD		10
    119  1.1  christos #define I_LS		11
    120  1.1  christos #define I_LUMASK	12
    121  1.1  christos #define I_MKDIR		13
    122  1.1  christos #define I_PUT		14
    123  1.1  christos #define I_PWD		15
    124  1.1  christos #define I_QUIT		16
    125  1.1  christos #define I_RENAME	17
    126  1.1  christos #define I_RM		18
    127  1.1  christos #define I_RMDIR		19
    128  1.1  christos #define I_SHELL		20
    129  1.1  christos #define I_SYMLINK	21
    130  1.1  christos #define I_VERSION	22
    131  1.1  christos #define I_PROGRESS	23
    132  1.1  christos 
    133  1.1  christos struct CMD {
    134  1.1  christos 	const char *c;
    135  1.1  christos 	const int n;
    136  1.4      adam 	const int t;
    137  1.1  christos };
    138  1.1  christos 
    139  1.4      adam /* Type of completion */
    140  1.4      adam #define NOARGS	0
    141  1.4      adam #define REMOTE	1
    142  1.4      adam #define LOCAL	2
    143  1.4      adam 
    144  1.1  christos static const struct CMD cmds[] = {
    145  1.4      adam 	{ "bye",	I_QUIT,		NOARGS	},
    146  1.4      adam 	{ "cd",		I_CHDIR,	REMOTE	},
    147  1.4      adam 	{ "chdir",	I_CHDIR,	REMOTE	},
    148  1.4      adam 	{ "chgrp",	I_CHGRP,	REMOTE	},
    149  1.4      adam 	{ "chmod",	I_CHMOD,	REMOTE	},
    150  1.4      adam 	{ "chown",	I_CHOWN,	REMOTE	},
    151  1.4      adam 	{ "df",		I_DF,		REMOTE	},
    152  1.4      adam 	{ "dir",	I_LS,		REMOTE	},
    153  1.4      adam 	{ "exit",	I_QUIT,		NOARGS	},
    154  1.4      adam 	{ "get",	I_GET,		REMOTE	},
    155  1.4      adam 	{ "help",	I_HELP,		NOARGS	},
    156  1.4      adam 	{ "lcd",	I_LCHDIR,	LOCAL	},
    157  1.4      adam 	{ "lchdir",	I_LCHDIR,	LOCAL	},
    158  1.4      adam 	{ "lls",	I_LLS,		LOCAL	},
    159  1.4      adam 	{ "lmkdir",	I_LMKDIR,	LOCAL	},
    160  1.4      adam 	{ "ln",		I_SYMLINK,	REMOTE	},
    161  1.4      adam 	{ "lpwd",	I_LPWD,		LOCAL	},
    162  1.4      adam 	{ "ls",		I_LS,		REMOTE	},
    163  1.4      adam 	{ "lumask",	I_LUMASK,	NOARGS	},
    164  1.4      adam 	{ "mkdir",	I_MKDIR,	REMOTE	},
    165  1.4      adam 	{ "mget",	I_GET,		REMOTE	},
    166  1.4      adam 	{ "mput",	I_PUT,		LOCAL	},
    167  1.4      adam 	{ "progress",	I_PROGRESS,	NOARGS	},
    168  1.4      adam 	{ "put",	I_PUT,		LOCAL	},
    169  1.4      adam 	{ "pwd",	I_PWD,		REMOTE	},
    170  1.4      adam 	{ "quit",	I_QUIT,		NOARGS	},
    171  1.4      adam 	{ "rename",	I_RENAME,	REMOTE	},
    172  1.4      adam 	{ "rm",		I_RM,		REMOTE	},
    173  1.4      adam 	{ "rmdir",	I_RMDIR,	REMOTE	},
    174  1.4      adam 	{ "symlink",	I_SYMLINK,	REMOTE	},
    175  1.4      adam 	{ "version",	I_VERSION,	NOARGS	},
    176  1.4      adam 	{ "!",		I_SHELL,	NOARGS	},
    177  1.4      adam 	{ "?",		I_HELP,		NOARGS	},
    178  1.4      adam 	{ NULL,		-1,		-1	}
    179  1.1  christos };
    180  1.1  christos 
    181  1.4      adam int interactive_loop(struct sftp_conn *, char *file1, char *file2);
    182  1.1  christos 
    183  1.1  christos /* ARGSUSED */
    184  1.1  christos static void
    185  1.1  christos killchild(int signo)
    186  1.1  christos {
    187  1.1  christos 	if (sshpid > 1) {
    188  1.1  christos 		kill(sshpid, SIGTERM);
    189  1.1  christos 		waitpid(sshpid, NULL, 0);
    190  1.1  christos 	}
    191  1.1  christos 
    192  1.1  christos 	_exit(1);
    193  1.1  christos }
    194  1.1  christos 
    195  1.1  christos /* ARGSUSED */
    196  1.1  christos static void
    197  1.1  christos cmd_interrupt(int signo)
    198  1.1  christos {
    199  1.1  christos 	const char msg[] = "\rInterrupt  \n";
    200  1.1  christos 	int olderrno = errno;
    201  1.1  christos 
    202  1.1  christos 	write(STDERR_FILENO, msg, sizeof(msg) - 1);
    203  1.1  christos 	interrupted = 1;
    204  1.1  christos 	errno = olderrno;
    205  1.1  christos }
    206  1.1  christos 
    207  1.1  christos static void
    208  1.1  christos help(void)
    209  1.1  christos {
    210  1.1  christos 	printf("Available commands:\n"
    211  1.1  christos 	    "bye                                Quit sftp\n"
    212  1.1  christos 	    "cd path                            Change remote directory to 'path'\n"
    213  1.1  christos 	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
    214  1.1  christos 	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
    215  1.1  christos 	    "chown own path                     Change owner of file 'path' to 'own'\n"
    216  1.1  christos 	    "df [-hi] [path]                    Display statistics for current directory or\n"
    217  1.1  christos 	    "                                   filesystem containing 'path'\n"
    218  1.1  christos 	    "exit                               Quit sftp\n"
    219  1.4      adam 	    "get [-Ppr] remote [local]          Download file\n"
    220  1.1  christos 	    "help                               Display this help text\n"
    221  1.1  christos 	    "lcd path                           Change local directory to 'path'\n"
    222  1.1  christos 	    "lls [ls-options [path]]            Display local directory listing\n"
    223  1.1  christos 	    "lmkdir path                        Create local directory\n"
    224  1.1  christos 	    "ln oldpath newpath                 Symlink remote file\n"
    225  1.1  christos 	    "lpwd                               Print local working directory\n"
    226  1.4      adam 	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
    227  1.1  christos 	    "lumask umask                       Set local umask to 'umask'\n"
    228  1.1  christos 	    "mkdir path                         Create remote directory\n"
    229  1.1  christos 	    "progress                           Toggle display of progress meter\n"
    230  1.4      adam 	    "put [-Ppr] local [remote]          Upload file\n"
    231  1.1  christos 	    "pwd                                Display remote working directory\n"
    232  1.1  christos 	    "quit                               Quit sftp\n"
    233  1.1  christos 	    "rename oldpath newpath             Rename remote file\n"
    234  1.1  christos 	    "rm path                            Delete remote file\n"
    235  1.1  christos 	    "rmdir path                         Remove remote directory\n"
    236  1.1  christos 	    "symlink oldpath newpath            Symlink remote file\n"
    237  1.1  christos 	    "version                            Show SFTP version\n"
    238  1.1  christos 	    "!command                           Execute 'command' in local shell\n"
    239  1.1  christos 	    "!                                  Escape to local shell\n"
    240  1.1  christos 	    "?                                  Synonym for help\n");
    241  1.1  christos }
    242  1.1  christos 
    243  1.1  christos static void
    244  1.1  christos local_do_shell(const char *args)
    245  1.1  christos {
    246  1.1  christos 	int status;
    247  1.1  christos 	char *shell;
    248  1.1  christos 	pid_t pid;
    249  1.1  christos 
    250  1.1  christos 	if (!*args)
    251  1.1  christos 		args = NULL;
    252  1.1  christos 
    253  1.1  christos 	if ((shell = getenv("SHELL")) == NULL)
    254  1.1  christos 		shell = _PATH_BSHELL;
    255  1.1  christos 
    256  1.1  christos 	if ((pid = fork()) == -1)
    257  1.1  christos 		fatal("Couldn't fork: %s", strerror(errno));
    258  1.1  christos 
    259  1.1  christos 	if (pid == 0) {
    260  1.1  christos 		/* XXX: child has pipe fds to ssh subproc open - issue? */
    261  1.1  christos 		if (args) {
    262  1.1  christos 			debug3("Executing %s -c \"%s\"", shell, args);
    263  1.1  christos 			execl(shell, shell, "-c", args, (char *)NULL);
    264  1.1  christos 		} else {
    265  1.1  christos 			debug3("Executing %s", shell);
    266  1.1  christos 			execl(shell, shell, (char *)NULL);
    267  1.1  christos 		}
    268  1.1  christos 		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
    269  1.1  christos 		    strerror(errno));
    270  1.1  christos 		_exit(1);
    271  1.1  christos 	}
    272  1.1  christos 	while (waitpid(pid, &status, 0) == -1)
    273  1.1  christos 		if (errno != EINTR)
    274  1.1  christos 			fatal("Couldn't wait for child: %s", strerror(errno));
    275  1.1  christos 	if (!WIFEXITED(status))
    276  1.1  christos 		error("Shell exited abnormally");
    277  1.1  christos 	else if (WEXITSTATUS(status))
    278  1.1  christos 		error("Shell exited with status %d", WEXITSTATUS(status));
    279  1.1  christos }
    280  1.1  christos 
    281  1.1  christos static void
    282  1.1  christos local_do_ls(const char *args)
    283  1.1  christos {
    284  1.1  christos 	if (!args || !*args)
    285  1.1  christos 		local_do_shell(_PATH_LS);
    286  1.1  christos 	else {
    287  1.1  christos 		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
    288  1.1  christos 		char *buf = xmalloc(len);
    289  1.1  christos 
    290  1.1  christos 		/* XXX: quoting - rip quoting code from ftp? */
    291  1.1  christos 		snprintf(buf, len, _PATH_LS " %s", args);
    292  1.1  christos 		local_do_shell(buf);
    293  1.1  christos 		xfree(buf);
    294  1.1  christos 	}
    295  1.1  christos }
    296  1.1  christos 
    297  1.1  christos /* Strip one path (usually the pwd) from the start of another */
    298  1.1  christos static char *
    299  1.1  christos path_strip(char *path, char *strip)
    300  1.1  christos {
    301  1.1  christos 	size_t len;
    302  1.1  christos 
    303  1.1  christos 	if (strip == NULL)
    304  1.1  christos 		return (xstrdup(path));
    305  1.1  christos 
    306  1.1  christos 	len = strlen(strip);
    307  1.1  christos 	if (strncmp(path, strip, len) == 0) {
    308  1.1  christos 		if (strip[len - 1] != '/' && path[len] == '/')
    309  1.1  christos 			len++;
    310  1.1  christos 		return (xstrdup(path + len));
    311  1.1  christos 	}
    312  1.1  christos 
    313  1.1  christos 	return (xstrdup(path));
    314  1.1  christos }
    315  1.1  christos 
    316  1.1  christos static char *
    317  1.1  christos make_absolute(char *p, char *pwd)
    318  1.1  christos {
    319  1.1  christos 	char *abs_str;
    320  1.1  christos 
    321  1.1  christos 	/* Derelativise */
    322  1.1  christos 	if (p && p[0] != '/') {
    323  1.1  christos 		abs_str = path_append(pwd, p);
    324  1.1  christos 		xfree(p);
    325  1.1  christos 		return(abs_str);
    326  1.1  christos 	} else
    327  1.1  christos 		return(p);
    328  1.1  christos }
    329  1.1  christos 
    330  1.1  christos static int
    331  1.4      adam parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
    332  1.4      adam     int *rflag)
    333  1.1  christos {
    334  1.1  christos 	extern int opterr, optind, optopt, optreset;
    335  1.1  christos 	int ch;
    336  1.1  christos 
    337  1.1  christos 	optind = optreset = 1;
    338  1.1  christos 	opterr = 0;
    339  1.1  christos 
    340  1.4      adam 	*rflag = *pflag = 0;
    341  1.4      adam 	while ((ch = getopt(argc, argv, "PpRr")) != -1) {
    342  1.1  christos 		switch (ch) {
    343  1.1  christos 		case 'p':
    344  1.1  christos 		case 'P':
    345  1.1  christos 			*pflag = 1;
    346  1.1  christos 			break;
    347  1.4      adam 		case 'r':
    348  1.4      adam 		case 'R':
    349  1.4      adam 			*rflag = 1;
    350  1.4      adam 			break;
    351  1.1  christos 		default:
    352  1.1  christos 			error("%s: Invalid flag -%c", cmd, optopt);
    353  1.1  christos 			return -1;
    354  1.1  christos 		}
    355  1.1  christos 	}
    356  1.1  christos 
    357  1.1  christos 	return optind;
    358  1.1  christos }
    359  1.1  christos 
    360  1.1  christos static int
    361  1.1  christos parse_ls_flags(char **argv, int argc, int *lflag)
    362  1.1  christos {
    363  1.1  christos 	extern int opterr, optind, optopt, optreset;
    364  1.1  christos 	int ch;
    365  1.1  christos 
    366  1.1  christos 	optind = optreset = 1;
    367  1.1  christos 	opterr = 0;
    368  1.1  christos 
    369  1.1  christos 	*lflag = LS_NAME_SORT;
    370  1.4      adam 	while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
    371  1.1  christos 		switch (ch) {
    372  1.1  christos 		case '1':
    373  1.1  christos 			*lflag &= ~VIEW_FLAGS;
    374  1.1  christos 			*lflag |= LS_SHORT_VIEW;
    375  1.1  christos 			break;
    376  1.1  christos 		case 'S':
    377  1.1  christos 			*lflag &= ~SORT_FLAGS;
    378  1.1  christos 			*lflag |= LS_SIZE_SORT;
    379  1.1  christos 			break;
    380  1.1  christos 		case 'a':
    381  1.1  christos 			*lflag |= LS_SHOW_ALL;
    382  1.1  christos 			break;
    383  1.1  christos 		case 'f':
    384  1.1  christos 			*lflag &= ~SORT_FLAGS;
    385  1.1  christos 			break;
    386  1.4      adam 		case 'h':
    387  1.4      adam 			*lflag |= LS_SI_UNITS;
    388  1.4      adam 			break;
    389  1.1  christos 		case 'l':
    390  1.4      adam 			*lflag &= ~LS_SHORT_VIEW;
    391  1.1  christos 			*lflag |= LS_LONG_VIEW;
    392  1.1  christos 			break;
    393  1.1  christos 		case 'n':
    394  1.4      adam 			*lflag &= ~LS_SHORT_VIEW;
    395  1.1  christos 			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
    396  1.1  christos 			break;
    397  1.1  christos 		case 'r':
    398  1.1  christos 			*lflag |= LS_REVERSE_SORT;
    399  1.1  christos 			break;
    400  1.1  christos 		case 't':
    401  1.1  christos 			*lflag &= ~SORT_FLAGS;
    402  1.1  christos 			*lflag |= LS_TIME_SORT;
    403  1.1  christos 			break;
    404  1.1  christos 		default:
    405  1.1  christos 			error("ls: Invalid flag -%c", optopt);
    406  1.1  christos 			return -1;
    407  1.1  christos 		}
    408  1.1  christos 	}
    409  1.1  christos 
    410  1.1  christos 	return optind;
    411  1.1  christos }
    412  1.1  christos 
    413  1.1  christos static int
    414  1.1  christos parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
    415  1.1  christos {
    416  1.1  christos 	extern int opterr, optind, optopt, optreset;
    417  1.1  christos 	int ch;
    418  1.1  christos 
    419  1.1  christos 	optind = optreset = 1;
    420  1.1  christos 	opterr = 0;
    421  1.1  christos 
    422  1.1  christos 	*hflag = *iflag = 0;
    423  1.1  christos 	while ((ch = getopt(argc, argv, "hi")) != -1) {
    424  1.1  christos 		switch (ch) {
    425  1.1  christos 		case 'h':
    426  1.1  christos 			*hflag = 1;
    427  1.1  christos 			break;
    428  1.1  christos 		case 'i':
    429  1.1  christos 			*iflag = 1;
    430  1.1  christos 			break;
    431  1.1  christos 		default:
    432  1.1  christos 			error("%s: Invalid flag -%c", cmd, optopt);
    433  1.1  christos 			return -1;
    434  1.1  christos 		}
    435  1.1  christos 	}
    436  1.1  christos 
    437  1.1  christos 	return optind;
    438  1.1  christos }
    439  1.1  christos 
    440  1.1  christos static int
    441  1.1  christos is_dir(char *path)
    442  1.1  christos {
    443  1.1  christos 	struct stat sb;
    444  1.1  christos 
    445  1.1  christos 	/* XXX: report errors? */
    446  1.1  christos 	if (stat(path, &sb) == -1)
    447  1.1  christos 		return(0);
    448  1.1  christos 
    449  1.1  christos 	return(S_ISDIR(sb.st_mode));
    450  1.1  christos }
    451  1.1  christos 
    452  1.1  christos static int
    453  1.1  christos remote_is_dir(struct sftp_conn *conn, char *path)
    454  1.1  christos {
    455  1.1  christos 	Attrib *a;
    456  1.1  christos 
    457  1.1  christos 	/* XXX: report errors? */
    458  1.1  christos 	if ((a = do_stat(conn, path, 1)) == NULL)
    459  1.1  christos 		return(0);
    460  1.1  christos 	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
    461  1.1  christos 		return(0);
    462  1.1  christos 	return(S_ISDIR(a->perm));
    463  1.1  christos }
    464  1.1  christos 
    465  1.4      adam /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
    466  1.4      adam static int
    467  1.4      adam pathname_is_dir(char *pathname)
    468  1.4      adam {
    469  1.4      adam 	size_t l = strlen(pathname);
    470  1.4      adam 
    471  1.4      adam 	return l > 0 && pathname[l - 1] == '/';
    472  1.4      adam }
    473  1.4      adam 
    474  1.1  christos static int
    475  1.4      adam process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
    476  1.4      adam     int pflag, int rflag)
    477  1.1  christos {
    478  1.1  christos 	char *abs_src = NULL;
    479  1.1  christos 	char *abs_dst = NULL;
    480  1.1  christos 	glob_t g;
    481  1.4      adam 	char *filename, *tmp=NULL;
    482  1.4      adam 	int i, err = 0;
    483  1.1  christos 
    484  1.1  christos 	abs_src = xstrdup(src);
    485  1.1  christos 	abs_src = make_absolute(abs_src, pwd);
    486  1.4      adam 	memset(&g, 0, sizeof(g));
    487  1.1  christos 
    488  1.1  christos 	debug3("Looking up %s", abs_src);
    489  1.4      adam 	if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
    490  1.1  christos 		error("File \"%s\" not found.", abs_src);
    491  1.1  christos 		err = -1;
    492  1.1  christos 		goto out;
    493  1.1  christos 	}
    494  1.1  christos 
    495  1.4      adam 	/*
    496  1.4      adam 	 * If multiple matches then dst must be a directory or
    497  1.4      adam 	 * unspecified.
    498  1.4      adam 	 */
    499  1.4      adam 	if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
    500  1.4      adam 		error("Multiple source paths, but destination "
    501  1.4      adam 		    "\"%s\" is not a directory", dst);
    502  1.1  christos 		err = -1;
    503  1.1  christos 		goto out;
    504  1.1  christos 	}
    505  1.1  christos 
    506  1.1  christos 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
    507  1.4      adam 		tmp = xstrdup(g.gl_pathv[i]);
    508  1.4      adam 		if ((filename = basename(tmp)) == NULL) {
    509  1.4      adam 			error("basename %s: %s", tmp, strerror(errno));
    510  1.4      adam 			xfree(tmp);
    511  1.1  christos 			err = -1;
    512  1.1  christos 			goto out;
    513  1.1  christos 		}
    514  1.1  christos 
    515  1.1  christos 		if (g.gl_matchc == 1 && dst) {
    516  1.1  christos 			if (is_dir(dst)) {
    517  1.4      adam 				abs_dst = path_append(dst, filename);
    518  1.4      adam 			} else {
    519  1.1  christos 				abs_dst = xstrdup(dst);
    520  1.4      adam 			}
    521  1.1  christos 		} else if (dst) {
    522  1.4      adam 			abs_dst = path_append(dst, filename);
    523  1.4      adam 		} else {
    524  1.4      adam 			abs_dst = xstrdup(filename);
    525  1.4      adam 		}
    526  1.4      adam 		xfree(tmp);
    527  1.1  christos 
    528  1.1  christos 		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
    529  1.4      adam 		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
    530  1.4      adam 			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
    531  1.4      adam 			    pflag || global_pflag, 1) == -1)
    532  1.4      adam 				err = -1;
    533  1.4      adam 		} else {
    534  1.4      adam 			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
    535  1.4      adam 			    pflag || global_pflag) == -1)
    536  1.4      adam 				err = -1;
    537  1.4      adam 		}
    538  1.1  christos 		xfree(abs_dst);
    539  1.1  christos 		abs_dst = NULL;
    540  1.1  christos 	}
    541  1.1  christos 
    542  1.1  christos out:
    543  1.1  christos 	xfree(abs_src);
    544  1.1  christos 	globfree(&g);
    545  1.1  christos 	return(err);
    546  1.1  christos }
    547  1.1  christos 
    548  1.1  christos static int
    549  1.4      adam process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
    550  1.4      adam     int pflag, int rflag)
    551  1.1  christos {
    552  1.1  christos 	char *tmp_dst = NULL;
    553  1.1  christos 	char *abs_dst = NULL;
    554  1.4      adam 	char *tmp = NULL, *filename = NULL;
    555  1.1  christos 	glob_t g;
    556  1.1  christos 	int err = 0;
    557  1.4      adam 	int i, dst_is_dir = 1;
    558  1.1  christos 	struct stat sb;
    559  1.1  christos 
    560  1.1  christos 	if (dst) {
    561  1.1  christos 		tmp_dst = xstrdup(dst);
    562  1.1  christos 		tmp_dst = make_absolute(tmp_dst, pwd);
    563  1.1  christos 	}
    564  1.1  christos 
    565  1.1  christos 	memset(&g, 0, sizeof(g));
    566  1.1  christos 	debug3("Looking up %s", src);
    567  1.4      adam 	if (glob(src, GLOB_NOCHECK | GLOB_LIMIT | GLOB_MARK, NULL, &g)) {
    568  1.1  christos 		error("File \"%s\" not found.", src);
    569  1.1  christos 		err = -1;
    570  1.1  christos 		goto out;
    571  1.1  christos 	}
    572  1.1  christos 
    573  1.4      adam 	/* If we aren't fetching to pwd then stash this status for later */
    574  1.4      adam 	if (tmp_dst != NULL)
    575  1.4      adam 		dst_is_dir = remote_is_dir(conn, tmp_dst);
    576  1.4      adam 
    577  1.1  christos 	/* If multiple matches, dst may be directory or unspecified */
    578  1.4      adam 	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
    579  1.4      adam 		error("Multiple paths match, but destination "
    580  1.4      adam 		    "\"%s\" is not a directory", tmp_dst);
    581  1.1  christos 		err = -1;
    582  1.1  christos 		goto out;
    583  1.1  christos 	}
    584  1.1  christos 
    585  1.1  christos 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
    586  1.1  christos 		if (stat(g.gl_pathv[i], &sb) == -1) {
    587  1.1  christos 			err = -1;
    588  1.1  christos 			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
    589  1.1  christos 			continue;
    590  1.1  christos 		}
    591  1.4      adam 
    592  1.4      adam 		tmp = xstrdup(g.gl_pathv[i]);
    593  1.4      adam 		if ((filename = basename(tmp)) == NULL) {
    594  1.4      adam 			error("basename %s: %s", tmp, strerror(errno));
    595  1.4      adam 			xfree(tmp);
    596  1.1  christos 			err = -1;
    597  1.1  christos 			goto out;
    598  1.1  christos 		}
    599  1.1  christos 
    600  1.1  christos 		if (g.gl_matchc == 1 && tmp_dst) {
    601  1.1  christos 			/* If directory specified, append filename */
    602  1.4      adam 			if (dst_is_dir)
    603  1.4      adam 				abs_dst = path_append(tmp_dst, filename);
    604  1.4      adam 			else
    605  1.1  christos 				abs_dst = xstrdup(tmp_dst);
    606  1.1  christos 		} else if (tmp_dst) {
    607  1.4      adam 			abs_dst = path_append(tmp_dst, filename);
    608  1.4      adam 		} else {
    609  1.4      adam 			abs_dst = make_absolute(xstrdup(filename), pwd);
    610  1.4      adam 		}
    611  1.4      adam 		xfree(tmp);
    612  1.1  christos 
    613  1.1  christos 		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
    614  1.4      adam 		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
    615  1.4      adam 			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
    616  1.4      adam 			    pflag || global_pflag, 1) == -1)
    617  1.4      adam 				err = -1;
    618  1.4      adam 		} else {
    619  1.4      adam 			if (do_upload(conn, g.gl_pathv[i], abs_dst,
    620  1.4      adam 			    pflag || global_pflag) == -1)
    621  1.4      adam 				err = -1;
    622  1.4      adam 		}
    623  1.1  christos 	}
    624  1.1  christos 
    625  1.1  christos out:
    626  1.1  christos 	if (abs_dst)
    627  1.1  christos 		xfree(abs_dst);
    628  1.1  christos 	if (tmp_dst)
    629  1.1  christos 		xfree(tmp_dst);
    630  1.1  christos 	globfree(&g);
    631  1.1  christos 	return(err);
    632  1.1  christos }
    633  1.1  christos 
    634  1.1  christos static int
    635  1.1  christos sdirent_comp(const void *aa, const void *bb)
    636  1.1  christos {
    637  1.1  christos 	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
    638  1.1  christos 	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
    639  1.1  christos 	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
    640  1.1  christos 
    641  1.1  christos #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
    642  1.1  christos 	if (sort_flag & LS_NAME_SORT)
    643  1.1  christos 		return (rmul * strcmp(a->filename, b->filename));
    644  1.1  christos 	else if (sort_flag & LS_TIME_SORT)
    645  1.1  christos 		return (rmul * NCMP(a->a.mtime, b->a.mtime));
    646  1.1  christos 	else if (sort_flag & LS_SIZE_SORT)
    647  1.1  christos 		return (rmul * NCMP(a->a.size, b->a.size));
    648  1.1  christos 
    649  1.1  christos 	fatal("Unknown ls sort type");
    650  1.2  christos 	/*NOTREACHED*/
    651  1.2  christos 	return 0;
    652  1.1  christos }
    653  1.1  christos 
    654  1.1  christos /* sftp ls.1 replacement for directories */
    655  1.1  christos static int
    656  1.1  christos do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
    657  1.1  christos {
    658  1.1  christos 	int n;
    659  1.1  christos 	u_int c = 1, colspace = 0, columns = 1;
    660  1.1  christos 	SFTP_DIRENT **d;
    661  1.1  christos 
    662  1.1  christos 	if ((n = do_readdir(conn, path, &d)) != 0)
    663  1.1  christos 		return (n);
    664  1.1  christos 
    665  1.1  christos 	if (!(lflag & LS_SHORT_VIEW)) {
    666  1.1  christos 		u_int m = 0, width = 80;
    667  1.1  christos 		struct winsize ws;
    668  1.1  christos 		char *tmp;
    669  1.1  christos 
    670  1.1  christos 		/* Count entries for sort and find longest filename */
    671  1.1  christos 		for (n = 0; d[n] != NULL; n++) {
    672  1.1  christos 			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
    673  1.1  christos 				m = MAX(m, strlen(d[n]->filename));
    674  1.1  christos 		}
    675  1.1  christos 
    676  1.1  christos 		/* Add any subpath that also needs to be counted */
    677  1.1  christos 		tmp = path_strip(path, strip_path);
    678  1.1  christos 		m += strlen(tmp);
    679  1.1  christos 		xfree(tmp);
    680  1.1  christos 
    681  1.1  christos 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
    682  1.1  christos 			width = ws.ws_col;
    683  1.1  christos 
    684  1.1  christos 		columns = width / (m + 2);
    685  1.1  christos 		columns = MAX(columns, 1);
    686  1.1  christos 		colspace = width / columns;
    687  1.1  christos 		colspace = MIN(colspace, width);
    688  1.1  christos 	}
    689  1.1  christos 
    690  1.1  christos 	if (lflag & SORT_FLAGS) {
    691  1.1  christos 		for (n = 0; d[n] != NULL; n++)
    692  1.1  christos 			;	/* count entries */
    693  1.1  christos 		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
    694  1.1  christos 		qsort(d, n, sizeof(*d), sdirent_comp);
    695  1.1  christos 	}
    696  1.1  christos 
    697  1.1  christos 	for (n = 0; d[n] != NULL && !interrupted; n++) {
    698  1.1  christos 		char *tmp, *fname;
    699  1.1  christos 
    700  1.1  christos 		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
    701  1.1  christos 			continue;
    702  1.1  christos 
    703  1.1  christos 		tmp = path_append(path, d[n]->filename);
    704  1.1  christos 		fname = path_strip(tmp, strip_path);
    705  1.1  christos 		xfree(tmp);
    706  1.1  christos 
    707  1.1  christos 		if (lflag & LS_LONG_VIEW) {
    708  1.4      adam 			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
    709  1.1  christos 				char *lname;
    710  1.1  christos 				struct stat sb;
    711  1.1  christos 
    712  1.1  christos 				memset(&sb, 0, sizeof(sb));
    713  1.1  christos 				attrib_to_stat(&d[n]->a, &sb);
    714  1.4      adam 				lname = ls_file(fname, &sb, 1,
    715  1.4      adam 				    (lflag & LS_SI_UNITS));
    716  1.1  christos 				printf("%s\n", lname);
    717  1.1  christos 				xfree(lname);
    718  1.1  christos 			} else
    719  1.1  christos 				printf("%s\n", d[n]->longname);
    720  1.1  christos 		} else {
    721  1.1  christos 			printf("%-*s", colspace, fname);
    722  1.1  christos 			if (c >= columns) {
    723  1.1  christos 				printf("\n");
    724  1.1  christos 				c = 1;
    725  1.1  christos 			} else
    726  1.1  christos 				c++;
    727  1.1  christos 		}
    728  1.1  christos 
    729  1.1  christos 		xfree(fname);
    730  1.1  christos 	}
    731  1.1  christos 
    732  1.1  christos 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
    733  1.1  christos 		printf("\n");
    734  1.1  christos 
    735  1.1  christos 	free_sftp_dirents(d);
    736  1.1  christos 	return (0);
    737  1.1  christos }
    738  1.1  christos 
    739  1.1  christos /* sftp ls.1 replacement which handles path globs */
    740  1.1  christos static int
    741  1.1  christos do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
    742  1.1  christos     int lflag)
    743  1.1  christos {
    744  1.1  christos 	glob_t g;
    745  1.1  christos 	u_int i, c = 1, colspace = 0, columns = 1;
    746  1.1  christos 	Attrib *a = NULL;
    747  1.1  christos 
    748  1.1  christos 	memset(&g, 0, sizeof(g));
    749  1.1  christos 
    750  1.1  christos 	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
    751  1.1  christos 	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
    752  1.1  christos 		if (g.gl_pathc)
    753  1.1  christos 			globfree(&g);
    754  1.1  christos 		error("Can't ls: \"%s\" not found", path);
    755  1.1  christos 		return (-1);
    756  1.1  christos 	}
    757  1.1  christos 
    758  1.1  christos 	if (interrupted)
    759  1.1  christos 		goto out;
    760  1.1  christos 
    761  1.1  christos 	/*
    762  1.1  christos 	 * If the glob returns a single match and it is a directory,
    763  1.1  christos 	 * then just list its contents.
    764  1.1  christos 	 */
    765  1.1  christos 	if (g.gl_matchc == 1) {
    766  1.1  christos 		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
    767  1.1  christos 			globfree(&g);
    768  1.1  christos 			return (-1);
    769  1.1  christos 		}
    770  1.1  christos 		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
    771  1.1  christos 		    S_ISDIR(a->perm)) {
    772  1.1  christos 			int err;
    773  1.1  christos 
    774  1.1  christos 			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
    775  1.1  christos 			globfree(&g);
    776  1.1  christos 			return (err);
    777  1.1  christos 		}
    778  1.1  christos 	}
    779  1.1  christos 
    780  1.1  christos 	if (!(lflag & LS_SHORT_VIEW)) {
    781  1.1  christos 		u_int m = 0, width = 80;
    782  1.1  christos 		struct winsize ws;
    783  1.1  christos 
    784  1.1  christos 		/* Count entries for sort and find longest filename */
    785  1.1  christos 		for (i = 0; g.gl_pathv[i]; i++)
    786  1.1  christos 			m = MAX(m, strlen(g.gl_pathv[i]));
    787  1.1  christos 
    788  1.1  christos 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
    789  1.1  christos 			width = ws.ws_col;
    790  1.1  christos 
    791  1.1  christos 		columns = width / (m + 2);
    792  1.1  christos 		columns = MAX(columns, 1);
    793  1.1  christos 		colspace = width / columns;
    794  1.1  christos 	}
    795  1.1  christos 
    796  1.1  christos 	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
    797  1.1  christos 		char *fname;
    798  1.1  christos 
    799  1.1  christos 		fname = path_strip(g.gl_pathv[i], strip_path);
    800  1.1  christos 
    801  1.1  christos 		if (lflag & LS_LONG_VIEW) {
    802  1.1  christos 			char *lname;
    803  1.1  christos 			struct stat sb;
    804  1.1  christos 
    805  1.1  christos 			/*
    806  1.1  christos 			 * XXX: this is slow - 1 roundtrip per path
    807  1.1  christos 			 * A solution to this is to fork glob() and
    808  1.1  christos 			 * build a sftp specific version which keeps the
    809  1.1  christos 			 * attribs (which currently get thrown away)
    810  1.1  christos 			 * that the server returns as well as the filenames.
    811  1.1  christos 			 */
    812  1.1  christos 			memset(&sb, 0, sizeof(sb));
    813  1.1  christos 			if (a == NULL)
    814  1.1  christos 				a = do_lstat(conn, g.gl_pathv[i], 1);
    815  1.1  christos 			if (a != NULL)
    816  1.1  christos 				attrib_to_stat(a, &sb);
    817  1.4      adam 			lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS));
    818  1.1  christos 			printf("%s\n", lname);
    819  1.1  christos 			xfree(lname);
    820  1.1  christos 		} else {
    821  1.1  christos 			printf("%-*s", colspace, fname);
    822  1.1  christos 			if (c >= columns) {
    823  1.1  christos 				printf("\n");
    824  1.1  christos 				c = 1;
    825  1.1  christos 			} else
    826  1.1  christos 				c++;
    827  1.1  christos 		}
    828  1.1  christos 		xfree(fname);
    829  1.1  christos 	}
    830  1.1  christos 
    831  1.1  christos 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
    832  1.1  christos 		printf("\n");
    833  1.1  christos 
    834  1.1  christos  out:
    835  1.1  christos 	if (g.gl_pathc)
    836  1.1  christos 		globfree(&g);
    837  1.1  christos 
    838  1.1  christos 	return (0);
    839  1.1  christos }
    840  1.1  christos 
    841  1.1  christos static int
    842  1.1  christos do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
    843  1.1  christos {
    844  1.1  christos 	struct sftp_statvfs st;
    845  1.1  christos 	char s_used[FMT_SCALED_STRSIZE];
    846  1.1  christos 	char s_avail[FMT_SCALED_STRSIZE];
    847  1.1  christos 	char s_root[FMT_SCALED_STRSIZE];
    848  1.1  christos 	char s_total[FMT_SCALED_STRSIZE];
    849  1.4      adam 	unsigned long long ffree;
    850  1.1  christos 
    851  1.1  christos 	if (do_statvfs(conn, path, &st, 1) == -1)
    852  1.1  christos 		return -1;
    853  1.1  christos 	if (iflag) {
    854  1.4      adam 		ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
    855  1.1  christos 		printf("     Inodes        Used       Avail      "
    856  1.1  christos 		    "(root)    %%Capacity\n");
    857  1.1  christos 		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
    858  1.1  christos 		    (unsigned long long)st.f_files,
    859  1.1  christos 		    (unsigned long long)(st.f_files - st.f_ffree),
    860  1.1  christos 		    (unsigned long long)st.f_favail,
    861  1.4      adam 		    (unsigned long long)st.f_ffree, ffree);
    862  1.1  christos 	} else if (hflag) {
    863  1.1  christos 		strlcpy(s_used, "error", sizeof(s_used));
    864  1.1  christos 		strlcpy(s_avail, "error", sizeof(s_avail));
    865  1.1  christos 		strlcpy(s_root, "error", sizeof(s_root));
    866  1.1  christos 		strlcpy(s_total, "error", sizeof(s_total));
    867  1.1  christos 		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
    868  1.1  christos 		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
    869  1.1  christos 		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
    870  1.1  christos 		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
    871  1.1  christos 		printf("    Size     Used    Avail   (root)    %%Capacity\n");
    872  1.1  christos 		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
    873  1.1  christos 		    s_total, s_used, s_avail, s_root,
    874  1.1  christos 		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
    875  1.1  christos 		    st.f_blocks));
    876  1.1  christos 	} else {
    877  1.1  christos 		printf("        Size         Used        Avail       "
    878  1.1  christos 		    "(root)    %%Capacity\n");
    879  1.1  christos 		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
    880  1.1  christos 		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
    881  1.1  christos 		    (unsigned long long)(st.f_frsize *
    882  1.1  christos 		    (st.f_blocks - st.f_bfree) / 1024),
    883  1.1  christos 		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
    884  1.1  christos 		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
    885  1.1  christos 		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
    886  1.1  christos 		    st.f_blocks));
    887  1.1  christos 	}
    888  1.1  christos 	return 0;
    889  1.1  christos }
    890  1.1  christos 
    891  1.1  christos /*
    892  1.1  christos  * Undo escaping of glob sequences in place. Used to undo extra escaping
    893  1.1  christos  * applied in makeargv() when the string is destined for a function that
    894  1.1  christos  * does not glob it.
    895  1.1  christos  */
    896  1.1  christos static void
    897  1.1  christos undo_glob_escape(char *s)
    898  1.1  christos {
    899  1.1  christos 	size_t i, j;
    900  1.1  christos 
    901  1.1  christos 	for (i = j = 0;;) {
    902  1.1  christos 		if (s[i] == '\0') {
    903  1.1  christos 			s[j] = '\0';
    904  1.1  christos 			return;
    905  1.1  christos 		}
    906  1.1  christos 		if (s[i] != '\\') {
    907  1.1  christos 			s[j++] = s[i++];
    908  1.1  christos 			continue;
    909  1.1  christos 		}
    910  1.1  christos 		/* s[i] == '\\' */
    911  1.1  christos 		++i;
    912  1.1  christos 		switch (s[i]) {
    913  1.1  christos 		case '?':
    914  1.1  christos 		case '[':
    915  1.1  christos 		case '*':
    916  1.1  christos 		case '\\':
    917  1.1  christos 			s[j++] = s[i++];
    918  1.1  christos 			break;
    919  1.1  christos 		case '\0':
    920  1.1  christos 			s[j++] = '\\';
    921  1.1  christos 			s[j] = '\0';
    922  1.1  christos 			return;
    923  1.1  christos 		default:
    924  1.1  christos 			s[j++] = '\\';
    925  1.1  christos 			s[j++] = s[i++];
    926  1.1  christos 			break;
    927  1.1  christos 		}
    928  1.1  christos 	}
    929  1.1  christos }
    930  1.1  christos 
    931  1.1  christos /*
    932  1.1  christos  * Split a string into an argument vector using sh(1)-style quoting,
    933  1.1  christos  * comment and escaping rules, but with some tweaks to handle glob(3)
    934  1.1  christos  * wildcards.
    935  1.4      adam  * The "sloppy" flag allows for recovery from missing terminating quote, for
    936  1.4      adam  * use in parsing incomplete commandlines during tab autocompletion.
    937  1.4      adam  *
    938  1.1  christos  * Returns NULL on error or a NULL-terminated array of arguments.
    939  1.4      adam  *
    940  1.4      adam  * If "lastquote" is not NULL, the quoting character used for the last
    941  1.4      adam  * argument is placed in *lastquote ("\0", "'" or "\"").
    942  1.4      adam  *
    943  1.4      adam  * If "terminated" is not NULL, *terminated will be set to 1 when the
    944  1.4      adam  * last argument's quote has been properly terminated or 0 otherwise.
    945  1.4      adam  * This parameter is only of use if "sloppy" is set.
    946  1.1  christos  */
    947  1.1  christos #define MAXARGS 	128
    948  1.1  christos #define MAXARGLEN	8192
    949  1.1  christos static char **
    950  1.4      adam makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
    951  1.4      adam     u_int *terminated)
    952  1.1  christos {
    953  1.1  christos 	int argc, quot;
    954  1.1  christos 	size_t i, j;
    955  1.1  christos 	static char argvs[MAXARGLEN];
    956  1.1  christos 	static char *argv[MAXARGS + 1];
    957  1.1  christos 	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
    958  1.1  christos 
    959  1.1  christos 	*argcp = argc = 0;
    960  1.1  christos 	if (strlen(arg) > sizeof(argvs) - 1) {
    961  1.1  christos  args_too_longs:
    962  1.1  christos 		error("string too long");
    963  1.1  christos 		return NULL;
    964  1.1  christos 	}
    965  1.4      adam 	if (terminated != NULL)
    966  1.4      adam 		*terminated = 1;
    967  1.4      adam 	if (lastquote != NULL)
    968  1.4      adam 		*lastquote = '\0';
    969  1.1  christos 	state = MA_START;
    970  1.1  christos 	i = j = 0;
    971  1.1  christos 	for (;;) {
    972  1.2  christos 		if (isspace((unsigned char)arg[i])) {
    973  1.1  christos 			if (state == MA_UNQUOTED) {
    974  1.1  christos 				/* Terminate current argument */
    975  1.1  christos 				argvs[j++] = '\0';
    976  1.1  christos 				argc++;
    977  1.1  christos 				state = MA_START;
    978  1.1  christos 			} else if (state != MA_START)
    979  1.1  christos 				argvs[j++] = arg[i];
    980  1.1  christos 		} else if (arg[i] == '"' || arg[i] == '\'') {
    981  1.1  christos 			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
    982  1.1  christos 			if (state == MA_START) {
    983  1.1  christos 				argv[argc] = argvs + j;
    984  1.1  christos 				state = q;
    985  1.4      adam 				if (lastquote != NULL)
    986  1.4      adam 					*lastquote = arg[i];
    987  1.1  christos 			} else if (state == MA_UNQUOTED)
    988  1.1  christos 				state = q;
    989  1.1  christos 			else if (state == q)
    990  1.1  christos 				state = MA_UNQUOTED;
    991  1.1  christos 			else
    992  1.1  christos 				argvs[j++] = arg[i];
    993  1.1  christos 		} else if (arg[i] == '\\') {
    994  1.1  christos 			if (state == MA_SQUOTE || state == MA_DQUOTE) {
    995  1.1  christos 				quot = state == MA_SQUOTE ? '\'' : '"';
    996  1.1  christos 				/* Unescape quote we are in */
    997  1.1  christos 				/* XXX support \n and friends? */
    998  1.1  christos 				if (arg[i + 1] == quot) {
    999  1.1  christos 					i++;
   1000  1.1  christos 					argvs[j++] = arg[i];
   1001  1.1  christos 				} else if (arg[i + 1] == '?' ||
   1002  1.1  christos 				    arg[i + 1] == '[' || arg[i + 1] == '*') {
   1003  1.1  christos 					/*
   1004  1.1  christos 					 * Special case for sftp: append
   1005  1.1  christos 					 * double-escaped glob sequence -
   1006  1.1  christos 					 * glob will undo one level of
   1007  1.1  christos 					 * escaping. NB. string can grow here.
   1008  1.1  christos 					 */
   1009  1.1  christos 					if (j >= sizeof(argvs) - 5)
   1010  1.1  christos 						goto args_too_longs;
   1011  1.1  christos 					argvs[j++] = '\\';
   1012  1.1  christos 					argvs[j++] = arg[i++];
   1013  1.1  christos 					argvs[j++] = '\\';
   1014  1.1  christos 					argvs[j++] = arg[i];
   1015  1.1  christos 				} else {
   1016  1.1  christos 					argvs[j++] = arg[i++];
   1017  1.1  christos 					argvs[j++] = arg[i];
   1018  1.1  christos 				}
   1019  1.1  christos 			} else {
   1020  1.1  christos 				if (state == MA_START) {
   1021  1.1  christos 					argv[argc] = argvs + j;
   1022  1.1  christos 					state = MA_UNQUOTED;
   1023  1.4      adam 					if (lastquote != NULL)
   1024  1.4      adam 						*lastquote = '\0';
   1025  1.1  christos 				}
   1026  1.1  christos 				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
   1027  1.1  christos 				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
   1028  1.1  christos 					/*
   1029  1.1  christos 					 * Special case for sftp: append
   1030  1.1  christos 					 * escaped glob sequence -
   1031  1.1  christos 					 * glob will undo one level of
   1032  1.1  christos 					 * escaping.
   1033  1.1  christos 					 */
   1034  1.1  christos 					argvs[j++] = arg[i++];
   1035  1.1  christos 					argvs[j++] = arg[i];
   1036  1.1  christos 				} else {
   1037  1.1  christos 					/* Unescape everything */
   1038  1.1  christos 					/* XXX support \n and friends? */
   1039  1.1  christos 					i++;
   1040  1.1  christos 					argvs[j++] = arg[i];
   1041  1.1  christos 				}
   1042  1.1  christos 			}
   1043  1.1  christos 		} else if (arg[i] == '#') {
   1044  1.1  christos 			if (state == MA_SQUOTE || state == MA_DQUOTE)
   1045  1.1  christos 				argvs[j++] = arg[i];
   1046  1.1  christos 			else
   1047  1.1  christos 				goto string_done;
   1048  1.1  christos 		} else if (arg[i] == '\0') {
   1049  1.1  christos 			if (state == MA_SQUOTE || state == MA_DQUOTE) {
   1050  1.4      adam 				if (sloppy) {
   1051  1.4      adam 					state = MA_UNQUOTED;
   1052  1.4      adam 					if (terminated != NULL)
   1053  1.4      adam 						*terminated = 0;
   1054  1.4      adam 					goto string_done;
   1055  1.4      adam 				}
   1056  1.1  christos 				error("Unterminated quoted argument");
   1057  1.1  christos 				return NULL;
   1058  1.1  christos 			}
   1059  1.1  christos  string_done:
   1060  1.1  christos 			if (state == MA_UNQUOTED) {
   1061  1.1  christos 				argvs[j++] = '\0';
   1062  1.1  christos 				argc++;
   1063  1.1  christos 			}
   1064  1.1  christos 			break;
   1065  1.1  christos 		} else {
   1066  1.1  christos 			if (state == MA_START) {
   1067  1.1  christos 				argv[argc] = argvs + j;
   1068  1.1  christos 				state = MA_UNQUOTED;
   1069  1.4      adam 				if (lastquote != NULL)
   1070  1.4      adam 					*lastquote = '\0';
   1071  1.1  christos 			}
   1072  1.1  christos 			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
   1073  1.1  christos 			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
   1074  1.1  christos 				/*
   1075  1.1  christos 				 * Special case for sftp: escape quoted
   1076  1.1  christos 				 * glob(3) wildcards. NB. string can grow
   1077  1.1  christos 				 * here.
   1078  1.1  christos 				 */
   1079  1.1  christos 				if (j >= sizeof(argvs) - 3)
   1080  1.1  christos 					goto args_too_longs;
   1081  1.1  christos 				argvs[j++] = '\\';
   1082  1.1  christos 				argvs[j++] = arg[i];
   1083  1.1  christos 			} else
   1084  1.1  christos 				argvs[j++] = arg[i];
   1085  1.1  christos 		}
   1086  1.1  christos 		i++;
   1087  1.1  christos 	}
   1088  1.1  christos 	*argcp = argc;
   1089  1.1  christos 	return argv;
   1090  1.1  christos }
   1091  1.1  christos 
   1092  1.1  christos static int
   1093  1.4      adam parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
   1094  1.4      adam     int *hflag, unsigned long *n_arg, char **path1, char **path2)
   1095  1.1  christos {
   1096  1.1  christos 	const char *cmd, *cp = *cpp;
   1097  1.1  christos 	char *cp2, **argv;
   1098  1.1  christos 	int base = 0;
   1099  1.1  christos 	long l;
   1100  1.1  christos 	int i, cmdnum, optidx, argc;
   1101  1.1  christos 
   1102  1.1  christos 	/* Skip leading whitespace */
   1103  1.1  christos 	cp = cp + strspn(cp, WHITESPACE);
   1104  1.1  christos 
   1105  1.1  christos 	/* Check for leading '-' (disable error processing) */
   1106  1.1  christos 	*iflag = 0;
   1107  1.1  christos 	if (*cp == '-') {
   1108  1.1  christos 		*iflag = 1;
   1109  1.1  christos 		cp++;
   1110  1.4      adam 		cp = cp + strspn(cp, WHITESPACE);
   1111  1.1  christos 	}
   1112  1.1  christos 
   1113  1.4      adam 	/* Ignore blank lines and lines which begin with comment '#' char */
   1114  1.4      adam 	if (*cp == '\0' || *cp == '#')
   1115  1.4      adam 		return (0);
   1116  1.4      adam 
   1117  1.4      adam 	if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
   1118  1.1  christos 		return -1;
   1119  1.1  christos 
   1120  1.1  christos 	/* Figure out which command we have */
   1121  1.1  christos 	for (i = 0; cmds[i].c != NULL; i++) {
   1122  1.1  christos 		if (strcasecmp(cmds[i].c, argv[0]) == 0)
   1123  1.1  christos 			break;
   1124  1.1  christos 	}
   1125  1.1  christos 	cmdnum = cmds[i].n;
   1126  1.1  christos 	cmd = cmds[i].c;
   1127  1.1  christos 
   1128  1.1  christos 	/* Special case */
   1129  1.1  christos 	if (*cp == '!') {
   1130  1.1  christos 		cp++;
   1131  1.1  christos 		cmdnum = I_SHELL;
   1132  1.1  christos 	} else if (cmdnum == -1) {
   1133  1.1  christos 		error("Invalid command.");
   1134  1.1  christos 		return -1;
   1135  1.1  christos 	}
   1136  1.1  christos 
   1137  1.1  christos 	/* Get arguments and parse flags */
   1138  1.4      adam 	*lflag = *pflag = *rflag = *hflag = *n_arg = 0;
   1139  1.1  christos 	*path1 = *path2 = NULL;
   1140  1.1  christos 	optidx = 1;
   1141  1.1  christos 	switch (cmdnum) {
   1142  1.1  christos 	case I_GET:
   1143  1.1  christos 	case I_PUT:
   1144  1.4      adam 		if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
   1145  1.1  christos 			return -1;
   1146  1.1  christos 		/* Get first pathname (mandatory) */
   1147  1.1  christos 		if (argc - optidx < 1) {
   1148  1.1  christos 			error("You must specify at least one path after a "
   1149  1.1  christos 			    "%s command.", cmd);
   1150  1.1  christos 			return -1;
   1151  1.1  christos 		}
   1152  1.1  christos 		*path1 = xstrdup(argv[optidx]);
   1153  1.1  christos 		/* Get second pathname (optional) */
   1154  1.1  christos 		if (argc - optidx > 1) {
   1155  1.1  christos 			*path2 = xstrdup(argv[optidx + 1]);
   1156  1.1  christos 			/* Destination is not globbed */
   1157  1.1  christos 			undo_glob_escape(*path2);
   1158  1.1  christos 		}
   1159  1.1  christos 		break;
   1160  1.1  christos 	case I_RENAME:
   1161  1.1  christos 	case I_SYMLINK:
   1162  1.1  christos 		if (argc - optidx < 2) {
   1163  1.1  christos 			error("You must specify two paths after a %s "
   1164  1.1  christos 			    "command.", cmd);
   1165  1.1  christos 			return -1;
   1166  1.1  christos 		}
   1167  1.1  christos 		*path1 = xstrdup(argv[optidx]);
   1168  1.1  christos 		*path2 = xstrdup(argv[optidx + 1]);
   1169  1.1  christos 		/* Paths are not globbed */
   1170  1.1  christos 		undo_glob_escape(*path1);
   1171  1.1  christos 		undo_glob_escape(*path2);
   1172  1.1  christos 		break;
   1173  1.1  christos 	case I_RM:
   1174  1.1  christos 	case I_MKDIR:
   1175  1.1  christos 	case I_RMDIR:
   1176  1.1  christos 	case I_CHDIR:
   1177  1.1  christos 	case I_LCHDIR:
   1178  1.1  christos 	case I_LMKDIR:
   1179  1.1  christos 		/* Get pathname (mandatory) */
   1180  1.1  christos 		if (argc - optidx < 1) {
   1181  1.1  christos 			error("You must specify a path after a %s command.",
   1182  1.1  christos 			    cmd);
   1183  1.1  christos 			return -1;
   1184  1.1  christos 		}
   1185  1.1  christos 		*path1 = xstrdup(argv[optidx]);
   1186  1.1  christos 		/* Only "rm" globs */
   1187  1.1  christos 		if (cmdnum != I_RM)
   1188  1.1  christos 			undo_glob_escape(*path1);
   1189  1.1  christos 		break;
   1190  1.1  christos 	case I_DF:
   1191  1.1  christos 		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
   1192  1.1  christos 		    iflag)) == -1)
   1193  1.1  christos 			return -1;
   1194  1.1  christos 		/* Default to current directory if no path specified */
   1195  1.1  christos 		if (argc - optidx < 1)
   1196  1.1  christos 			*path1 = NULL;
   1197  1.1  christos 		else {
   1198  1.1  christos 			*path1 = xstrdup(argv[optidx]);
   1199  1.1  christos 			undo_glob_escape(*path1);
   1200  1.1  christos 		}
   1201  1.1  christos 		break;
   1202  1.1  christos 	case I_LS:
   1203  1.1  christos 		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
   1204  1.1  christos 			return(-1);
   1205  1.1  christos 		/* Path is optional */
   1206  1.1  christos 		if (argc - optidx > 0)
   1207  1.1  christos 			*path1 = xstrdup(argv[optidx]);
   1208  1.1  christos 		break;
   1209  1.1  christos 	case I_LLS:
   1210  1.1  christos 		/* Skip ls command and following whitespace */
   1211  1.1  christos 		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
   1212  1.1  christos 	case I_SHELL:
   1213  1.1  christos 		/* Uses the rest of the line */
   1214  1.1  christos 		break;
   1215  1.1  christos 	case I_LUMASK:
   1216  1.1  christos 	case I_CHMOD:
   1217  1.1  christos 		base = 8;
   1218  1.1  christos 	case I_CHOWN:
   1219  1.1  christos 	case I_CHGRP:
   1220  1.1  christos 		/* Get numeric arg (mandatory) */
   1221  1.1  christos 		if (argc - optidx < 1)
   1222  1.1  christos 			goto need_num_arg;
   1223  1.1  christos 		errno = 0;
   1224  1.1  christos 		l = strtol(argv[optidx], &cp2, base);
   1225  1.1  christos 		if (cp2 == argv[optidx] || *cp2 != '\0' ||
   1226  1.1  christos 		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
   1227  1.1  christos 		    l < 0) {
   1228  1.1  christos  need_num_arg:
   1229  1.1  christos 			error("You must supply a numeric argument "
   1230  1.1  christos 			    "to the %s command.", cmd);
   1231  1.1  christos 			return -1;
   1232  1.1  christos 		}
   1233  1.1  christos 		*n_arg = l;
   1234  1.1  christos 		if (cmdnum == I_LUMASK)
   1235  1.1  christos 			break;
   1236  1.1  christos 		/* Get pathname (mandatory) */
   1237  1.1  christos 		if (argc - optidx < 2) {
   1238  1.1  christos 			error("You must specify a path after a %s command.",
   1239  1.1  christos 			    cmd);
   1240  1.1  christos 			return -1;
   1241  1.1  christos 		}
   1242  1.1  christos 		*path1 = xstrdup(argv[optidx + 1]);
   1243  1.1  christos 		break;
   1244  1.1  christos 	case I_QUIT:
   1245  1.1  christos 	case I_PWD:
   1246  1.1  christos 	case I_LPWD:
   1247  1.1  christos 	case I_HELP:
   1248  1.1  christos 	case I_VERSION:
   1249  1.1  christos 	case I_PROGRESS:
   1250  1.1  christos 		break;
   1251  1.1  christos 	default:
   1252  1.1  christos 		fatal("Command not implemented");
   1253  1.1  christos 	}
   1254  1.1  christos 
   1255  1.1  christos 	*cpp = cp;
   1256  1.1  christos 	return(cmdnum);
   1257  1.1  christos }
   1258  1.1  christos 
   1259  1.1  christos static int
   1260  1.1  christos parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
   1261  1.1  christos     int err_abort)
   1262  1.1  christos {
   1263  1.1  christos 	char *path1, *path2, *tmp;
   1264  1.4      adam 	int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
   1265  1.1  christos 	unsigned long n_arg = 0;
   1266  1.1  christos 	Attrib a, *aa;
   1267  1.1  christos 	char path_buf[MAXPATHLEN];
   1268  1.1  christos 	int err = 0;
   1269  1.1  christos 	glob_t g;
   1270  1.1  christos 
   1271  1.2  christos 	pflag = 0;	/* XXX gcc */
   1272  1.2  christos 	lflag = 0;	/* XXX gcc */
   1273  1.2  christos 	iflag = 0;	/* XXX gcc */
   1274  1.2  christos 	hflag = 0;	/* XXX gcc */
   1275  1.2  christos 	n_arg = 0;	/* XXX gcc */
   1276  1.2  christos 
   1277  1.1  christos 	path1 = path2 = NULL;
   1278  1.4      adam 	cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
   1279  1.1  christos 	    &path1, &path2);
   1280  1.1  christos 
   1281  1.1  christos 	if (iflag != 0)
   1282  1.1  christos 		err_abort = 0;
   1283  1.1  christos 
   1284  1.1  christos 	memset(&g, 0, sizeof(g));
   1285  1.1  christos 
   1286  1.1  christos 	/* Perform command */
   1287  1.1  christos 	switch (cmdnum) {
   1288  1.1  christos 	case 0:
   1289  1.1  christos 		/* Blank line */
   1290  1.1  christos 		break;
   1291  1.1  christos 	case -1:
   1292  1.1  christos 		/* Unrecognized command */
   1293  1.1  christos 		err = -1;
   1294  1.1  christos 		break;
   1295  1.1  christos 	case I_GET:
   1296  1.4      adam 		err = process_get(conn, path1, path2, *pwd, pflag, rflag);
   1297  1.1  christos 		break;
   1298  1.1  christos 	case I_PUT:
   1299  1.4      adam 		err = process_put(conn, path1, path2, *pwd, pflag, rflag);
   1300  1.1  christos 		break;
   1301  1.1  christos 	case I_RENAME:
   1302  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1303  1.1  christos 		path2 = make_absolute(path2, *pwd);
   1304  1.1  christos 		err = do_rename(conn, path1, path2);
   1305  1.1  christos 		break;
   1306  1.1  christos 	case I_SYMLINK:
   1307  1.1  christos 		path2 = make_absolute(path2, *pwd);
   1308  1.1  christos 		err = do_symlink(conn, path1, path2);
   1309  1.1  christos 		break;
   1310  1.1  christos 	case I_RM:
   1311  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1312  1.1  christos 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
   1313  1.1  christos 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
   1314  1.1  christos 			printf("Removing %s\n", g.gl_pathv[i]);
   1315  1.1  christos 			err = do_rm(conn, g.gl_pathv[i]);
   1316  1.1  christos 			if (err != 0 && err_abort)
   1317  1.1  christos 				break;
   1318  1.1  christos 		}
   1319  1.1  christos 		break;
   1320  1.1  christos 	case I_MKDIR:
   1321  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1322  1.1  christos 		attrib_clear(&a);
   1323  1.1  christos 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
   1324  1.1  christos 		a.perm = 0777;
   1325  1.4      adam 		err = do_mkdir(conn, path1, &a, 1);
   1326  1.1  christos 		break;
   1327  1.1  christos 	case I_RMDIR:
   1328  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1329  1.1  christos 		err = do_rmdir(conn, path1);
   1330  1.1  christos 		break;
   1331  1.1  christos 	case I_CHDIR:
   1332  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1333  1.1  christos 		if ((tmp = do_realpath(conn, path1)) == NULL) {
   1334  1.1  christos 			err = 1;
   1335  1.1  christos 			break;
   1336  1.1  christos 		}
   1337  1.1  christos 		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
   1338  1.1  christos 			xfree(tmp);
   1339  1.1  christos 			err = 1;
   1340  1.1  christos 			break;
   1341  1.1  christos 		}
   1342  1.1  christos 		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
   1343  1.1  christos 			error("Can't change directory: Can't check target");
   1344  1.1  christos 			xfree(tmp);
   1345  1.1  christos 			err = 1;
   1346  1.1  christos 			break;
   1347  1.1  christos 		}
   1348  1.1  christos 		if (!S_ISDIR(aa->perm)) {
   1349  1.1  christos 			error("Can't change directory: \"%s\" is not "
   1350  1.1  christos 			    "a directory", tmp);
   1351  1.1  christos 			xfree(tmp);
   1352  1.1  christos 			err = 1;
   1353  1.1  christos 			break;
   1354  1.1  christos 		}
   1355  1.1  christos 		xfree(*pwd);
   1356  1.1  christos 		*pwd = tmp;
   1357  1.1  christos 		break;
   1358  1.1  christos 	case I_LS:
   1359  1.1  christos 		if (!path1) {
   1360  1.4      adam 			do_ls_dir(conn, *pwd, *pwd, lflag);
   1361  1.1  christos 			break;
   1362  1.1  christos 		}
   1363  1.1  christos 
   1364  1.1  christos 		/* Strip pwd off beginning of non-absolute paths */
   1365  1.1  christos 		tmp = NULL;
   1366  1.1  christos 		if (*path1 != '/')
   1367  1.1  christos 			tmp = *pwd;
   1368  1.1  christos 
   1369  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1370  1.1  christos 		err = do_globbed_ls(conn, path1, tmp, lflag);
   1371  1.1  christos 		break;
   1372  1.1  christos 	case I_DF:
   1373  1.1  christos 		/* Default to current directory if no path specified */
   1374  1.1  christos 		if (path1 == NULL)
   1375  1.1  christos 			path1 = xstrdup(*pwd);
   1376  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1377  1.1  christos 		err = do_df(conn, path1, hflag, iflag);
   1378  1.1  christos 		break;
   1379  1.1  christos 	case I_LCHDIR:
   1380  1.1  christos 		if (chdir(path1) == -1) {
   1381  1.1  christos 			error("Couldn't change local directory to "
   1382  1.1  christos 			    "\"%s\": %s", path1, strerror(errno));
   1383  1.1  christos 			err = 1;
   1384  1.1  christos 		}
   1385  1.1  christos 		break;
   1386  1.1  christos 	case I_LMKDIR:
   1387  1.1  christos 		if (mkdir(path1, 0777) == -1) {
   1388  1.1  christos 			error("Couldn't create local directory "
   1389  1.1  christos 			    "\"%s\": %s", path1, strerror(errno));
   1390  1.1  christos 			err = 1;
   1391  1.1  christos 		}
   1392  1.1  christos 		break;
   1393  1.1  christos 	case I_LLS:
   1394  1.1  christos 		local_do_ls(cmd);
   1395  1.1  christos 		break;
   1396  1.1  christos 	case I_SHELL:
   1397  1.1  christos 		local_do_shell(cmd);
   1398  1.1  christos 		break;
   1399  1.1  christos 	case I_LUMASK:
   1400  1.1  christos 		umask(n_arg);
   1401  1.1  christos 		printf("Local umask: %03lo\n", n_arg);
   1402  1.1  christos 		break;
   1403  1.1  christos 	case I_CHMOD:
   1404  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1405  1.1  christos 		attrib_clear(&a);
   1406  1.1  christos 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
   1407  1.1  christos 		a.perm = n_arg;
   1408  1.1  christos 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
   1409  1.1  christos 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
   1410  1.1  christos 			printf("Changing mode on %s\n", g.gl_pathv[i]);
   1411  1.1  christos 			err = do_setstat(conn, g.gl_pathv[i], &a);
   1412  1.1  christos 			if (err != 0 && err_abort)
   1413  1.1  christos 				break;
   1414  1.1  christos 		}
   1415  1.1  christos 		break;
   1416  1.1  christos 	case I_CHOWN:
   1417  1.1  christos 	case I_CHGRP:
   1418  1.1  christos 		path1 = make_absolute(path1, *pwd);
   1419  1.1  christos 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
   1420  1.1  christos 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
   1421  1.1  christos 			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
   1422  1.1  christos 				if (err_abort) {
   1423  1.1  christos 					err = -1;
   1424  1.1  christos 					break;
   1425  1.1  christos 				} else
   1426  1.1  christos 					continue;
   1427  1.1  christos 			}
   1428  1.1  christos 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
   1429  1.1  christos 				error("Can't get current ownership of "
   1430  1.1  christos 				    "remote file \"%s\"", g.gl_pathv[i]);
   1431  1.1  christos 				if (err_abort) {
   1432  1.1  christos 					err = -1;
   1433  1.1  christos 					break;
   1434  1.1  christos 				} else
   1435  1.1  christos 					continue;
   1436  1.1  christos 			}
   1437  1.1  christos 			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
   1438  1.1  christos 			if (cmdnum == I_CHOWN) {
   1439  1.1  christos 				printf("Changing owner on %s\n", g.gl_pathv[i]);
   1440  1.1  christos 				aa->uid = n_arg;
   1441  1.1  christos 			} else {
   1442  1.1  christos 				printf("Changing group on %s\n", g.gl_pathv[i]);
   1443  1.1  christos 				aa->gid = n_arg;
   1444  1.1  christos 			}
   1445  1.1  christos 			err = do_setstat(conn, g.gl_pathv[i], aa);
   1446  1.1  christos 			if (err != 0 && err_abort)
   1447  1.1  christos 				break;
   1448  1.1  christos 		}
   1449  1.1  christos 		break;
   1450  1.1  christos 	case I_PWD:
   1451  1.1  christos 		printf("Remote working directory: %s\n", *pwd);
   1452  1.1  christos 		break;
   1453  1.1  christos 	case I_LPWD:
   1454  1.1  christos 		if (!getcwd(path_buf, sizeof(path_buf))) {
   1455  1.1  christos 			error("Couldn't get local cwd: %s", strerror(errno));
   1456  1.1  christos 			err = -1;
   1457  1.1  christos 			break;
   1458  1.1  christos 		}
   1459  1.1  christos 		printf("Local working directory: %s\n", path_buf);
   1460  1.1  christos 		break;
   1461  1.1  christos 	case I_QUIT:
   1462  1.1  christos 		/* Processed below */
   1463  1.1  christos 		break;
   1464  1.1  christos 	case I_HELP:
   1465  1.1  christos 		help();
   1466  1.1  christos 		break;
   1467  1.1  christos 	case I_VERSION:
   1468  1.1  christos 		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
   1469  1.1  christos 		break;
   1470  1.1  christos 	case I_PROGRESS:
   1471  1.1  christos 		showprogress = !showprogress;
   1472  1.1  christos 		if (showprogress)
   1473  1.1  christos 			printf("Progress meter enabled\n");
   1474  1.1  christos 		else
   1475  1.1  christos 			printf("Progress meter disabled\n");
   1476  1.1  christos 		break;
   1477  1.1  christos 	default:
   1478  1.1  christos 		fatal("%d is not implemented", cmdnum);
   1479  1.1  christos 	}
   1480  1.1  christos 
   1481  1.1  christos 	if (g.gl_pathc)
   1482  1.1  christos 		globfree(&g);
   1483  1.1  christos 	if (path1)
   1484  1.1  christos 		xfree(path1);
   1485  1.1  christos 	if (path2)
   1486  1.1  christos 		xfree(path2);
   1487  1.1  christos 
   1488  1.1  christos 	/* If an unignored error occurs in batch mode we should abort. */
   1489  1.1  christos 	if (err_abort && err != 0)
   1490  1.1  christos 		return (-1);
   1491  1.1  christos 	else if (cmdnum == I_QUIT)
   1492  1.1  christos 		return (1);
   1493  1.1  christos 
   1494  1.1  christos 	return (0);
   1495  1.1  christos }
   1496  1.1  christos 
   1497  1.1  christos static char *
   1498  1.1  christos prompt(EditLine *el)
   1499  1.1  christos {
   1500  1.1  christos 	return ("sftp> ");
   1501  1.1  christos }
   1502  1.1  christos 
   1503  1.4      adam /* Display entries in 'list' after skipping the first 'len' chars */
   1504  1.4      adam static void
   1505  1.4      adam complete_display(char **list, u_int len)
   1506  1.4      adam {
   1507  1.4      adam 	u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
   1508  1.4      adam 	struct winsize ws;
   1509  1.4      adam 	char *tmp;
   1510  1.4      adam 
   1511  1.4      adam 	/* Count entries for sort and find longest */
   1512  1.4      adam 	for (y = 0; list[y]; y++)
   1513  1.4      adam 		m = MAX(m, strlen(list[y]));
   1514  1.4      adam 
   1515  1.4      adam 	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
   1516  1.4      adam 		width = ws.ws_col;
   1517  1.4      adam 
   1518  1.4      adam 	m = m > len ? m - len : 0;
   1519  1.4      adam 	columns = width / (m + 2);
   1520  1.4      adam 	columns = MAX(columns, 1);
   1521  1.4      adam 	colspace = width / columns;
   1522  1.4      adam 	colspace = MIN(colspace, width);
   1523  1.4      adam 
   1524  1.4      adam 	printf("\n");
   1525  1.4      adam 	m = 1;
   1526  1.4      adam 	for (y = 0; list[y]; y++) {
   1527  1.4      adam 		llen = strlen(list[y]);
   1528  1.4      adam 		tmp = llen > len ? list[y] + len : "";
   1529  1.4      adam 		printf("%-*s", colspace, tmp);
   1530  1.4      adam 		if (m >= columns) {
   1531  1.4      adam 			printf("\n");
   1532  1.4      adam 			m = 1;
   1533  1.4      adam 		} else
   1534  1.4      adam 			m++;
   1535  1.4      adam 	}
   1536  1.4      adam 	printf("\n");
   1537  1.4      adam }
   1538  1.4      adam 
   1539  1.4      adam /*
   1540  1.4      adam  * Given a "list" of words that begin with a common prefix of "word",
   1541  1.4      adam  * attempt to find an autocompletion to extends "word" by the next
   1542  1.4      adam  * characters common to all entries in "list".
   1543  1.4      adam  */
   1544  1.4      adam static char *
   1545  1.4      adam complete_ambiguous(const char *word, char **list, size_t count)
   1546  1.4      adam {
   1547  1.4      adam 	if (word == NULL)
   1548  1.4      adam 		return NULL;
   1549  1.4      adam 
   1550  1.4      adam 	if (count > 0) {
   1551  1.4      adam 		u_int y, matchlen = strlen(list[0]);
   1552  1.4      adam 
   1553  1.4      adam 		/* Find length of common stem */
   1554  1.4      adam 		for (y = 1; list[y]; y++) {
   1555  1.4      adam 			u_int x;
   1556  1.4      adam 
   1557  1.4      adam 			for (x = 0; x < matchlen; x++)
   1558  1.4      adam 				if (list[0][x] != list[y][x])
   1559  1.4      adam 					break;
   1560  1.4      adam 
   1561  1.4      adam 			matchlen = x;
   1562  1.4      adam 		}
   1563  1.4      adam 
   1564  1.4      adam 		if (matchlen > strlen(word)) {
   1565  1.4      adam 			char *tmp = xstrdup(list[0]);
   1566  1.4      adam 
   1567  1.4      adam 			tmp[matchlen] = '\0';
   1568  1.4      adam 			return tmp;
   1569  1.4      adam 		}
   1570  1.4      adam 	}
   1571  1.4      adam 
   1572  1.4      adam 	return xstrdup(word);
   1573  1.4      adam }
   1574  1.4      adam 
   1575  1.4      adam /* Autocomplete a sftp command */
   1576  1.4      adam static int
   1577  1.4      adam complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
   1578  1.4      adam     int terminated)
   1579  1.4      adam {
   1580  1.4      adam 	u_int y, count = 0, cmdlen, tmplen;
   1581  1.4      adam 	char *tmp, **list, argterm[3];
   1582  1.4      adam 	const LineInfo *lf;
   1583  1.4      adam 
   1584  1.4      adam 	list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
   1585  1.4      adam 
   1586  1.4      adam 	/* No command specified: display all available commands */
   1587  1.4      adam 	if (cmd == NULL) {
   1588  1.4      adam 		for (y = 0; cmds[y].c; y++)
   1589  1.4      adam 			list[count++] = xstrdup(cmds[y].c);
   1590  1.4      adam 
   1591  1.4      adam 		list[count] = NULL;
   1592  1.4      adam 		complete_display(list, 0);
   1593  1.4      adam 
   1594  1.4      adam 		for (y = 0; list[y] != NULL; y++)
   1595  1.4      adam 			xfree(list[y]);
   1596  1.4      adam 		xfree(list);
   1597  1.4      adam 		return count;
   1598  1.4      adam 	}
   1599  1.4      adam 
   1600  1.4      adam 	/* Prepare subset of commands that start with "cmd" */
   1601  1.4      adam 	cmdlen = strlen(cmd);
   1602  1.4      adam 	for (y = 0; cmds[y].c; y++)  {
   1603  1.4      adam 		if (!strncasecmp(cmd, cmds[y].c, cmdlen))
   1604  1.4      adam 			list[count++] = xstrdup(cmds[y].c);
   1605  1.4      adam 	}
   1606  1.4      adam 	list[count] = NULL;
   1607  1.4      adam 
   1608  1.4      adam 	if (count == 0)
   1609  1.4      adam 		return 0;
   1610  1.4      adam 
   1611  1.4      adam 	/* Complete ambigious command */
   1612  1.4      adam 	tmp = complete_ambiguous(cmd, list, count);
   1613  1.4      adam 	if (count > 1)
   1614  1.4      adam 		complete_display(list, 0);
   1615  1.4      adam 
   1616  1.4      adam 	for (y = 0; list[y]; y++)
   1617  1.4      adam 		xfree(list[y]);
   1618  1.4      adam 	xfree(list);
   1619  1.4      adam 
   1620  1.4      adam 	if (tmp != NULL) {
   1621  1.4      adam 		tmplen = strlen(tmp);
   1622  1.4      adam 		cmdlen = strlen(cmd);
   1623  1.4      adam 		/* If cmd may be extended then do so */
   1624  1.4      adam 		if (tmplen > cmdlen)
   1625  1.4      adam 			if (el_insertstr(el, tmp + cmdlen) == -1)
   1626  1.4      adam 				fatal("el_insertstr failed.");
   1627  1.4      adam 		lf = el_line(el);
   1628  1.4      adam 		/* Terminate argument cleanly */
   1629  1.4      adam 		if (count == 1) {
   1630  1.4      adam 			y = 0;
   1631  1.4      adam 			if (!terminated)
   1632  1.4      adam 				argterm[y++] = quote;
   1633  1.4      adam 			if (lastarg || *(lf->cursor) != ' ')
   1634  1.4      adam 				argterm[y++] = ' ';
   1635  1.4      adam 			argterm[y] = '\0';
   1636  1.4      adam 			if (y > 0 && el_insertstr(el, argterm) == -1)
   1637  1.4      adam 				fatal("el_insertstr failed.");
   1638  1.4      adam 		}
   1639  1.4      adam 		xfree(tmp);
   1640  1.4      adam 	}
   1641  1.4      adam 
   1642  1.4      adam 	return count;
   1643  1.4      adam }
   1644  1.4      adam 
   1645  1.4      adam /*
   1646  1.4      adam  * Determine whether a particular sftp command's arguments (if any)
   1647  1.4      adam  * represent local or remote files.
   1648  1.4      adam  */
   1649  1.4      adam static int
   1650  1.4      adam complete_is_remote(char *cmd) {
   1651  1.4      adam 	int i;
   1652  1.4      adam 
   1653  1.4      adam 	if (cmd == NULL)
   1654  1.4      adam 		return -1;
   1655  1.4      adam 
   1656  1.4      adam 	for (i = 0; cmds[i].c; i++) {
   1657  1.4      adam 		if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
   1658  1.4      adam 			return cmds[i].t;
   1659  1.4      adam 	}
   1660  1.4      adam 
   1661  1.4      adam 	return -1;
   1662  1.4      adam }
   1663  1.4      adam 
   1664  1.4      adam /* Autocomplete a filename "file" */
   1665  1.4      adam static int
   1666  1.4      adam complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
   1667  1.4      adam     char *file, int remote, int lastarg, char quote, int terminated)
   1668  1.4      adam {
   1669  1.4      adam 	glob_t g;
   1670  1.4      adam 	char *tmp, *tmp2, ins[3];
   1671  1.4      adam 	u_int i, hadglob, pwdlen, len, tmplen, filelen;
   1672  1.4      adam 	const LineInfo *lf;
   1673  1.4      adam 
   1674  1.4      adam 	/* Glob from "file" location */
   1675  1.4      adam 	if (file == NULL)
   1676  1.4      adam 		tmp = xstrdup("*");
   1677  1.4      adam 	else
   1678  1.4      adam 		xasprintf(&tmp, "%s*", file);
   1679  1.4      adam 
   1680  1.4      adam 	memset(&g, 0, sizeof(g));
   1681  1.4      adam 	if (remote != LOCAL) {
   1682  1.4      adam 		tmp = make_absolute(tmp, remote_path);
   1683  1.4      adam 		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
   1684  1.4      adam 	} else
   1685  1.4      adam 		glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
   1686  1.4      adam 
   1687  1.4      adam 	/* Determine length of pwd so we can trim completion display */
   1688  1.4      adam 	for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
   1689  1.4      adam 		/* Terminate counting on first unescaped glob metacharacter */
   1690  1.4      adam 		if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
   1691  1.4      adam 			if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
   1692  1.4      adam 				hadglob = 1;
   1693  1.4      adam 			break;
   1694  1.4      adam 		}
   1695  1.4      adam 		if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
   1696  1.4      adam 			tmplen++;
   1697  1.4      adam 		if (tmp[tmplen] == '/')
   1698  1.4      adam 			pwdlen = tmplen + 1;	/* track last seen '/' */
   1699  1.4      adam 	}
   1700  1.4      adam 	xfree(tmp);
   1701  1.4      adam 
   1702  1.4      adam 	if (g.gl_matchc == 0)
   1703  1.4      adam 		goto out;
   1704  1.4      adam 
   1705  1.4      adam 	if (g.gl_matchc > 1)
   1706  1.4      adam 		complete_display(g.gl_pathv, pwdlen);
   1707  1.4      adam 
   1708  1.4      adam 	tmp = NULL;
   1709  1.4      adam 	/* Don't try to extend globs */
   1710  1.4      adam 	if (file == NULL || hadglob)
   1711  1.4      adam 		goto out;
   1712  1.4      adam 
   1713  1.4      adam 	tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
   1714  1.4      adam 	tmp = path_strip(tmp2, remote_path);
   1715  1.4      adam 	xfree(tmp2);
   1716  1.4      adam 
   1717  1.4      adam 	if (tmp == NULL)
   1718  1.4      adam 		goto out;
   1719  1.4      adam 
   1720  1.4      adam 	tmplen = strlen(tmp);
   1721  1.4      adam 	filelen = strlen(file);
   1722  1.4      adam 
   1723  1.4      adam 	if (tmplen > filelen)  {
   1724  1.4      adam 		tmp2 = tmp + filelen;
   1725  1.4      adam 		len = strlen(tmp2);
   1726  1.4      adam 		/* quote argument on way out */
   1727  1.4      adam 		for (i = 0; i < len; i++) {
   1728  1.4      adam 			ins[0] = '\\';
   1729  1.4      adam 			ins[1] = tmp2[i];
   1730  1.4      adam 			ins[2] = '\0';
   1731  1.4      adam 			switch (tmp2[i]) {
   1732  1.4      adam 			case '\'':
   1733  1.4      adam 			case '"':
   1734  1.4      adam 			case '\\':
   1735  1.4      adam 			case '\t':
   1736  1.4      adam 			case ' ':
   1737  1.4      adam 				if (quote == '\0' || tmp2[i] == quote) {
   1738  1.4      adam 					if (el_insertstr(el, ins) == -1)
   1739  1.4      adam 						fatal("el_insertstr "
   1740  1.4      adam 						    "failed.");
   1741  1.4      adam 					break;
   1742  1.4      adam 				}
   1743  1.4      adam 				/* FALLTHROUGH */
   1744  1.4      adam 			default:
   1745  1.4      adam 				if (el_insertstr(el, ins + 1) == -1)
   1746  1.4      adam 					fatal("el_insertstr failed.");
   1747  1.4      adam 				break;
   1748  1.4      adam 			}
   1749  1.4      adam 		}
   1750  1.4      adam 	}
   1751  1.4      adam 
   1752  1.4      adam 	lf = el_line(el);
   1753  1.4      adam 	if (g.gl_matchc == 1) {
   1754  1.4      adam 		i = 0;
   1755  1.4      adam 		if (!terminated)
   1756  1.4      adam 			ins[i++] = quote;
   1757  1.4      adam 		if (*(lf->cursor - 1) != '/' &&
   1758  1.4      adam 		    (lastarg || *(lf->cursor) != ' '))
   1759  1.4      adam 			ins[i++] = ' ';
   1760  1.4      adam 		ins[i] = '\0';
   1761  1.4      adam 		if (i > 0 && el_insertstr(el, ins) == -1)
   1762  1.4      adam 			fatal("el_insertstr failed.");
   1763  1.4      adam 	}
   1764  1.4      adam 	xfree(tmp);
   1765  1.4      adam 
   1766  1.4      adam  out:
   1767  1.4      adam 	globfree(&g);
   1768  1.4      adam 	return g.gl_matchc;
   1769  1.4      adam }
   1770  1.4      adam 
   1771  1.4      adam /* tab-completion hook function, called via libedit */
   1772  1.4      adam static unsigned char
   1773  1.4      adam complete(EditLine *el, int ch)
   1774  1.4      adam {
   1775  1.4      adam 	char **argv, *line, quote;
   1776  1.4      adam 	u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
   1777  1.4      adam 	const LineInfo *lf;
   1778  1.4      adam 	struct complete_ctx *complete_ctx;
   1779  1.4      adam 
   1780  1.4      adam 	lf = el_line(el);
   1781  1.5      adam 	if (el_get(el, EL_CLIENTDATA, &complete_ctx) != 0)
   1782  1.4      adam 		fatal("%s: el_get failed", __func__);
   1783  1.4      adam 
   1784  1.4      adam 	/* Figure out which argument the cursor points to */
   1785  1.4      adam 	cursor = lf->cursor - lf->buffer;
   1786  1.4      adam 	line = (char *)xmalloc(cursor + 1);
   1787  1.4      adam 	memcpy(line, lf->buffer, cursor);
   1788  1.4      adam 	line[cursor] = '\0';
   1789  1.4      adam 	argv = makeargv(line, &carg, 1, &quote, &terminated);
   1790  1.4      adam 	xfree(line);
   1791  1.4      adam 
   1792  1.4      adam 	/* Get all the arguments on the line */
   1793  1.4      adam 	len = lf->lastchar - lf->buffer;
   1794  1.4      adam 	line = (char *)xmalloc(len + 1);
   1795  1.4      adam 	memcpy(line, lf->buffer, len);
   1796  1.4      adam 	line[len] = '\0';
   1797  1.4      adam 	argv = makeargv(line, &argc, 1, NULL, NULL);
   1798  1.4      adam 
   1799  1.4      adam 	/* Ensure cursor is at EOL or a argument boundary */
   1800  1.4      adam 	if (line[cursor] != ' ' && line[cursor] != '\0' &&
   1801  1.4      adam 	    line[cursor] != '\n') {
   1802  1.4      adam 		xfree(line);
   1803  1.4      adam 		return ret;
   1804  1.4      adam 	}
   1805  1.4      adam 
   1806  1.4      adam 	if (carg == 0) {
   1807  1.4      adam 		/* Show all available commands */
   1808  1.4      adam 		complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
   1809  1.4      adam 		ret = CC_REDISPLAY;
   1810  1.4      adam 	} else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
   1811  1.4      adam 		/* Handle the command parsing */
   1812  1.4      adam 		if (complete_cmd_parse(el, argv[0], argc == carg,
   1813  1.4      adam 		    quote, terminated) != 0)
   1814  1.4      adam 			ret = CC_REDISPLAY;
   1815  1.4      adam 	} else if (carg >= 1) {
   1816  1.4      adam 		/* Handle file parsing */
   1817  1.4      adam 		int remote = complete_is_remote(argv[0]);
   1818  1.4      adam 		char *filematch = NULL;
   1819  1.4      adam 
   1820  1.4      adam 		if (carg > 1 && line[cursor-1] != ' ')
   1821  1.4      adam 			filematch = argv[carg - 1];
   1822  1.4      adam 
   1823  1.4      adam 		if (remote != 0 &&
   1824  1.4      adam 		    complete_match(el, complete_ctx->conn,
   1825  1.4      adam 		    *complete_ctx->remote_pathp, filematch,
   1826  1.4      adam 		    remote, carg == argc, quote, terminated) != 0)
   1827  1.4      adam 			ret = CC_REDISPLAY;
   1828  1.4      adam 	}
   1829  1.4      adam 
   1830  1.4      adam 	xfree(line);
   1831  1.4      adam 	return ret;
   1832  1.4      adam }
   1833  1.4      adam 
   1834  1.1  christos int
   1835  1.4      adam interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
   1836  1.1  christos {
   1837  1.4      adam 	char *remote_path;
   1838  1.1  christos 	char *dir = NULL;
   1839  1.1  christos 	char cmd[2048];
   1840  1.1  christos 	int err, interactive;
   1841  1.1  christos 	EditLine *el = NULL;
   1842  1.1  christos 	History *hl = NULL;
   1843  1.1  christos 	HistEvent hev;
   1844  1.1  christos 	extern char *__progname;
   1845  1.4      adam 	struct complete_ctx complete_ctx;
   1846  1.1  christos 
   1847  1.1  christos 	if (!batchmode && isatty(STDIN_FILENO)) {
   1848  1.1  christos 		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
   1849  1.1  christos 			fatal("Couldn't initialise editline");
   1850  1.1  christos 		if ((hl = history_init()) == NULL)
   1851  1.1  christos 			fatal("Couldn't initialise editline history");
   1852  1.1  christos 		history(hl, &hev, H_SETSIZE, 100);
   1853  1.1  christos 		el_set(el, EL_HIST, history, hl);
   1854  1.1  christos 
   1855  1.1  christos 		el_set(el, EL_PROMPT, prompt);
   1856  1.1  christos 		el_set(el, EL_EDITOR, "emacs");
   1857  1.1  christos 		el_set(el, EL_TERMINAL, NULL);
   1858  1.1  christos 		el_set(el, EL_SIGNAL, 1);
   1859  1.1  christos 		el_source(el, NULL);
   1860  1.4      adam 
   1861  1.4      adam 		/* Tab Completion */
   1862  1.4      adam 		el_set(el, EL_ADDFN, "ftp-complete",
   1863  1.4      adam 		    "Context senstive argument completion", complete);
   1864  1.4      adam 		complete_ctx.conn = conn;
   1865  1.4      adam 		complete_ctx.remote_pathp = &remote_path;
   1866  1.4      adam 		el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
   1867  1.4      adam 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
   1868  1.1  christos 	}
   1869  1.1  christos 
   1870  1.4      adam 	remote_path = do_realpath(conn, ".");
   1871  1.4      adam 	if (remote_path == NULL)
   1872  1.1  christos 		fatal("Need cwd");
   1873  1.1  christos 
   1874  1.1  christos 	if (file1 != NULL) {
   1875  1.1  christos 		dir = xstrdup(file1);
   1876  1.4      adam 		dir = make_absolute(dir, remote_path);
   1877  1.1  christos 
   1878  1.1  christos 		if (remote_is_dir(conn, dir) && file2 == NULL) {
   1879  1.1  christos 			printf("Changing to: %s\n", dir);
   1880  1.1  christos 			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
   1881  1.4      adam 			if (parse_dispatch_command(conn, cmd,
   1882  1.4      adam 			    &remote_path, 1) != 0) {
   1883  1.1  christos 				xfree(dir);
   1884  1.4      adam 				xfree(remote_path);
   1885  1.1  christos 				xfree(conn);
   1886  1.1  christos 				return (-1);
   1887  1.1  christos 			}
   1888  1.1  christos 		} else {
   1889  1.1  christos 			if (file2 == NULL)
   1890  1.1  christos 				snprintf(cmd, sizeof cmd, "get %s", dir);
   1891  1.1  christos 			else
   1892  1.1  christos 				snprintf(cmd, sizeof cmd, "get %s %s", dir,
   1893  1.1  christos 				    file2);
   1894  1.1  christos 
   1895  1.4      adam 			err = parse_dispatch_command(conn, cmd,
   1896  1.4      adam 			    &remote_path, 1);
   1897  1.1  christos 			xfree(dir);
   1898  1.4      adam 			xfree(remote_path);
   1899  1.1  christos 			xfree(conn);
   1900  1.1  christos 			return (err);
   1901  1.1  christos 		}
   1902  1.1  christos 		xfree(dir);
   1903  1.1  christos 	}
   1904  1.1  christos 
   1905  1.1  christos 	setvbuf(stdout, NULL, _IOLBF, 0);
   1906  1.1  christos 	setvbuf(infile, NULL, _IOLBF, 0);
   1907  1.1  christos 
   1908  1.1  christos 	interactive = !batchmode && isatty(STDIN_FILENO);
   1909  1.1  christos 	err = 0;
   1910  1.1  christos 	for (;;) {
   1911  1.1  christos 		char *cp;
   1912  1.1  christos 		const char *line;
   1913  1.1  christos 		int count = 0;
   1914  1.1  christos 
   1915  1.1  christos 		signal(SIGINT, SIG_IGN);
   1916  1.1  christos 
   1917  1.1  christos 		if (el == NULL) {
   1918  1.1  christos 			if (interactive)
   1919  1.1  christos 				printf("sftp> ");
   1920  1.1  christos 			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
   1921  1.1  christos 				if (interactive)
   1922  1.1  christos 					printf("\n");
   1923  1.1  christos 				break;
   1924  1.1  christos 			}
   1925  1.1  christos 			if (!interactive) { /* Echo command */
   1926  1.1  christos 				printf("sftp> %s", cmd);
   1927  1.1  christos 				if (strlen(cmd) > 0 &&
   1928  1.1  christos 				    cmd[strlen(cmd) - 1] != '\n')
   1929  1.1  christos 					printf("\n");
   1930  1.1  christos 			}
   1931  1.1  christos 		} else {
   1932  1.4      adam 			if ((line = el_gets(el, &count)) == NULL ||
   1933  1.4      adam 			    count <= 0) {
   1934  1.1  christos 				printf("\n");
   1935  1.1  christos 				break;
   1936  1.1  christos 			}
   1937  1.1  christos 			history(hl, &hev, H_ENTER, line);
   1938  1.1  christos 			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
   1939  1.1  christos 				fprintf(stderr, "Error: input line too long\n");
   1940  1.1  christos 				continue;
   1941  1.1  christos 			}
   1942  1.1  christos 		}
   1943  1.1  christos 
   1944  1.1  christos 		cp = strrchr(cmd, '\n');
   1945  1.1  christos 		if (cp)
   1946  1.1  christos 			*cp = '\0';
   1947  1.1  christos 
   1948  1.1  christos 		/* Handle user interrupts gracefully during commands */
   1949  1.1  christos 		interrupted = 0;
   1950  1.1  christos 		signal(SIGINT, cmd_interrupt);
   1951  1.1  christos 
   1952  1.4      adam 		err = parse_dispatch_command(conn, cmd, &remote_path,
   1953  1.4      adam 		    batchmode);
   1954  1.1  christos 		if (err != 0)
   1955  1.1  christos 			break;
   1956  1.1  christos 	}
   1957  1.4      adam 	xfree(remote_path);
   1958  1.1  christos 	xfree(conn);
   1959  1.1  christos 
   1960  1.1  christos 	if (el != NULL)
   1961  1.1  christos 		el_end(el);
   1962  1.1  christos 
   1963  1.1  christos 	/* err == 1 signifies normal "quit" exit */
   1964  1.1  christos 	return (err >= 0 ? 0 : -1);
   1965  1.1  christos }
   1966  1.1  christos 
   1967  1.1  christos static void
   1968  1.1  christos connect_to_server(char *path, char **args, int *in, int *out)
   1969  1.1  christos {
   1970  1.1  christos 	int c_in, c_out;
   1971  1.1  christos 
   1972  1.1  christos 	int inout[2];
   1973  1.1  christos 
   1974  1.1  christos 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
   1975  1.1  christos 		fatal("socketpair: %s", strerror(errno));
   1976  1.1  christos 	*in = *out = inout[0];
   1977  1.1  christos 	c_in = c_out = inout[1];
   1978  1.1  christos 
   1979  1.1  christos 	if ((sshpid = fork()) == -1)
   1980  1.1  christos 		fatal("fork: %s", strerror(errno));
   1981  1.1  christos 	else if (sshpid == 0) {
   1982  1.1  christos 		if ((dup2(c_in, STDIN_FILENO) == -1) ||
   1983  1.1  christos 		    (dup2(c_out, STDOUT_FILENO) == -1)) {
   1984  1.1  christos 			fprintf(stderr, "dup2: %s\n", strerror(errno));
   1985  1.1  christos 			_exit(1);
   1986  1.1  christos 		}
   1987  1.1  christos 		close(*in);
   1988  1.1  christos 		close(*out);
   1989  1.1  christos 		close(c_in);
   1990  1.1  christos 		close(c_out);
   1991  1.1  christos 
   1992  1.1  christos 		/*
   1993  1.1  christos 		 * The underlying ssh is in the same process group, so we must
   1994  1.1  christos 		 * ignore SIGINT if we want to gracefully abort commands,
   1995  1.1  christos 		 * otherwise the signal will make it to the ssh process and
   1996  1.4      adam 		 * kill it too.  Contrawise, since sftp sends SIGTERMs to the
   1997  1.4      adam 		 * underlying ssh, it must *not* ignore that signal.
   1998  1.1  christos 		 */
   1999  1.1  christos 		signal(SIGINT, SIG_IGN);
   2000  1.4      adam 		signal(SIGTERM, SIG_DFL);
   2001  1.1  christos 		execvp(path, args);
   2002  1.1  christos 		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
   2003  1.1  christos 		_exit(1);
   2004  1.1  christos 	}
   2005  1.1  christos 
   2006  1.1  christos 	signal(SIGTERM, killchild);
   2007  1.1  christos 	signal(SIGINT, killchild);
   2008  1.1  christos 	signal(SIGHUP, killchild);
   2009  1.1  christos 	close(c_in);
   2010  1.1  christos 	close(c_out);
   2011  1.1  christos }
   2012  1.1  christos 
   2013  1.1  christos static void
   2014  1.1  christos usage(void)
   2015  1.1  christos {
   2016  1.1  christos 	extern char *__progname;
   2017  1.1  christos 
   2018  1.1  christos 	fprintf(stderr,
   2019  1.4      adam 	    "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
   2020  1.4      adam 	    "          [-D sftp_server_path] [-F ssh_config] "
   2021  1.4      adam 	    "[-i identity_file]\n"
   2022  1.4      adam 	    "          [-o ssh_option] [-P port] [-R num_requests] "
   2023  1.4      adam 	    "[-S program]\n"
   2024  1.4      adam 	    "          [-s subsystem | sftp_server] host\n"
   2025  1.1  christos 	    "       %s [user@]host[:file ...]\n"
   2026  1.1  christos 	    "       %s [user@]host[:dir[/]]\n"
   2027  1.4      adam 	    "       %s -b batchfile [user@]host\n",
   2028  1.4      adam 	    __progname, __progname, __progname, __progname);
   2029  1.1  christos 	exit(1);
   2030  1.1  christos }
   2031  1.1  christos 
   2032  1.1  christos int
   2033  1.1  christos main(int argc, char **argv)
   2034  1.1  christos {
   2035  1.1  christos 	int in, out, ch, err;
   2036  1.4      adam 	char *host = NULL, *userhost, *cp, *file2 = NULL;
   2037  1.1  christos 	int debug_level = 0, sshver = 2;
   2038  1.1  christos 	char *file1 = NULL, *sftp_server = NULL;
   2039  1.1  christos 	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
   2040  1.1  christos 	LogLevel ll = SYSLOG_LEVEL_INFO;
   2041  1.1  christos 	arglist args;
   2042  1.1  christos 	extern int optind;
   2043  1.1  christos 	extern char *optarg;
   2044  1.4      adam 	struct sftp_conn *conn;
   2045  1.4      adam 	size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
   2046  1.4      adam 	size_t num_requests = DEFAULT_NUM_REQUESTS;
   2047  1.1  christos 
   2048  1.1  christos 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
   2049  1.1  christos 	sanitise_stdfd();
   2050  1.1  christos 
   2051  1.1  christos 	memset(&args, '\0', sizeof(args));
   2052  1.1  christos 	args.list = NULL;
   2053  1.1  christos 	addargs(&args, "%s", ssh_program);
   2054  1.1  christos 	addargs(&args, "-oForwardX11 no");
   2055  1.1  christos 	addargs(&args, "-oForwardAgent no");
   2056  1.1  christos 	addargs(&args, "-oPermitLocalCommand no");
   2057  1.1  christos 	addargs(&args, "-oClearAllForwardings yes");
   2058  1.1  christos 
   2059  1.1  christos 	ll = SYSLOG_LEVEL_INFO;
   2060  1.1  christos 	infile = stdin;
   2061  1.1  christos 
   2062  1.4      adam 	while ((ch = getopt(argc, argv,
   2063  1.4      adam 	    "1246hpqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
   2064  1.1  christos 		switch (ch) {
   2065  1.4      adam 		/* Passed through to ssh(1) */
   2066  1.4      adam 		case '4':
   2067  1.4      adam 		case '6':
   2068  1.1  christos 		case 'C':
   2069  1.4      adam 			addargs(&args, "-%c", ch);
   2070  1.4      adam 			break;
   2071  1.4      adam 		/* Passed through to ssh(1) with argument */
   2072  1.4      adam 		case 'F':
   2073  1.4      adam 		case 'c':
   2074  1.4      adam 		case 'i':
   2075  1.4      adam 		case 'o':
   2076  1.4      adam 			addargs(&args, "-%c", ch);
   2077  1.4      adam 			addargs(&args, "%s", optarg);
   2078  1.4      adam 			break;
   2079  1.4      adam 		case 'q':
   2080  1.4      adam 			showprogress = 0;
   2081  1.4      adam 			addargs(&args, "-%c", ch);
   2082  1.4      adam 			break;
   2083  1.4      adam 		case 'P':
   2084  1.4      adam 			addargs(&args, "-oPort %s", optarg);
   2085  1.1  christos 			break;
   2086  1.1  christos 		case 'v':
   2087  1.1  christos 			if (debug_level < 3) {
   2088  1.1  christos 				addargs(&args, "-v");
   2089  1.1  christos 				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
   2090  1.1  christos 			}
   2091  1.1  christos 			debug_level++;
   2092  1.1  christos 			break;
   2093  1.1  christos 		case '1':
   2094  1.1  christos 			sshver = 1;
   2095  1.1  christos 			if (sftp_server == NULL)
   2096  1.1  christos 				sftp_server = _PATH_SFTP_SERVER;
   2097  1.1  christos 			break;
   2098  1.4      adam 		case '2':
   2099  1.4      adam 			sshver = 2;
   2100  1.1  christos 			break;
   2101  1.4      adam 		case 'B':
   2102  1.4      adam 			copy_buffer_len = strtol(optarg, &cp, 10);
   2103  1.4      adam 			if (copy_buffer_len == 0 || *cp != '\0')
   2104  1.4      adam 				fatal("Invalid buffer size \"%s\"", optarg);
   2105  1.1  christos 			break;
   2106  1.1  christos 		case 'b':
   2107  1.1  christos 			if (batchmode)
   2108  1.1  christos 				fatal("Batch file already specified.");
   2109  1.1  christos 
   2110  1.1  christos 			/* Allow "-" as stdin */
   2111  1.1  christos 			if (strcmp(optarg, "-") != 0 &&
   2112  1.1  christos 			    (infile = fopen(optarg, "r")) == NULL)
   2113  1.1  christos 				fatal("%s (%s).", strerror(errno), optarg);
   2114  1.1  christos 			showprogress = 0;
   2115  1.1  christos 			batchmode = 1;
   2116  1.1  christos 			addargs(&args, "-obatchmode yes");
   2117  1.1  christos 			break;
   2118  1.4      adam 		case 'p':
   2119  1.4      adam 			global_pflag = 1;
   2120  1.4      adam 			break;
   2121  1.4      adam 		case 'D':
   2122  1.1  christos 			sftp_direct = optarg;
   2123  1.1  christos 			break;
   2124  1.4      adam 		case 'r':
   2125  1.4      adam 			global_rflag = 1;
   2126  1.1  christos 			break;
   2127  1.1  christos 		case 'R':
   2128  1.1  christos 			num_requests = strtol(optarg, &cp, 10);
   2129  1.1  christos 			if (num_requests == 0 || *cp != '\0')
   2130  1.1  christos 				fatal("Invalid number of requests \"%s\"",
   2131  1.1  christos 				    optarg);
   2132  1.1  christos 			break;
   2133  1.4      adam 		case 's':
   2134  1.4      adam 			sftp_server = optarg;
   2135  1.4      adam 			break;
   2136  1.4      adam 		case 'S':
   2137  1.4      adam 			ssh_program = optarg;
   2138  1.4      adam 			replacearg(&args, 0, "%s", ssh_program);
   2139  1.4      adam 			break;
   2140  1.1  christos 		case 'h':
   2141  1.1  christos 		default:
   2142  1.1  christos 			usage();
   2143  1.1  christos 		}
   2144  1.1  christos 	}
   2145  1.1  christos 
   2146  1.1  christos 	if (!isatty(STDERR_FILENO))
   2147  1.1  christos 		showprogress = 0;
   2148  1.1  christos 
   2149  1.1  christos 	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
   2150  1.1  christos 
   2151  1.1  christos 	if (sftp_direct == NULL) {
   2152  1.1  christos 		if (optind == argc || argc > (optind + 2))
   2153  1.1  christos 			usage();
   2154  1.1  christos 
   2155  1.1  christos 		userhost = xstrdup(argv[optind]);
   2156  1.1  christos 		file2 = argv[optind+1];
   2157  1.1  christos 
   2158  1.1  christos 		if ((host = strrchr(userhost, '@')) == NULL)
   2159  1.1  christos 			host = userhost;
   2160  1.1  christos 		else {
   2161  1.1  christos 			*host++ = '\0';
   2162  1.1  christos 			if (!userhost[0]) {
   2163  1.1  christos 				fprintf(stderr, "Missing username\n");
   2164  1.1  christos 				usage();
   2165  1.1  christos 			}
   2166  1.4      adam 			addargs(&args, "-l");
   2167  1.4      adam 			addargs(&args, "%s", userhost);
   2168  1.1  christos 		}
   2169  1.1  christos 
   2170  1.1  christos 		if ((cp = colon(host)) != NULL) {
   2171  1.1  christos 			*cp++ = '\0';
   2172  1.1  christos 			file1 = cp;
   2173  1.1  christos 		}
   2174  1.1  christos 
   2175  1.1  christos 		host = cleanhostname(host);
   2176  1.1  christos 		if (!*host) {
   2177  1.1  christos 			fprintf(stderr, "Missing hostname\n");
   2178  1.1  christos 			usage();
   2179  1.1  christos 		}
   2180  1.1  christos 
   2181  1.1  christos 		addargs(&args, "-oProtocol %d", sshver);
   2182  1.1  christos 
   2183  1.1  christos 		/* no subsystem if the server-spec contains a '/' */
   2184  1.1  christos 		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
   2185  1.1  christos 			addargs(&args, "-s");
   2186  1.1  christos 
   2187  1.4      adam 		addargs(&args, "--");
   2188  1.1  christos 		addargs(&args, "%s", host);
   2189  1.1  christos 		addargs(&args, "%s", (sftp_server != NULL ?
   2190  1.1  christos 		    sftp_server : "sftp"));
   2191  1.1  christos 
   2192  1.1  christos 		connect_to_server(ssh_program, args.list, &in, &out);
   2193  1.1  christos 	} else {
   2194  1.1  christos 		args.list = NULL;
   2195  1.1  christos 		addargs(&args, "sftp-server");
   2196  1.1  christos 
   2197  1.1  christos 		connect_to_server(sftp_direct, args.list, &in, &out);
   2198  1.1  christos 	}
   2199  1.1  christos 	freeargs(&args);
   2200  1.1  christos 
   2201  1.4      adam 	conn = do_init(in, out, copy_buffer_len, num_requests);
   2202  1.4      adam 	if (conn == NULL)
   2203  1.4      adam 		fatal("Couldn't initialise connection to server");
   2204  1.4      adam 
   2205  1.4      adam 	if (!batchmode) {
   2206  1.4      adam 		if (sftp_direct == NULL)
   2207  1.4      adam 			fprintf(stderr, "Connected to %s.\n", host);
   2208  1.4      adam 		else
   2209  1.4      adam 			fprintf(stderr, "Attached to %s.\n", sftp_direct);
   2210  1.4      adam 	}
   2211  1.4      adam 
   2212  1.4      adam 	err = interactive_loop(conn, file1, file2);
   2213  1.1  christos 
   2214  1.1  christos 	close(in);
   2215  1.1  christos 	close(out);
   2216  1.1  christos 	if (batchmode)
   2217  1.1  christos 		fclose(infile);
   2218  1.1  christos 
   2219  1.1  christos 	while (waitpid(sshpid, NULL, 0) == -1)
   2220  1.1  christos 		if (errno != EINTR)
   2221  1.1  christos 			fatal("Couldn't wait for ssh process: %s",
   2222  1.1  christos 			    strerror(errno));
   2223  1.1  christos 
   2224  1.1  christos 	exit(err == 0 ? 0 : 1);
   2225  1.1  christos }
   2226