- djm@cvs.openbsd.org 2001/03/16 08:16:18
     [sftp-client.c sftp-client.h sftp-glob.c sftp-int.c]
     Revise globbing for get/put to be more shell-like. In particular,
     "get/put file* directory/" now works. ok markus@
diff --git a/sftp-int.c b/sftp-int.c
index cf86012..fdadf23 100644
--- a/sftp-int.c
+++ b/sftp-int.c
@@ -26,7 +26,7 @@
 /* XXX: recursive operations */
 
 #include "includes.h"
-RCSID("$OpenBSD: sftp-int.c,v 1.28 2001/03/14 15:15:58 markus Exp $");
+RCSID("$OpenBSD: sftp-int.c,v 1.29 2001/03/16 08:16:18 djm Exp $");
 
 #include "buffer.h"
 #include "xmalloc.h"
@@ -195,18 +195,50 @@
 }
 
 char *
+path_append(char *p1, char *p2)
+{
+	char *ret;
+
+	ret = xmalloc(strlen(p1) + strlen(p2) + 2);
+	strcpy(ret, p1);
+	strcat(ret, "/");
+	strcat(ret, p2);
+
+	return(ret);
+}
+
+char *
 make_absolute(char *p, char *pwd)
 {
-	char buf[2048];
+	char *abs;
 
 	/* Derelativise */
 	if (p && p[0] != '/') {
-		snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
+		abs = path_append(pwd, p);
 		xfree(p);
-		p = xstrdup(buf);
+		return(abs);
+	} else
+		return(p);
+}
+
+int
+infer_path(const char *p, char **ifp)
+{
+	char *cp;
+
+	cp = strrchr(p, '/');
+	if (cp == NULL) {
+		*ifp = xstrdup(p);
+		return(0);
 	}
 
-	return(p);
+	if (!cp[1]) {
+		error("Invalid path");
+		return(-1);
+	}
+
+	*ifp = xstrdup(cp + 1);
+	return(0);
 }
 
 int
@@ -281,23 +313,182 @@
 }
 
 int
