sftp needs a long time for sending a filelist

Damien Miller djm at mindrot.org
Mon Jul 21 23:16:51 EST 2008


On Mon, 21 Jul 2008, Markus Wünsch wrote:

> 
>    Hello all
>    Im using sftp 1:4.7p1-8ubuntu1.2
>    in a batchjob
>    Ive noticed that sftp needs a long time for sending a filelist.
>    The timespan increases exponential if many files are on the
>    remoteserver.
>    for example "ls -la *.txt" needs 10 seconds for 2000 files
>    but needs 50 seconds for 4000 files.
>    For 150.000 Files i have to wait 15 minutes for example
>    but the Conecction is very fast. I think the sftp-server needs this
>    time to
>    build the list.

There are two performance bugs in the sftp client related to directory
listings:

1. The sftp-client.c do_readdir() makes a server round trip per batch of
   filenames rather than doing pipelined reads like do_upload/do_download

2. The sftp.c code then does a server round trip per filename to refetch
   the attrib (stat) data for each file. This is especially wasteful,
   because this data was already sent via do_readdir()

>    What can i do to get the list in normal time. ?

Fixing this is on my (long) todo list. Here is a patch that I made some
time back to fix #2, but never fully tested (updated to -current). Fixing
#1 would probably be the work of an evening if someone is interested.

-d

