2 * psftp.c: front end for PSFTP.
13 #define smalloc malloc
14 #define srealloc realloc
17 /* ----------------------------------------------------------------------
18 * String handling routines.
21 char *dupstr(char *s) {
23 char *p = smalloc(len+1);
28 /* Allocate the concatenation of N strings. Terminate arg list with NULL. */
29 char *dupcat(char *s1, ...) {
37 sn = va_arg(ap, char *);
50 sn = va_arg(ap, char *);
61 /* ----------------------------------------------------------------------
67 /* ----------------------------------------------------------------------
68 * Higher-level helper functions used in commands.
72 * Attempt to canonify a pathname starting from the pwd. If
73 * canonification fails, at least fall back to returning a _valid_
74 * pathname (though it may be ugly, eg /home/simon/../foobar).
76 char *canonify(char *name) {
77 char *fullname, *canonname;
79 fullname = dupstr(name);
81 fullname = dupcat(pwd, "/", name, NULL);
83 canonname = fxp_realpath(name);
91 /* ----------------------------------------------------------------------
92 * Actual sftp commands.
96 int nwords, wordssize;
97 int (*obey)(struct sftp_command *);/* returns <0 to quit */
100 int sftp_cmd_null(struct sftp_command *cmd) {
104 int sftp_cmd_unknown(struct sftp_command *cmd) {
105 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
109 int sftp_cmd_quit(struct sftp_command *cmd) {
114 * List a directory. If no arguments are given, list pwd; otherwise
115 * list the directory given in words[1].
117 static int sftp_ls_compare(const void *av, const void *bv) {
118 const struct fxp_name *a = (const struct fxp_name *)av;
119 const struct fxp_name *b = (const struct fxp_name *)bv;
120 return strcmp(a->filename, b->filename);
122 int sftp_cmd_ls(struct sftp_command *cmd) {
123 struct fxp_handle *dirh;
124 struct fxp_names *names;
125 struct fxp_name *ournames;
126 int nnames, namesize;
135 cdir = canonify(dir);
137 printf("%s: %s\n", dir, fxp_error());
141 printf("Listing directory %s\n", cdir);
143 dirh = fxp_opendir(cdir);
145 printf("Unable to open %s: %s\n", dir, fxp_error());
147 nnames = namesize = 0;
152 names = fxp_readdir(dirh);
154 if (fxp_error_type() == SSH_FX_EOF)
156 printf("Reading directory %s: %s\n", dir, fxp_error());
159 if (names->nnames == 0) {
160 fxp_free_names(names);
164 if (nnames + names->nnames >= namesize) {
165 namesize += names->nnames + 128;
166 ournames = srealloc(ournames, namesize * sizeof(*ournames));
169 for (i = 0; i < names->nnames; i++)
170 ournames[nnames++] = names->names[i];
172 names->nnames = 0; /* prevent free_names */
173 fxp_free_names(names);
178 * Now we have our filenames. Sort them by actual file
179 * name, and then output the longname parts.
181 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
186 for (i = 0; i < nnames; i++)
187 printf("%s\n", ournames[i].longname);
196 * Change directories. We do this by canonifying the new name, then
197 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
199 int sftp_cmd_cd(struct sftp_command *cmd) {
200 struct fxp_handle *dirh;
204 dir = dupstr(homedir);
206 dir = canonify(cmd->words[1]);
209 printf("%s: %s\n", dir, fxp_error());
213 dirh = fxp_opendir(dir);
215 printf("Directory %s: %s\n", dir, fxp_error());
224 printf("Remote directory is now %s\n", pwd);
230 * Get a file and save it at the local end.
232 int sftp_cmd_get(struct sftp_command *cmd) {
233 struct fxp_handle *fh;
234 char *fname, *outfname;
238 if (cmd->nwords < 2) {
239 printf("get: expects a filename\n");
243 fname = canonify(cmd->words[1]);
245 printf("%s: %s\n", cmd->words[1], fxp_error());
248 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
250 fh = fxp_open(fname, SSH_FXF_READ);
252 printf("%s: %s\n", fname, fxp_error());
256 fp = fopen(outfname, "wb");
258 printf("local: unable to open %s\n", outfname);
264 printf("remote:%s => local:%s\n", fname, outfname);
266 offset = uint64_make(0,0);
269 * FIXME: we can use FXP_FSTAT here to get the file size, and
270 * thus put up a progress bar.
277 len = fxp_read(fh, buffer, offset, sizeof(buffer));
278 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) ||
282 printf("error while reading: %s\n", fxp_error());
288 wlen = fwrite(buffer, 1, len-wpos, fp);
290 printf("error while writing local file\n");
295 if (wpos < len) /* we had an error */
297 offset = uint64_add32(offset, len);
308 * Send a file and store it at the remote end.
310 int sftp_cmd_put(struct sftp_command *cmd) {
311 struct fxp_handle *fh;
312 char *fname, *origoutfname, *outfname;
316 if (cmd->nwords < 2) {
317 printf("put: expects a filename\n");
321 fname = cmd->words[1];
322 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
323 outfname = canonify(origoutfname);
325 printf("%s: %s\n", origoutfname, fxp_error());
329 fp = fopen(fname, "rb");
331 printf("local: unable to open %s\n", fname);
336 fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
338 printf("%s: %s\n", outfname, fxp_error());
343 printf("local:%s => remote:%s\n", fname, outfname);
345 offset = uint64_make(0,0);
348 * FIXME: we can use FXP_FSTAT here to get the file size, and
349 * thus put up a progress bar.
355 len = fread(buffer, 1, sizeof(buffer), fp);
357 printf("error while reading local file\n");
359 } else if (len == 0) {
362 if (!fxp_write(fh, buffer, offset, len)) {
363 printf("error while writing: %s\n", fxp_error());
366 offset = uint64_add32(offset, len);
376 static struct sftp_cmd_lookup {
378 int (*obey)(struct sftp_command *);
381 * List of sftp commands. This is binary-searched so it MUST be
384 {"bye", sftp_cmd_quit},
386 {"exit", sftp_cmd_quit},
387 {"get", sftp_cmd_get},
389 {"put", sftp_cmd_put},
390 {"quit", sftp_cmd_quit},
393 /* ----------------------------------------------------------------------
394 * Command line reading and parsing.
396 struct sftp_command *sftp_getcmd(void) {
398 int linelen, linesize;
399 struct sftp_command *cmd;
406 cmd = smalloc(sizeof(struct sftp_command));
412 linesize = linelen = 0;
418 line = srealloc(line, linesize);
419 ret = fgets(line+linelen, linesize-linelen, stdin);
421 if (!ret || (linelen == 0 && line[0] == '\0')) {
422 cmd->obey = sftp_cmd_quit;
424 return cmd; /* eof */
426 len = linelen + strlen(line+linelen);
428 if (line[linelen-1] == '\n') {
430 line[linelen] = '\0';
436 * Parse the command line into words. The syntax is:
437 * - double quotes are removed, but cause spaces within to be
438 * treated as non-separating.
439 * - a double-doublequote pair is a literal double quote, inside
440 * _or_ outside quotes. Like this:
442 * firstword "second word" "this has ""quotes"" in" sodoes""this""
448 * >this has "quotes" in<
453 /* skip whitespace */
454 while (*p && (*p == ' ' || *p == '\t')) p++;
455 /* mark start of word */
456 q = r = p; /* q sits at start, r writes word */
459 if (!quoting && (*p == ' ' || *p == '\t'))
460 break; /* reached end of word */
461 else if (*p == '"' && p[1] == '"')
462 p+=2, *r++ = '"'; /* a literal quote */
464 p++, quoting = !quoting;
468 if (*p) p++; /* skip over the whitespace */
470 if (cmd->nwords >= cmd->wordssize) {
471 cmd->wordssize = cmd->nwords + 16;
472 cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
474 cmd->words[cmd->nwords++] = q;
478 * Now parse the first word and assign a function.
481 if (cmd->nwords == 0)
482 cmd->obey = sftp_cmd_null;
486 cmd->obey = sftp_cmd_unknown;
489 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
492 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
498 cmd->obey = sftp_lookup[k].obey;
509 * Do protocol initialisation.
513 "Fatal: unable to initialise SFTP: %s\n",
518 * Find out where our home directory is.
520 homedir = fxp_realpath(".");
523 "Warning: failed to resolve home directory: %s\n",
525 homedir = dupstr(".");
527 printf("Remote working directory is %s\n", homedir);
529 pwd = dupstr(homedir);
531 /* ------------------------------------------------------------------
532 * Now we're ready to do Real Stuff.
535 struct sftp_command *cmd;
539 if (cmd->obey(cmd) < 0)
543 /* ------------------------------------------------------------------
544 * We've received an exit command. Tidy up and leave.