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 /* ----------------------------------------------------------------------
34 /* ----------------------------------------------------------------------
35 * Higher-level helper functions used in commands.
39 * Canonify a pathname starting from the pwd.
41 char *canonify(char *name) {
43 return fxp_realpath(name, NULL);
45 return fxp_realpath(pwd, name);
48 /* ----------------------------------------------------------------------
49 * Actual sftp commands.
53 int nwords, wordssize;
54 int (*obey)(struct sftp_command *);/* returns <0 to quit */
57 int sftp_cmd_null(struct sftp_command *cmd) {
61 int sftp_cmd_unknown(struct sftp_command *cmd) {
62 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
66 int sftp_cmd_quit(struct sftp_command *cmd) {
71 * List a directory. If no arguments are given, list pwd; otherwise
72 * list the directory given in words[1].
74 static int sftp_ls_compare(const void *av, const void *bv) {
75 const struct fxp_name *a = (const struct fxp_name *)av;
76 const struct fxp_name *b = (const struct fxp_name *)bv;
77 return strcmp(a->filename, b->filename);
79 int sftp_cmd_ls(struct sftp_command *cmd) {
80 struct fxp_handle *dirh;
81 struct fxp_names *names;
82 struct fxp_name *ournames;
94 printf("%s: %s\n", dir, fxp_error());
98 printf("Listing directory %s\n", cdir);
100 dirh = fxp_opendir(cdir);
102 printf("Unable to open %s: %s\n", dir, fxp_error());
104 nnames = namesize = 0;
109 names = fxp_readdir(dirh);
111 if (fxp_error_type() == SSH_FX_EOF)
113 printf("Reading directory %s: %s\n", dir, fxp_error());
116 if (names->nnames == 0) {
117 fxp_free_names(names);
121 if (nnames + names->nnames >= namesize) {
122 namesize += names->nnames + 128;
123 ournames = srealloc(ournames, namesize * sizeof(*ournames));
126 for (i = 0; i < names->nnames; i++)
127 ournames[nnames++] = names->names[i];
129 names->nnames = 0; /* prevent free_names */
130 fxp_free_names(names);
135 * Now we have our filenames. Sort them by actual file
136 * name, and then output the longname parts.
138 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
143 for (i = 0; i < nnames; i++)
144 printf("%s\n", ournames[i].longname);
153 * Change directories. We do this by canonifying the new name, then
154 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
156 int sftp_cmd_cd(struct sftp_command *cmd) {
157 struct fxp_handle *dirh;
161 dir = fxp_realpath(".", NULL);
163 dir = canonify(cmd->words[1]);
166 printf("%s: %s\n", dir, fxp_error());
170 dirh = fxp_opendir(dir);
172 printf("Directory %s: %s\n", dir, fxp_error());
181 printf("Remote directory is now %s\n", pwd);
187 * Get a file and save it at the local end.
189 int sftp_cmd_get(struct sftp_command *cmd) {
190 struct fxp_handle *fh;
191 char *fname, *outfname;
195 if (cmd->nwords < 2) {
196 printf("get: expects a filename\n");
200 fname = canonify(cmd->words[1]);
202 printf("%s: %s\n", cmd->words[1], fxp_error());
205 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
207 fh = fxp_open(fname, SSH_FXF_READ);
209 printf("%s: %s\n", fname, fxp_error());
213 fp = fopen(outfname, "wb");
215 printf("local: unable to open %s\n", outfname);
221 printf("remote:%s => local:%s\n", fname, outfname);
223 offset = uint64_make(0,0);
226 * FIXME: we can use FXP_FSTAT here to get the file size, and
227 * thus put up a progress bar.
234 len = fxp_read(fh, buffer, offset, sizeof(buffer));
235 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) ||
239 printf("error while reading: %s\n", fxp_error());
245 wlen = fwrite(buffer, 1, len-wpos, fp);
247 printf("error while writing local file\n");
252 if (wpos < len) /* we had an error */
254 offset = uint64_add32(offset, len);
265 * Send a file and store it at the remote end.
267 int sftp_cmd_put(struct sftp_command *cmd) {
268 struct fxp_handle *fh;
269 char *fname, *origoutfname, *outfname;
273 if (cmd->nwords < 2) {
274 printf("put: expects a filename\n");
278 fname = cmd->words[1];
279 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
280 outfname = canonify(origoutfname);
282 printf("%s: %s\n", origoutfname, fxp_error());
286 fp = fopen(fname, "rb");
288 printf("local: unable to open %s\n", fname);
293 fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
295 printf("%s: %s\n", outfname, fxp_error());
300 printf("local:%s => remote:%s\n", fname, outfname);
302 offset = uint64_make(0,0);
305 * FIXME: we can use FXP_FSTAT here to get the file size, and
306 * thus put up a progress bar.
312 len = fread(buffer, 1, len, fp);
314 printf("error while reading local file\n");
316 } else if (len == 0) {
319 if (!fxp_write(fh, buffer, offset, sizeof(buffer))) {
320 printf("error while writing: %s\n", fxp_error());
323 offset = uint64_add32(offset, len);
333 static struct sftp_cmd_lookup {
335 int (*obey)(struct sftp_command *);
338 * List of sftp commands. This is binary-searched so it MUST be
341 {"bye", sftp_cmd_quit},
343 {"exit", sftp_cmd_quit},
344 {"get", sftp_cmd_get},
346 {"put", sftp_cmd_put},
347 {"quit", sftp_cmd_quit},
350 /* ----------------------------------------------------------------------
351 * Command line reading and parsing.
353 struct sftp_command *sftp_getcmd(void) {
355 int linelen, linesize;
356 struct sftp_command *cmd;
363 cmd = smalloc(sizeof(struct sftp_command));
369 linesize = linelen = 0;
375 line = srealloc(line, linesize);
376 ret = fgets(line+linelen, linesize-linelen, stdin);
378 if (!ret || (linelen == 0 && line[0] == '\0')) {
379 cmd->obey = sftp_cmd_quit;
381 return cmd; /* eof */
383 len = linelen + strlen(line+linelen);
385 if (line[linelen-1] == '\n') {
387 line[linelen] = '\0';
393 * Parse the command line into words. The syntax is:
394 * - double quotes are removed, but cause spaces within to be
395 * treated as non-separating.
396 * - a double-doublequote pair is a literal double quote, inside
397 * _or_ outside quotes. Like this:
399 * firstword "second word" "this has ""quotes"" in" sodoes""this""
405 * >this has "quotes" in<
410 /* skip whitespace */
411 while (*p && (*p == ' ' || *p == '\t')) p++;
412 /* mark start of word */
413 q = r = p; /* q sits at start, r writes word */
416 if (!quoting && (*p == ' ' || *p == '\t'))
417 break; /* reached end of word */
418 else if (*p == '"' && p[1] == '"')
419 p+=2, *r++ = '"'; /* a literal quote */
421 p++, quoting = !quoting;
425 if (*p) p++; /* skip over the whitespace */
427 if (cmd->nwords >= cmd->wordssize) {
428 cmd->wordssize = cmd->nwords + 16;
429 cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
431 cmd->words[cmd->nwords++] = q;
435 * Now parse the first word and assign a function.
438 if (cmd->nwords == 0)
439 cmd->obey = sftp_cmd_null;
443 cmd->obey = sftp_cmd_unknown;
446 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
449 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
455 cmd->obey = sftp_lookup[k].obey;
466 * Do protocol initialisation.
470 "Fatal: unable to initialise SFTP: %s\n",
475 * Find out where our home directory is.
477 homedir = fxp_realpath(".", NULL);
480 "Warning: failed to resolve home directory: %s\n",
482 homedir = dupstr(".");
484 printf("Remote working directory is %s\n", homedir);
486 pwd = dupstr(homedir);
488 /* ------------------------------------------------------------------
489 * Now we're ready to do Real Stuff.
492 struct sftp_command *cmd;
496 if (cmd->obey(cmd) < 0)
500 /* ------------------------------------------------------------------
501 * We've received an exit command. Tidy up and leave.