Index: sftp-client.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp-client.c,v
retrieving revision 1.86
diff -u -p -r1.86 sftp-client.c
--- sftp-client.c	26 Jun 2008 06:10:09 -0000	1.86
+++ sftp-client.c	21 Jul 2008 13:11:46 -0000
@@ -465,6 +465,7 @@ do_lsreaddir(struct sftp_conn *conn, cha
 		if (count == 0)
 			break;
 		debug3("Received %d SSH2_FXP_NAME responses", count);
+		/* XXX - implement pipelined read here like do_download */
 		for (i = 0; i < count; i++) {
 			char *filename, *longname;
 			Attrib *a;
Index: sftp-glob.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp-glob.c,v
retrieving revision 1.22
diff -u -p -r1.22 sftp-glob.c
--- sftp-glob.c	3 Aug 2006 03:34:42 -0000	1.22
+++ sftp-glob.c	21 Jul 2008 13:11:46 -0000
@@ -19,35 +19,31 @@
 #include <sys/stat.h>
 
 #include <dirent.h>
-#include <glob.h>
 #include <string.h>
 
+#include "xglob.h"
 #include "xmalloc.h"
 #include "sftp.h"
 #include "buffer.h"
 #include "sftp-common.h"
 #include "sftp-client.h"
 
-int remote_glob(struct sftp_conn *, const char *, int,
-    int (*)(const char *, int), glob_t *);
+int remote_xglob(struct sftp_conn *, const char *, int,
+    int (*)(const char *, int), xglob_t *);
 
 struct SFTP_OPENDIR {
 	SFTP_DIRENT **dir;
 	int offset;
 };
 
-static struct {
-	struct sftp_conn *conn;
-} cur;
-
 static void *
-fudge_opendir(const char *path)
+remote_opendir(void *ctx, const char *path)
 {
 	struct SFTP_OPENDIR *r;
+	struct sftp_conn *conn = ctx;
 
 	r = xmalloc(sizeof(*r));
-
-	if (do_readdir(cur.conn, (char *)path, &r->dir)) {
+	if (do_readdir(conn, (char *)path, &r->dir)) {
 		xfree(r);
 		return(NULL);
 	}
@@ -58,33 +54,39 @@ fudge_opendir(const char *path)
 }
 
 static struct dirent *
-fudge_readdir(struct SFTP_OPENDIR *od)
+remote_readdir(void *ctx, void *od_)
 {
+	struct SFTP_OPENDIR *od = od_;
 	static struct dirent ret;
 
 	if (od->dir[od->offset] == NULL)
 		return(NULL);
 
 	memset(&ret, 0, sizeof(ret));
-	strlcpy(ret.d_name, od->dir[od->offset++]->filename,
+	strlcpy(ret.d_name, od->dir[od->offset]->filename,
 	    sizeof(ret.d_name));
+	od->offset++;
 
 	return(&ret);
 }
 
 static void
-fudge_closedir(struct SFTP_OPENDIR *od)
+remote_closedir(void *ctx, void *od_)
 {
+	struct SFTP_OPENDIR *od = od_;
+
 	free_sftp_dirents(od->dir);
 	xfree(od);
 }
 
 static int
-fudge_lstat(const char *path, struct stat *st)
+remote_lstat(void *ctx, const char *path, struct stat *st)
 {
 	Attrib *a;
+	struct sftp_conn *conn = ctx;
 
-	if (!(a = do_lstat(cur.conn, (char *)path, 0)))
+	a = do_lstat(conn, (char *)path, 0);
+	if (a == NULL)
 		return(-1);
 
 	attrib_to_stat(a, st);
@@ -93,11 +95,17 @@ fudge_lstat(const char *path, struct sta
 }
 
 static int
-fudge_stat(const char *path, struct stat *st)
+remote_stat(void *ctx, const char *path, struct stat *st)
 {
 	Attrib *a;
+	struct sftp_conn *conn = ctx;
 
-	if (!(a = do_stat(cur.conn, (char *)path, 0)))
+	/*
+	 * XXX it is possible to skip another roundtrip here in
+	 * some cases, where the Attrib information is in the results of
+	 * a recent opendir()
+	 */
+	if (!(a = do_stat(conn, (char *)path, 0)))
 		return(-1);
 
 	attrib_to_stat(a, st);
@@ -106,17 +114,15 @@ fudge_stat(const char *path, struct stat
 }
 
 int
-remote_glob(struct sftp_conn *conn, const char *pattern, int flags,
-    int (*errfunc)(const char *, int), glob_t *pglob)
+remote_xglob(struct sftp_conn *conn, const char *pattern, int flags,
+    int (*errfunc)(const char *, int), xglob_t *pglob)
 {
-	pglob->gl_opendir = fudge_opendir;
-	pglob->gl_readdir = (struct dirent *(*)(void *))fudge_readdir;
-	pglob->gl_closedir = (void (*)(void *))fudge_closedir;
-	pglob->gl_lstat = fudge_lstat;
-	pglob->gl_stat = fudge_stat;
-
-	memset(&cur, 0, sizeof(cur));
-	cur.conn = conn;
+	pglob->gl_opendir = remote_opendir;
+	pglob->gl_readdir = remote_readdir;
+	pglob->gl_closedir = remote_closedir;
+	pglob->gl_lstat = remote_lstat;
+	pglob->gl_stat = remote_stat;
+	pglob->gl_user_ctx = conn;
 
-	return(glob(pattern, flags | GLOB_ALTDIRFUNC, errfunc, pglob));
+	return(xglob(pattern, flags | XGLOB_ALTDIRFUNC, errfunc, pglob));
 }
Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.103
diff -u -p -r1.103 sftp.c
--- sftp.c	13 Jul 2008 22:16:03 -0000	1.103
+++ sftp.c	21 Jul 2008 13:11:46 -0000
@@ -25,7 +25,6 @@
 
 #include <ctype.h>
 #include <errno.h>
-#include <glob.h>
 #include <histedit.h>
 #include <paths.h>
 #include <signal.h>
@@ -41,6 +40,7 @@
 #include "pathnames.h"
 #include "misc.h"
 
+#include "xglob.h"
 #include "sftp.h"
 #include "buffer.h"
 #include "sftp-common.h"
@@ -70,8 +70,8 @@ volatile sig_atomic_t interrupted = 0;
 /* I wish qsort() took a separate ctx for the comparison function...*/
 int sort_flag;
 
-int remote_glob(struct sftp_conn *, const char *, int,
-    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
+int remote_xglob(struct sftp_conn *, const char *, int,
+    int (*)(const char *, int), xglob_t *); /* proto for sftp-glob.c */
 
 /* Separators for interactive commands */
 #define WHITESPACE " \t\r\n"
@@ -473,7 +473,7 @@ process_get(struct sftp_conn *conn, char
 	char *abs_src = NULL;
 	char *abs_dst = NULL;
 	char *tmp;
-	glob_t g;
+	xglob_t g;
 	int err = 0;
 	int i;
 
@@ -482,7 +482,7 @@ process_get(struct sftp_conn *conn, char
 
 	memset(&g, 0, sizeof(g));
 	debug3("Looking up %s", abs_src);
-	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
+	if (remote_xglob(conn, abs_src, 0, NULL, &g)) {
 		error("File \"%s\" not found.", abs_src);
 		err = -1;
 		goto out;
@@ -496,8 +496,8 @@ process_get(struct sftp_conn *conn, char
 		goto out;
 	}
 
-	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-		if (infer_path(g.gl_pathv[i], &tmp)) {
+	for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+		if (infer_path(g.gl_matchv[i].gl_path, &tmp)) {
 			err = -1;
 			goto out;
 		}
@@ -506,7 +506,7 @@ process_get(struct sftp_conn *conn, char
 			/* If directory specified, append filename */
 			xfree(tmp);
 			if (is_dir(dst)) {
-				if (infer_path(g.gl_pathv[0], &tmp)) {
+				if (infer_path(g.gl_matchv[0].gl_path, &tmp)) {
 					err = 1;
 					goto out;
 				}
@@ -520,8 +520,9 @@ process_get(struct sftp_conn *conn, char
 		} else
 			abs_dst = tmp;
 
-		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
+		printf("Fetching %s to %s\n", g.gl_matchv[i].gl_path, abs_dst);
+		if (do_download(conn, g.gl_matchv[i].gl_path, abs_dst,
+		    pflag) == -1)
 			err = -1;
 		xfree(abs_dst);
 		abs_dst = NULL;
@@ -529,7 +530,7 @@ process_get(struct sftp_conn *conn, char
 
 out:
 	xfree(abs_src);
-	globfree(&g);
+	xglobfree(&g);
 	return(err);
 }
 
@@ -539,7 +540,7 @@ process_put(struct sftp_conn *conn, char
 	char *tmp_dst = NULL;
 	char *abs_dst = NULL;
 	char *tmp;
-	glob_t g;
+	xglob_t g;
 	int err = 0;
 	int i;
 	struct stat sb;
@@ -551,7 +552,7 @@ process_put(struct sftp_conn *conn, char
 
 	memset(&g, 0, sizeof(g));
 	debug3("Looking up %s", src);
-	if (glob(src, GLOB_NOCHECK, NULL, &g)) {
+	if (xglob(src, XGLOB_NOCHECK, NULL, &g)) {
 		error("File \"%s\" not found.", src);
 		err = -1;
 		goto out;
@@ -565,19 +566,20 @@ process_put(struct sftp_conn *conn, char
 		goto out;
 	}
 
-	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-		if (stat(g.gl_pathv[i], &sb) == -1) {
+	for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+		if (stat(g.gl_matchv[i].gl_path, &sb) == -1) {
 			err = -1;
-			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
+			error("stat %s: %s", g.gl_matchv[i].gl_path,
+			    strerror(errno));
 			continue;
 		}
 
 		if (!S_ISREG(sb.st_mode)) {
 			error("skipping non-regular file %s",
-			    g.gl_pathv[i]);
+			    g.gl_matchv[i].gl_path);
 			continue;
 		}
-		if (infer_path(g.gl_pathv[i], &tmp)) {
+		if (infer_path(g.gl_matchv[i].gl_path, &tmp)) {
 			err = -1;
 			goto out;
 		}
@@ -585,7 +587,7 @@ process_put(struct sftp_conn *conn, char
 		if (g.gl_matchc == 1 && tmp_dst) {
 			/* If directory specified, append filename */
 			if (remote_is_dir(conn, tmp_dst)) {
-				if (infer_path(g.gl_pathv[0], &tmp)) {
+				if (infer_path(g.gl_matchv[0].gl_path, &tmp)) {
 					err = 1;
 					goto out;
 				}
@@ -600,8 +602,9 @@ process_put(struct sftp_conn *conn, char
 		} else
 			abs_dst = make_absolute(tmp, pwd);
 
-		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
+		printf("Uploading %s to %s\n", g.gl_matchv[i].gl_path, abs_dst);
+		if (do_upload(conn, g.gl_matchv[i].gl_path, abs_dst,
+		    pflag) == -1)
 			err = -1;
 	}
 
@@ -610,7 +613,7 @@ out:
 		xfree(abs_dst);
 	if (tmp_dst)
 		xfree(tmp_dst);
-	globfree(&g);
+	xglobfree(&g);
 	return(err);
 }
 
@@ -716,21 +719,50 @@ do_ls_dir(struct sftp_conn *conn, char *
 	return (0);
 }
 
+static int
+xglobentry_comp(const void *aa, const void *bb)
+{
+	struct xglob_entry *a = (struct xglob_entry *)aa;
+	struct xglob_entry *b = (struct xglob_entry *)bb;
+	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
+
+#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
+	if (sort_flag & LS_NAME_SORT)
+		return (rmul * strcmp(a->gl_path, b->gl_path));
+	else if (sort_flag & LS_TIME_SORT) {
+		if (timespeccmp(&a->gl_stat.st_mtimespec, 
+		    &b->gl_stat.st_mtimespec, >))
+			return rmul;
+		if (timespeccmp(&a->gl_stat.st_mtimespec, 
+		    &b->gl_stat.st_mtimespec, <))
+			return -rmul;
+		return 0;
+	} else if (sort_flag & LS_SIZE_SORT)
+		return (rmul * NCMP(a->gl_stat.st_size, b->gl_stat.st_size));
+
+	fatal("Unknown ls sort type");
+}
+
 /* sftp ls.1 replacement which handles path globs */
 static int
 do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
     int lflag)
 {
-	glob_t g;
+	xglob_t g;
 	u_int i, c = 1, colspace = 0, columns = 1;
 	Attrib *a = NULL;
+	int err;
 
 	memset(&g, 0, sizeof(g));
 
-	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
+	g.gl_cmp = xglobentry_comp;
+	sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
+
+	if (remote_xglob(conn, path,
+	    XGLOB_MARK|XGLOB_NOCHECK|XGLOB_BRACE|XGLOB_ALTCMPFUNC,
 	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
 		if (g.gl_pathc)
-			globfree(&g);
+			xglobfree(&g);
 		error("Can't ls: \"%s\" not found", path);
 		return (-1);
 	}
@@ -739,20 +771,19 @@ do_globbed_ls(struct sftp_conn *conn, ch
 		goto out;
 
 	/*
-	 * If the glob returns a single match and it is a directory,
+	 * If the xglob returns a single match and it is a directory,
 	 * then just list its contents.
 	 */
 	if (g.gl_matchc == 1) {
-		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
-			globfree(&g);
+		if ((a = do_lstat(conn, g.gl_matchv[0].gl_path, 1)) == NULL) {
+			xglobfree(&g);
 			return (-1);
 		}
 		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
 		    S_ISDIR(a->perm)) {
-			int err;
-
-			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
-			globfree(&g);
+			err = do_ls_dir(conn, g.gl_matchv[0].gl_path,
+			    strip_path, lflag);
+			xglobfree(&g);
 			return (err);
 		}
 	}
@@ -762,8 +793,8 @@ do_globbed_ls(struct sftp_conn *conn, ch
 		struct winsize ws;
 
 		/* Count entries for sort and find longest filename */
-		for (i = 0; g.gl_pathv[i]; i++)
-			m = MAX(m, strlen(g.gl_pathv[i]));
+		for (i = 0; g.gl_matchv[i].gl_path; i++)
+			m = MAX(m, strlen(g.gl_matchv[i].gl_path));
 
 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
 			width = ws.ws_col;
@@ -773,28 +804,12 @@ do_globbed_ls(struct sftp_conn *conn, ch
 		colspace = width / columns;
 	}
 
-	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
-		char *fname;
-
-		fname = path_strip(g.gl_pathv[i], strip_path);
+	for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+		char *fname, *lname;
 
+		fname = path_strip(g.gl_matchv[i].gl_path, strip_path);
 		if (lflag & LS_LONG_VIEW) {
-			char *lname;
-			struct stat sb;
-
-			/*
-			 * XXX: this is slow - 1 roundtrip per path
-			 * A solution to this is to fork glob() and
-			 * build a sftp specific version which keeps the
-			 * attribs (which currently get thrown away)
-			 * that the server returns as well as the filenames.
-			 */
-			memset(&sb, 0, sizeof(sb));
-			if (a == NULL)
-				a = do_lstat(conn, g.gl_pathv[i], 1);
-			if (a != NULL)
-				attrib_to_stat(a, &sb);
-			lname = ls_file(fname, &sb, 1);
+			lname = ls_file(fname, &g.gl_matchv[i].gl_stat, 1);
 			printf("%s\n", lname);
 			xfree(lname);
 		} else {
@@ -813,7 +828,7 @@ do_globbed_ls(struct sftp_conn *conn, ch
 
  out:
 	if (g.gl_pathc)
-		globfree(&g);
+		xglobfree(&g);
 
 	return (0);
 }
@@ -1218,7 +1233,7 @@ parse_dispatch_command(struct sftp_conn 
 	Attrib a, *aa;
 	char path_buf[MAXPATHLEN];
 	int err = 0;
-	glob_t g;
+	xglob_t g;
 
 	path1 = path2 = NULL;
 	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
@@ -1255,10 +1270,10 @@ parse_dispatch_command(struct sftp_conn 
 		break;
 	case I_RM:
 		path1 = make_absolute(path1, *pwd);
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-			printf("Removing %s\n", g.gl_pathv[i]);
-			err = do_rm(conn, g.gl_pathv[i]);
+		remote_xglob(conn, path1, XGLOB_NOCHECK, NULL, &g);
+		for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+			printf("Removing %s\n", g.gl_matchv[i].gl_path);
+			err = do_rm(conn, g.gl_matchv[i].gl_path);
 			if (err != 0 && err_abort)
 				break;
 		}
@@ -1351,10 +1366,10 @@ parse_dispatch_command(struct sftp_conn 
 		attrib_clear(&a);
 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
 		a.perm = n_arg;
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-			printf("Changing mode on %s\n", g.gl_pathv[i]);
-			err = do_setstat(conn, g.gl_pathv[i], &a);
+		remote_xglob(conn, path1, XGLOB_NOCHECK, NULL, &g);
+		for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+			printf("Changing mode on %s\n", g.gl_matchv[i].gl_path);
+			err = do_setstat(conn, g.gl_matchv[i].gl_path, &a);
 			if (err != 0 && err_abort)
 				break;
 		}
@@ -1362,9 +1377,9 @@ parse_dispatch_command(struct sftp_conn 
 	case I_CHOWN:
 	case I_CHGRP:
 		path1 = make_absolute(path1, *pwd);
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
+		remote_xglob(conn, path1, XGLOB_NOCHECK, NULL, &g);
+		for (i = 0; g.gl_matchv[i].gl_path && !interrupted; i++) {
+			if (!(aa = do_stat(conn, g.gl_matchv[i].gl_path, 0))) {
 				if (err != 0 && err_abort)
 					break;
 				else
@@ -1372,7 +1387,8 @@ parse_dispatch_command(struct sftp_conn 
 			}
 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
 				error("Can't get current ownership of "
-				    "remote file \"%s\"", g.gl_pathv[i]);
+				    "remote file \"%s\"", 
+				    g.gl_matchv[i].gl_path);
 				if (err != 0 && err_abort)
 					break;
 				else
@@ -1380,13 +1396,15 @@ parse_dispatch_command(struct sftp_conn 
 			}
 			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
 			if (cmdnum == I_CHOWN) {
-				printf("Changing owner on %s\n", g.gl_pathv[i]);
+				printf("Changing owner on %s\n",
+				    g.gl_matchv[i].gl_path);
 				aa->uid = n_arg;
 			} else {
-				printf("Changing group on %s\n", g.gl_pathv[i]);
+				printf("Changing group on %s\n",
+				    g.gl_matchv[i].gl_path);
 				aa->gid = n_arg;
 			}
-			err = do_setstat(conn, g.gl_pathv[i], aa);
+			err = do_setstat(conn, g.gl_matchv[i].gl_path, aa);
 			if (err != 0 && err_abort)
 				break;
 		}
@@ -1423,7 +1441,7 @@ parse_dispatch_command(struct sftp_conn 
 	}
 
 	if (g.gl_pathc)
-		globfree(&g);
+		xglobfree(&g);
 	if (path1)
 		xfree(path1);
 	if (path2)
Index: xglob.c
===================================================================
RCS file: xglob.c
diff -N xglob.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ xglob.c	21 Jul 2008 13:11:46 -0000
@@ -0,0 +1,835 @@
+/*	$OpenBSD$ */
+/*
+ * Copyright 2007 Damien Miller <djm at mindrot.org>
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Modified version of BSD glob(3):
+ *  - ALTDIRFUNCs gain context arguments. sftp uses this to pass in a
+ *    structure representing the connection.
+ *  - Instead of returning just a vector of paths, xglob returns a struct
+ *    that contains the path and its stuct stat. This is useful for sftp, as
+ *    it saves a round-trip later.
+ *  - Added ALTCMPFUNC for caller-specified comparison functions to be passed
+ *    to the final qsort(3).
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "xglob.h"
+
+#define	DOLLAR		'$'
+#define	DOT		'.'
+#define	EOS		'\0'
+#define	LBRACKET	'['
+#define	NOT		'!'
+#define	QUESTION	'?'
+#define	QUOTE		'\\'
+#define	RANGE		'-'
+#define	RBRACKET	']'
+#define	SEP		'/'
+#define	STAR		'*'
+#define	TILDE		'~'
+#define	UNDERSCORE	'_'
+#define	LBRACE		'{'
+#define	RBRACE		'}'
+#define	SLASH		'/'
+#define	COMMA		','
+
+#ifndef DEBUG
+
+#define	M_QUOTE		0x8000
+#define	M_PROTECT	0x4000
+#define	M_MASK		0xffff
+#define	M_ASCII		0x00ff
+
+typedef u_short Char;
+
+#else
+
+#define	M_QUOTE		0x80
+#define	M_PROTECT	0x40
+#define	M_MASK		0xff
+#define	M_ASCII		0x7f
+
+typedef char Char;
+
+#endif
+
+
+#define	CHAR(c)		((Char)((c)&M_ASCII))
+#define	META(c)		((Char)((c)|M_QUOTE))
+#define	M_ALL		META('*')
+#define	M_END		META(']')
+#define	M_NOT		META('!')
+#define	M_ONE		META('?')
+#define	M_RNG		META('-')
+#define	M_SET		META('[')
+#define	ismeta(c)	(((c)&M_QUOTE) != 0)
+
+
+static int	 path_compare(const void *, const void *);
+static int	 g_Ctoc(const Char *, char *, u_int);
+static int	 g_lstat(Char *, struct stat *, xglob_t *);
+static DIR	*g_opendir(Char *, xglob_t *);
+static Char	*g_strchr(Char *, int);
+static int	 g_stat(Char *, struct stat *, xglob_t *);
+static int	 xglob0(const Char *, xglob_t *);
+static int	 xglob1(Char *, Char *, xglob_t *, size_t *);
+static int	 xglob2(Char *, Char *, Char *, Char *, Char *, Char *,
+		    xglob_t *, size_t *);
+static int	 xglob3(Char *, Char *, Char *, Char *, Char *,
+		    Char *, Char *, xglob_t *, size_t *);
+static int	 xglobextend(const Char *, struct stat *, xglob_t *, size_t *);
+static const Char *
+		 xglobtilde(const Char *, Char *, size_t, xglob_t *);
+static int	 xglobexp1(const Char *, xglob_t *);
+static int	 xglobexp2(const Char *, const Char *, xglob_t *, int *);
+static int	 match(Char *, Char *, Char *);
+#ifdef DEBUG
+static void	 qprintf(const char *, Char *);
+#endif
+
+int
+xglob(const char *pattern, int flags, int (*errfunc)(const char *, int),
+    xglob_t *pglob)
+{
+	const u_char *patnext;
+	int c;
+	Char *bufnext, *bufend, patbuf[MAXPATHLEN];
+
+	patnext = (u_char *) pattern;
+	if (!(flags & XGLOB_APPEND)) {
+		pglob->gl_pathc = 0;
+		pglob->gl_matchv = NULL;
+		if (!(flags & XGLOB_DOOFFS))
+			pglob->gl_offs = 0;
+	}
+	pglob->gl_flags = flags & ~XGLOB_MAGCHAR;
+	pglob->gl_errfunc = errfunc;
+	pglob->gl_matchc = 0;
+
+	bufnext = patbuf;
+	bufend = bufnext + MAXPATHLEN - 1;
+	if (flags & XGLOB_NOESCAPE)
+		while (bufnext < bufend && (c = *patnext++) != EOS)
+			*bufnext++ = c;
+	else {
+		/* Protect the quoted characters. */
+		while (bufnext < bufend && (c = *patnext++) != EOS)
+			if (c == QUOTE) {
+				if ((c = *patnext++) == EOS) {
+					c = QUOTE;
+					--patnext;
+				}
+				*bufnext++ = c | M_PROTECT;
+			} else
+				*bufnext++ = c;
+	}
+	*bufnext = EOS;
+
+	if (flags & XGLOB_BRACE)
+		return xglobexp1(patbuf, pglob);
+	else
+		return xglob0(patbuf, pglob);
+}
+
+/*
+ * Expand recursively a glob {} pattern. When there is no more expansion
+ * invoke the standard globbing routine to glob the rest of the magic
+ * characters
+ */
+static int
+xglobexp1(const Char *pattern, xglob_t *pglob)
+{
+	const Char* ptr = pattern;
+	int rv;
+
+	/* Protect a single {}, for find(1), like csh */
+	if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
+		return xglob0(pattern, pglob);
+
+	while ((ptr = (const Char *) g_strchr((Char *) ptr, LBRACE)) != NULL)
+		if (!xglobexp2(ptr, pattern, pglob, &rv))
+			return rv;
+
+	return xglob0(pattern, pglob);
+}
+
+
+/*
+ * Recursive brace globbing helper. Tries to expand a single brace.
+ * If it succeeds then it invokes xglobexp1 with the new pattern.
+ * If it fails then it tries to glob the rest of the pattern and returns.
+ */
+static int
+xglobexp2(const Char *ptr, const Char *pattern, xglob_t *pglob, int *rv)
+{
+	int     i;
+	Char   *lm, *ls;
+	const Char *pe, *pm, *pl;
+	Char    patbuf[MAXPATHLEN];
+
+	/* copy part up to the brace */
+	for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
+		;
+	*lm = EOS;
+	ls = lm;
+
+	/* Find the balanced brace */
+	for (i = 0, pe = ++ptr; *pe; pe++)
+		if (*pe == LBRACKET) {
+			/* Ignore everything between [] */
+			for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
+				;
+			if (*pe == EOS) {
+				/*
+				 * We could not find a matching RBRACKET.
+				 * Ignore and just look for RBRACE
+				 */
+				pe = pm;
+			}
+		} else if (*pe == LBRACE)
+			i++;
+		else if (*pe == RBRACE) {
+			if (i == 0)
+				break;
+			i--;
+		}
+
+	/* Non matching braces; just glob the pattern */
+	if (i != 0 || *pe == EOS) {
+		*rv = xglob0(patbuf, pglob);
+		return 0;
+	}
+
+	for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
+		switch (*pm) {
+		case LBRACKET:
+			/* Ignore everything between [] */
+			for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++)
+				;
+			if (*pm == EOS) {
+				/*
+				 * We could not find a matching RBRACKET.
+				 * Ignore and just look for RBRACE
+				 */
+				pm = pl;
+			}
+			break;
+
+		case LBRACE:
+			i++;
+			break;
+
+		case RBRACE:
+			if (i) {
+				i--;
+				break;
+			}
+			/* FALLTHROUGH */
+		case COMMA:
+			if (i && *pm == COMMA)
+				break;
+			else {
+				/* Append the current string */
+				for (lm = ls; (pl < pm); *lm++ = *pl++)
+					;
+
+				/*
+				 * Append the rest of the pattern after the
+				 * closing brace
+				 */
+				for (pl = pe + 1; (*lm++ = *pl++) != EOS; )
+					;
+
+				/* Expand the current pattern */
+#ifdef DEBUG
+				qprintf("xglobexp2:", patbuf);
+#endif
+				*rv = xglobexp1(patbuf, pglob);
+
+				/* move after the comma, to the next string */
+				pl = pm + 1;
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+	*rv = 0;
+	return 0;
+}
+
+
+
+/*
+ * expand tilde from the passwd file.
+ */
+static const Char *
+xglobtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, xglob_t *pglob)
+{
+	struct passwd *pwd;
+	char *h;
+	const Char *p;
+	Char *b, *eb;
+
+	if (*pattern != TILDE || !(pglob->gl_flags & XGLOB_TILDE))
+		return pattern;
+
+	/* Copy up to the end of the string or / */
+	eb = &patbuf[patbuf_len - 1];
+	for (p = pattern + 1, h = (char *) patbuf;
+	    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
+		;
+
+	*h = EOS;
+
+#if 0
+	if (h == (char *)eb)
+		return what;
+#endif
+
+	if (((char *) patbuf)[0] == EOS) {
+		/*
+		 * handle a plain ~ or ~/ by expanding $HOME
+		 * first and then trying the password file
+		 */
+		if (issetugid() != 0 || (h = getenv("HOME")) == NULL) {
+			if ((pwd = getpwuid(getuid())) == NULL)
+				return pattern;
+			else
+				h = pwd->pw_dir;
+		}
+	} else {
+		/*
+		 * Expand a ~user
+		 */
+		if ((pwd = getpwnam((char*) patbuf)) == NULL)
+			return pattern;
+		else
+			h = pwd->pw_dir;
+	}
+
+	/* Copy the home directory */
+	for (b = patbuf; b < eb && *h; *b++ = *h++)
+		;
+
+	/* Append the rest of the pattern */
+	while (b < eb && (*b++ = *p++) != EOS)
+		;
+	*b = EOS;
+
+	return patbuf;
+}
+
+
+/*
+ * The main xglob() routine: compiles the pattern (optionally processing
+ * quotes), calls xglob1() to do the real pattern matching, and finally
+ * sorts the list (unless unsorted operation is requested).  Returns 0
+ * if things went well, nonzero if errors occurred.  It is not an error
+ * to find no matches.
+ */
+static int
+xglob0(const Char *pattern, xglob_t *pglob)
+{
+	const Char *qpatnext;
+	int c, err, oldpathc;
+	Char *bufnext, patbuf[MAXPATHLEN];
+	size_t limit = 0;
+
+	qpatnext = xglobtilde(pattern, patbuf, MAXPATHLEN, pglob);
+	oldpathc = pglob->gl_pathc;
+	bufnext = patbuf;
+
+	/* We don't need to check for buffer overflow any more. */
+	while ((c = *qpatnext++) != EOS) {
+		switch (c) {
+		case LBRACKET:
+			c = *qpatnext;
+			if (c == NOT)
+				++qpatnext;
+			if (*qpatnext == EOS ||
+			    g_strchr((Char *) qpatnext+1, RBRACKET) == NULL) {
+				*bufnext++ = LBRACKET;
+				if (c == NOT)
+					--qpatnext;
+				break;
+			}
+			*bufnext++ = M_SET;
+			if (c == NOT)
+				*bufnext++ = M_NOT;
+			c = *qpatnext++;
+			do {
+				*bufnext++ = CHAR(c);
+				if (*qpatnext == RANGE &&
+				    (c = qpatnext[1]) != RBRACKET) {
+					*bufnext++ = M_RNG;
+					*bufnext++ = CHAR(c);
+					qpatnext += 2;
+				}
+			} while ((c = *qpatnext++) != RBRACKET);
+			pglob->gl_flags |= XGLOB_MAGCHAR;
+			*bufnext++ = M_END;
+			break;
+		case QUESTION:
+			pglob->gl_flags |= XGLOB_MAGCHAR;
+			*bufnext++ = M_ONE;
+			break;
+		case STAR:
+			pglob->gl_flags |= XGLOB_MAGCHAR;
+			/* collapse adjacent stars to one,
+			 * to avoid exponential behavior
+			 */
+			if (bufnext == patbuf || bufnext[-1] != M_ALL)
+				*bufnext++ = M_ALL;
+			break;
+		default:
+			*bufnext++ = CHAR(c);
+			break;
+		}
+	}
+	*bufnext = EOS;
+#ifdef DEBUG
+	qprintf("xglob0:", patbuf);
+#endif
+
+	if ((err = xglob1(patbuf, patbuf+MAXPATHLEN-1, pglob, &limit)) != 0)
+		return(err);
+
+	/*
+	 * If there was no match we are going to append the pattern
+	 * if XGLOB_NOCHECK was specified or if XGLOB_NOMAGIC was specified
+	 * and the pattern did not contain any magic characters
+	 * XGLOB_NOMAGIC is there just for compatibility with csh.
+	 */
+	if (pglob->gl_pathc == oldpathc) {
+		if ((pglob->gl_flags & XGLOB_NOCHECK) ||
+		    ((pglob->gl_flags & XGLOB_NOMAGIC) &&
+		    !(pglob->gl_flags & XGLOB_MAGCHAR)))
+			return(xglobextend(pattern, NULL, pglob, &limit));
+		else
+			return(XGLOB_NOMATCH);
+	}
+	if (!(pglob->gl_flags & XGLOB_NOSORT))
+		qsort(pglob->gl_matchv + pglob->gl_offs + oldpathc,
+		    pglob->gl_pathc - oldpathc, sizeof(*pglob->gl_matchv),
+		    (pglob->gl_flags & XGLOB_ALTCMPFUNC) ?
+		     pglob->gl_cmp : path_compare);
+	return(0);
+}
+
+static int
+path_compare(const void *a, const void *b)
+{
+	struct xglob_entry *aa = (struct xglob_entry *)a;
+	struct xglob_entry *bb = (struct xglob_entry *)b;
+
+	return(strcmp(aa->gl_path, bb->gl_path));
+}
+
+static int
+xglob1(Char *pattern, Char *pattern_last, xglob_t *pglob, size_t *limitp)
+{
+	Char pathbuf[MAXPATHLEN];
+
+	/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
+	if (*pattern == EOS)
+		return(0);
+	return(xglob2(pathbuf, pathbuf+MAXPATHLEN-1,
+	    pathbuf, pathbuf+MAXPATHLEN-1,
+	    pattern, pattern_last, pglob, limitp));
+}
+
+/*
+ * The functions xglob2 and xglob3 are mutually recursive; there is one level
+ * of recursion for each segment in the pattern that contains one or more
+ * meta characters.
+ */
+static int
+xglob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+    Char *pattern, Char *pattern_last, xglob_t *pglob, size_t *limitp)
+{
+	struct stat sb;
+	Char *p, *q;
+	int anymeta;
+
+	/*
+	 * Loop over pattern segments until end of pattern or until
+	 * segment with meta character found.
+	 */
+	for (anymeta = 0;;) {
+		if (*pattern == EOS) {		/* End of pattern? */
+			*pathend = EOS;
+			if (g_lstat(pathbuf, &sb, pglob))
+				return(0);
+
+			if (((pglob->gl_flags & XGLOB_MARK) &&
+			    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ||
+			    (S_ISLNK(sb.st_mode) &&
+			    (g_stat(pathbuf, &sb, pglob) == 0) &&
+			    S_ISDIR(sb.st_mode)))) {
+				if (pathend+1 > pathend_last)
+					return (1);
+				*pathend++ = SEP;
+				*pathend = EOS;
+			}
+			++pglob->gl_matchc;
+			return(xglobextend(pathbuf, &sb, pglob, limitp));
+		}
+
+		/* Find end of next segment, copy tentatively to pathend. */
+		q = pathend;
+		p = pattern;
+		while (*p != EOS && *p != SEP) {
+			if (ismeta(*p))
+				anymeta = 1;
+			if (q+1 > pathend_last)
+				return (1);
+			*q++ = *p++;
+		}
+
+		if (!anymeta) {		/* No expansion, do next segment. */
+			pathend = q;
+			pattern = p;
+			while (*pattern == SEP) {
+				if (pathend+1 > pathend_last)
+					return (1);
+				*pathend++ = *pattern++;
+			}
+		} else
+			/* Need expansion, recurse. */
+			return(xglob3(pathbuf, pathbuf_last, pathend,
+			    pathend_last, pattern, p, pattern_last,
+			    pglob, limitp));
+	}
+	/* NOTREACHED */
+}
+
+static int
+xglob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+    Char *pattern, Char *restpattern, Char *restpattern_last, xglob_t *pglob,
+    size_t *limitp)
+{
+	struct dirent *dp;
+	DIR *dirp;
+	int err;
+	char buf[MAXPATHLEN];
+
+	if (pathend > pathend_last)
+		return (1);
+	*pathend = EOS;
+	errno = 0;
+
+	if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
+		/* TODO: don't call for ENOENT or ENOTDIR? */
+		if (pglob->gl_errfunc) {
+			if (g_Ctoc(pathbuf, buf, sizeof(buf)))
+				return(XGLOB_ABORTED);
+			if (pglob->gl_errfunc(buf, errno) ||
+			    pglob->gl_flags & XGLOB_ERR)
+				return(XGLOB_ABORTED);
+		}
+		return(0);
+	}
+
+	err = 0;
+
+	/* Search directory for matching names. */
+	while (1) {
+		if (pglob->gl_flags & XGLOB_ALTDIRFUNC)
+			dp = pglob->gl_readdir(pglob->gl_user_ctx, dirp);
+		else
+			dp = readdir(dirp);
+
+		if (dp == NULL)
+			break;
+
+		u_char *sc;
+		Char *dc;
+
+		/* Initial DOT must be matched literally. */
+		if (dp->d_name[0] == DOT && *pattern != DOT)
+			continue;
+		dc = pathend;
+		sc = (u_char *) dp->d_name;
+		while (dc < pathend_last && (*dc++ = *sc++) != EOS)
+			;
+		if (dc >= pathend_last) {
+			*dc = EOS;
+			err = 1;
+			break;
+		}
+
+		if (!match(pathend, pattern, restpattern)) {
+			*pathend = EOS;
+			continue;
+		}
+		err = xglob2(pathbuf, pathbuf_last, --dc, pathend_last,
+		    restpattern, restpattern_last, pglob, limitp);
+		if (err)
+			break;
+	}
+
+	if (pglob->gl_flags & XGLOB_ALTDIRFUNC)
+		(*pglob->gl_closedir)(pglob->gl_user_ctx, dirp);
+	else
+		closedir(dirp);
+	return(err);
+}
+
+
+/*
+ * Extend the gl_matchv member of a xglob_t structure to accommodate a new item,
+ * add the new item, and update gl_pathc.
+ *
+ * This assumes the BSD realloc, which only copies the block when its size
+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
+ * behavior.
+ *
+ * Return 0 if new item added, error code if memory couldn't be allocated.
+ *
+ * Invariant of the xglob_t structure:
+ *	Either gl_pathc is zero and gl_matchv is NULL; or gl_pathc > 0 and
+ *	gl_matchv points to (gl_offs + gl_pathc + 1) items.
+ */
+static int
+xglobextend(const Char *path, struct stat *st, xglob_t *pglob, size_t *limitp)
+{
+	struct xglob_entry *matchv;
+	size_t newsize, len, i;
+	char *copy;
+	const Char *p;
+
+	newsize = sizeof(*matchv) * (2 + pglob->gl_pathc + pglob->gl_offs);
+	matchv = realloc(pglob->gl_matchv, newsize);
+	if (matchv == NULL) {
+		if (pglob->gl_matchv) {
+			free(pglob->gl_matchv);
+			pglob->gl_matchv = NULL;
+		}
+		return(XGLOB_NOSPACE);
+	}
+
+	if (pglob->gl_matchv == NULL && pglob->gl_offs > 0)
+		memset(matchv, '\0', newsize);
+
+	pglob->gl_matchv = matchv;
+
+	for (p = path; *p++;)
+		;
+	len = (size_t)(p - path);
+	*limitp += len;
+	copy = malloc(len);
+	if (copy != NULL) {
+		if (g_Ctoc(path, copy, len)) {
+			free(copy);
+			copy = NULL;
+		}
+		i = pglob->gl_offs + pglob->gl_pathc++;
+		matchv[i].gl_path = copy;
+		if (st != NULL)
+			matchv[i].gl_stat = *st;
+	}
+	memset(&matchv[pglob->gl_offs + pglob->gl_pathc], '\0',
+	    sizeof(*matchv));
+
+	if ((pglob->gl_flags & XGLOB_LIMIT) &&
+	    newsize + *limitp >= ARG_MAX) {
+		errno = 0;
+		return(XGLOB_NOSPACE);
+	}
+
+	return(copy == NULL ? XGLOB_NOSPACE : 0);
+}
+
+
+/*
+ * pattern matching function for filenames.  Each occurrence of the *
+ * pattern causes a recursion level.
+ */
+static int
+match(Char *name, Char *pat, Char *patend)
+{
+	int ok, negate_range;
+	Char c, k;
+
+	while (pat < patend) {
+		c = *pat++;
+		switch (c & M_MASK) {
+		case M_ALL:
+			if (pat == patend)
+				return(1);
+			do {
+			    if (match(name, pat, patend))
+				    return(1);
+			} while (*name++ != EOS);
+			return(0);
+		case M_ONE:
+			if (*name++ == EOS)
+				return(0);
+			break;
+		case M_SET:
+			ok = 0;
+			if ((k = *name++) == EOS)
+				return(0);
+			if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
+				++pat;
+			while (((c = *pat++) & M_MASK) != M_END)
+				if ((*pat & M_MASK) == M_RNG) {
+					if (c <= k && k <= pat[1])
+						ok = 1;
+					pat += 2;
+				} else if (c == k)
+					ok = 1;
+			if (ok == negate_range)
+				return(0);
+			break;
+		default:
+			if (*name++ != c)
+				return(0);
+			break;
+		}
+	}
+	return(*name == EOS);
+}
+
+/* Free allocated data belonging to a xglob_t structure. */
+void
+xglobfree(xglob_t *pglob)
+{
+	int i;
+	struct xglob_entry *pp;
+
+	if (pglob->gl_matchv != NULL) {
+		pp = pglob->gl_matchv + pglob->gl_offs;
+		for (i = pglob->gl_pathc; i--; ++pp)
+			if (pp->gl_path)
+				free(pp->gl_path);
+		free(pglob->gl_matchv);
+		pglob->gl_matchv = NULL;
+	}
+}
+
+static DIR *
+g_opendir(Char *str, xglob_t *pglob)
+{
+	char buf[MAXPATHLEN];
+
+	if (!*str)
+		strlcpy(buf, ".", sizeof buf);
+	else {
+		if (g_Ctoc(str, buf, sizeof(buf)))
+			return(NULL);
+	}
+
+	if (pglob->gl_flags & XGLOB_ALTDIRFUNC)
+		return((*pglob->gl_opendir)(pglob->gl_user_ctx, buf));
+
+	return(opendir(buf));
+}
+
+static int
+g_lstat(Char *fn, struct stat *sb, xglob_t *pglob)
+{
+	char buf[MAXPATHLEN];
+
+	if (g_Ctoc(fn, buf, sizeof(buf)))
+		return(-1);
+	if (pglob->gl_flags & XGLOB_ALTDIRFUNC)
+		return((*pglob->gl_lstat)(pglob->gl_user_ctx, buf, sb));
+	return(lstat(buf, sb));
+}
+
+static int
+g_stat(Char *fn, struct stat *sb, xglob_t *pglob)
+{
+	char buf[MAXPATHLEN];
+
+	if (g_Ctoc(fn, buf, sizeof(buf)))
+		return(-1);
+	if (pglob->gl_flags & XGLOB_ALTDIRFUNC)
+		return((*pglob->gl_stat)(pglob->gl_user_ctx, buf, sb));
+	return(stat(buf, sb));
+}
+
+static Char *
+g_strchr(Char *str, int ch)
+{
+	do {
+		if (*str == ch)
+			return (str);
+	} while (*str++);
+	return (NULL);
+}
+
+static int
+g_Ctoc(const Char *str, char *buf, u_int len)
+{
+
+	while (len--) {
+		if ((*buf++ = *str++) == EOS)
+			return (0);
+	}
+	return (1);
+}
+
+#ifdef DEBUG
+static void
+qprintf(const char *str, Char *s)
+{
+	Char *p;
+
+	(void)printf("%s:\n", str);
+	for (p = s; *p; p++)
+		(void)printf("%c", CHAR(*p));
+	(void)printf("\n");
+	for (p = s; *p; p++)
+		(void)printf("%c", *p & M_PROTECT ? '"' : ' ');
+	(void)printf("\n");
+	for (p = s; *p; p++)
+		(void)printf("%c", ismeta(*p) ? '_' : ' ');
+	(void)printf("\n");
+}
+#endif
Index: xglob.h
===================================================================
RCS file: xglob.h
diff -N xglob.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ xglob.h	21 Jul 2008 13:11:46 -0000
@@ -0,0 +1,108 @@
+/*	$OpenBSD: glob.h,v 1.10 2005/12/13 00:35:22 millert Exp $	*/
+/*	$NetBSD: glob.h,v 1.5 1994/10/26 00:55:56 cgd Exp $	*/
+
+/*
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)glob.h	8.1 (Berkeley) 6/2/93
+ */
+
+#ifndef _XGLOB_H_
+#define	_XGLOB_H_
+
+#include <sys/cdefs.h>
+
+struct xglob_entry {
+	char *gl_path;		/* Path name */
+	struct stat gl_stat;	/* Corresponding struct stat */
+};
+
+struct stat;
+typedef struct {
+	int gl_pathc;		/* Count of total paths so far. */
+	int gl_matchc;		/* Count of paths matching pattern. */
+	int gl_offs;		/* Reserved at beginning of gl_matchv. */
+	int gl_flags;		/* Copy of flags parameter to glob. */
+
+	struct xglob_entry *gl_matchv;
+				/* List of entries matching */
+
+	void *gl_user_ctx;	/* User context, also passed to ALTDIRFUNCs */
+
+	int (*gl_errfunc)(const char *, int);
+
+	/*
+	 * Alternate filesystem access methods for glob; replacement
+	 * versions of closedir(3), readdir(3), opendir(3), stat(2)
+	 * and lstat(2). These versions are passed xglob_t->gl_user_ctx
+	 * as their first arguments.
+	 */
+	void (*gl_closedir)(void *, void *);
+	struct dirent *(*gl_readdir)(void *, void *);
+	void *(*gl_opendir)(void *, const char *);
+	int (*gl_lstat)(void *, const char *, struct stat *);
+	int (*gl_stat)(void *, const char *, struct stat *);
+
+	/*
+	 * Alternate comparison function, used to qsort(3) gl_matchv.
+	 * if the XGLOB_ALTCMPFUNC option has been specified.
+	 * Arguments to the comparison function are both struct glob_entry *
+	 */
+	int (*gl_cmp)(const void *, const void *);
+} xglob_t;
+
+#define	XGLOB_APPEND	0x0001	/* Append to output from previous call. */
+#define	XGLOB_DOOFFS	0x0002	/* Use gl_offs. */
+#define	XGLOB_ERR	0x0004	/* Return on error. */
+#define	XGLOB_MARK	0x0008	/* Append / to matching directories. */
+#define	XGLOB_NOCHECK	0x0010	/* Return pattern itself if nothing matches. */
+#define	XGLOB_NOSORT	0x0020	/* Don't sort. */
+#define	XGLOB_NOESCAPE	0x1000	/* Disable backslash escaping. */
+
+#define	XGLOB_NOSPACE	(-1)	/* Malloc call failed. */
+#define	XGLOB_ABORTED	(-2)	/* Unignored error. */
+#define	XGLOB_NOMATCH	(-3)	/* No match and GLOB_NOCHECK not set. */
+#define	XGLOB_NOSYS	(-4)	/* Function not supported. */
+
+#define	XGLOB_ALTDIRFUNC 0x0040	/* Use alternately specified directory funcs. */
+#define	XGLOB_BRACE	0x0080	/* Expand braces ala csh. */
+#define	XGLOB_MAGCHAR	0x0100	/* Pattern had globbing characters. */
+#define	XGLOB_NOMAGIC	0x0200	/* GLOB_NOCHECK without magic chars (csh). */
+#define	XGLOB_QUOTE	0x0400	/* Quote special chars with \. */
+#define	XGLOB_TILDE	0x0800	/* Expand tilde names from the passwd file. */
+#define XGLOB_LIMIT	0x2000	/* Limit pattern match output to ARG_MAX */
+#define XGLOB_ALTCMPFUNC 0x4000	/* Use alternately specified comparison func */
+#define XGLOB_ABEND	GLOB_ABORTED /* backward compatibility */
+
+int	xglob(const char *, int, int (*)(const char *, int), xglob_t *);
+void	xglobfree(xglob_t *);
+
+#endif /* !_XGLOB_H_ */
Index: sftp/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp/Makefile,v
retrieving revision 1.11
diff -u -p -r1.11 Makefile
--- sftp/Makefile	18 Apr 2008 12:32:11 -0000	1.11
+++ sftp/Makefile	21 Jul 2008 13:11:46 -0000
@@ -10,7 +10,7 @@ BINMODE?=555
 BINDIR=	/usr/bin
 MAN=	sftp.1
 
-SRCS=	sftp.c sftp-client.c sftp-common.c sftp-glob.c
+SRCS=	sftp.c sftp-client.c sftp-common.c xglob.c sftp-glob.c
 
 .include <bsd.prog.mk>
 


More information about the openssh-unix-dev mailing list