-infer_path(const char *p, char **ifp)
+is_dir(char *path)
 {
-	char *cp;
+	struct stat sb;
 
-	cp = strrchr(p, '/');
-	if (cp == NULL) {
-		*ifp = xstrdup(p);
+	/* XXX: report errors? */
+	if (stat(path, &sb) == -1)
 		return(0);
+
+	return(sb.st_mode & S_IFDIR);
+}
+
+int
+remote_is_dir(int in, int out, char *path)
+{
+	Attrib *a;
+
+	/* XXX: report errors? */
+	if ((a = do_stat(in, out, path, 1)) == NULL)
+		return(0);
+	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
+		return(0);
+	return(a->perm & S_IFDIR);
+}
+
+int
+process_get(int in, int out, char *src, char *dst, char *pwd, int pflag)
+{
+	char *abs_src = NULL;
+	char *abs_dst = NULL;
+	char *tmp;
+	glob_t g;
+	int err = 0;
+	int i;
+	
+	abs_src = xstrdup(src);
+	abs_src = make_absolute(abs_src, pwd);
+
+	memset(&g, '\0', sizeof(g));
+	debug3("Looking up %s", abs_src);
+	if (remote_glob(in, out, abs_src, 0, NULL, &g)) {
+		error("File \"%s\" not found.", abs_src);
+		err = -1;
+		goto out;
 	}
 
-	if (!cp[1]) {
-		error("Invalid path");
-		return(-1);
+	/* Only one match, dst may be file, directory or unspecified */
+	if (g.gl_pathv[0] && g.gl_matchc == 1) {
+		if (dst) {
+			/* If directory specified, append filename */
+			if (is_dir(dst)) {
+				if (infer_path(g.gl_pathv[0], &tmp)) {
+					err = 1;
+					goto out;
+				}
+				abs_dst = path_append(dst, tmp);
+				xfree(tmp);
+			} else
+				abs_dst = xstrdup(dst);
+		} else if (infer_path(g.gl_pathv[0], &abs_dst)) {
+			err = -1;
+			goto out;
+		}
+		printf("Fetching %s to %s\n", g.gl_pathv[0], abs_dst);
+		err = do_download(in, out, g.gl_pathv[0], abs_dst, pflag);
+		goto out;
 	}
 
-	*ifp = xstrdup(cp + 1);
-	return(0);
+	/* Multiple matches, dst may be directory or unspecified */
+	if (dst && !is_dir(dst)) {
+		error("Multiple files match, but \"%s\" is not a directory", 
+		    dst);
+		err = -1;
+		goto out;
+	}
+	
+	for(i = 0; g.gl_pathv[i]; i++) {
+		if (infer_path(g.gl_pathv[i], &tmp)) {
+			err = -1;
+			goto out;
+		}
+		if (dst) {
+			abs_dst = path_append(dst, tmp);
+			xfree(tmp);
+		} else
+			abs_dst = tmp;
+
+		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
+		if (do_download(in, out, g.gl_pathv[i], abs_dst, pflag) == -1)
+			err = -1;
+		xfree(abs_dst);
+		abs_dst = NULL;
+	}
+
+out:
+	xfree(abs_src);
+	if (abs_dst)
+		xfree(abs_dst);
+	globfree(&g);
+	return(err);
+}
+
+int
+process_put(int in, int out, char *src, char *dst, char *pwd, int pflag)
+{
+	char *tmp_dst = NULL;
+	char *abs_dst = NULL;
+	char *tmp;
+	glob_t g;
+	int err = 0;
+	int i;
+
+	if (dst) {
+		tmp_dst = xstrdup(dst);
+		tmp_dst = make_absolute(tmp_dst, pwd);
+	}
+
+	memset(&g, '\0', sizeof(g));
+	debug3("Looking up %s", src);
+	if (glob(src, 0, NULL, &g)) {
+		error("File \"%s\" not found.", src);
+		err = -1;
+		goto out;
+	}
+
+	/* Only one match, dst may be file, directory or unspecified */
+	if (g.gl_pathv[0] && g.gl_matchc == 1) {
+		if (tmp_dst) {
+			/* If directory specified, append filename */
+			if (remote_is_dir(in, out, tmp_dst)) {
+				if (infer_path(g.gl_pathv[0], &tmp)) {
+					err = 1;
+					goto out;
+				}
+				abs_dst = path_append(tmp_dst, tmp);
+				xfree(tmp);
+			} else
+				abs_dst = xstrdup(tmp_dst);
+		} else if (infer_path(g.gl_pathv[0], &abs_dst)) {
+			err = -1;
+			goto out;
+		}
+		printf("Uploading %s to %s\n", g.gl_pathv[0], abs_dst);
+		err = do_upload(in, out, g.gl_pathv[0], abs_dst, pflag);
+		goto out;
+	}
+
+	/* Multiple matches, dst may be directory or unspecified */
+	if (tmp_dst && !remote_is_dir(in, out, tmp_dst)) {
+		error("Multiple files match, but \"%s\" is not a directory", 
+		    tmp_dst);
+		err = -1;
+		goto out;
+	}
+
+	for(i = 0; g.gl_pathv[i]; i++) {
+		if (infer_path(g.gl_pathv[i], &tmp)) {
+			err = -1;
+			goto out;
+		}
+		if (tmp_dst) {
+			abs_dst = path_append(tmp_dst, tmp);
+			xfree(tmp);
+		} else
+			abs_dst = make_absolute(tmp, pwd);
+
+		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
+		if (do_upload(in, out, g.gl_pathv[i], abs_dst, pflag) == -1)
+			err = -1;
+	}
+
+out:
+	if (abs_dst)
+		xfree(abs_dst);
+	if (tmp_dst)
+		xfree(tmp_dst);
+	return(err);
 }
 
 int
@@ -459,66 +650,17 @@
 	path1 = path2 = NULL;
 	cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
 
+	memset(&g, 0, sizeof(g));
+
 	/* Perform command */
 	switch (cmdnum) {
 	case -1:
 		break;
 	case I_GET:
-		memset(&g, 0, sizeof(g));
-		if (!remote_glob(in, out, path1, 0, NULL, &g)) {
-			if (path2) {
-				/* XXX: target should be directory */
-				error("You cannot specify a target when "
-				    "downloading multiple files");
-				err = -1;
-				break;
-			}
-			for(i = 0; g.gl_pathv[i]; i++) {
-				if (!infer_path(g.gl_pathv[i], &path2)) {
-					printf("Fetching %s\n", g.gl_pathv[i]);
-					if (do_download(in, out, g.gl_pathv[i],
-					    path2, pflag) == -1)
-						err = -1;
-					free(path2);
-					path2 = NULL;
-				} else
-					err = -1;
-			}
-		} else {
-			if (!path2 && infer_path(path1, &path2)) {
-				err = -1;
-				break;
-			}
-			err = do_download(in, out, path1, path2, pflag);
-		}
+		err = process_get(in, out, path1, path2, *pwd, pflag);
 		break;
 	case I_PUT:
-		if (!glob(path1, 0, NULL, &g)) {
-			if (path2) {
-				error("You cannot specify a target when "
-				    "uploading multiple files");
-				err = -1;
-				break;
-			}
-			for(i = 0; g.gl_pathv[i]; i++) {
-				if (!infer_path(g.gl_pathv[i], &path2)) {
-					path2 = make_absolute(path2, *pwd);
-					printf("Uploading %s\n", g.gl_pathv[i]);
-					if (do_upload(in, out, g.gl_pathv[i],
-					    path2, pflag) == -1)
-						err = -1;
-					free(path2);
-					path2 = NULL;
-				} else
-					err = -1;
-			}
-		} else {
-			if (!path2 && infer_path(path1, &path2)) {
-				err = -1;
-				break;
-			}
-			err = do_upload(in, out, path1, path2, pflag);
-		}
+		err = process_put(in, out, path1, path2, *pwd, pflag);
   		break;
   	case I_RENAME:
 		path1 = make_absolute(path1, *pwd);
@@ -561,7 +703,7 @@
 			err = 1;
 			break;
 		}
-		if ((aa = do_stat(in, out, tmp)) == NULL) {
+		if ((aa = do_stat(in, out, tmp, 0)) == NULL) {
 			xfree(tmp);
 			err = 1;
 			break;
@@ -592,7 +734,7 @@
 			break;
 		xfree(path1);
 		path1 = tmp;
-		if ((aa = do_stat(in, out, path1)) == NULL)
+		if ((aa = do_stat(in, out, path1, 0)) == NULL)
 			break;
 		if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 
 		    !S_ISDIR(aa->perm)) {
@@ -640,7 +782,7 @@
 		path1 = make_absolute(path1, *pwd);
 		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
 		for(i = 0; g.gl_pathv[i]; i++) {
-			if (!(aa = do_stat(in, out, g.gl_pathv[i])))
+			if (!(aa = do_stat(in, out, g.gl_pathv[i], 0)))
 				continue;
 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
 				error("Can't get current ownership of "
@@ -657,7 +799,7 @@
 		path1 = make_absolute(path1, *pwd);
 		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
 		for(i = 0; g.gl_pathv[i]; i++) {
-			if (!(aa = do_stat(in, out, g.gl_pathv[i])))
+			if (!(aa = do_stat(in, out, g.gl_pathv[i], 0)))
 				continue;
 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
 				error("Can't get current ownership of "
@@ -693,6 +835,8 @@
 		fatal("%d is not implemented", cmdnum);
 	}
 
+	if (g.gl_pathc)
+		globfree(&g);
 	if (path1)
 		xfree(path1);
 	if (path2)