/*
** glob.c
**
** rc's (ugly) globber. This code is not elegant, but it works.
*/

#include "rc.h"

#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

static List *dmatch(char *, char *, char *, bool);
static List *doglob(char *, char *, bool);
static List *lglob(List *, char *, char *, size_t, bool);
static List *sort(List *);

/* returns true (number of dots) if the path is a dotpath - one beginning with a string of dots followed by a / */
int
isdotpath ( char * path ) {
	char * p;

	/* Walk the string, returning 0 if any character couldn't be
	** part of a dotpath. */
	for ( p = path ; *p ; p++ ) {
		if ( *p != '.' ) return (*p=='/')? p-path : 0;
	}

	/* Everything was dotty. That's a dotpath, kids. */
	return p-path;
}

/*
   Matches a list of words s against a list of patterns p. Returns true iff
   a pattern in p matches a word in s. () matches (), but otherwise null
   patterns match nothing.
*/

extern bool lmatch(List *s, List *p) {
	List *q;
	int i;
	bool okay;
	if (s == NULL) {
		if (p == NULL) /* null matches null */
			return TRUE;
		for (; p != NULL; p = p->n) { /* one or more stars match null */
			if (*p->w != '\0') { /* the null string is a special case; it does *not* match () */
				okay = TRUE;
				for (i = 0; p->w[i] != '\0'; i++)
					if (p->w[i] != '*' || p->m[i] != 1) {
						okay = FALSE;
						break;
					}
				if (okay)
					return TRUE;
			}
		}
		return FALSE;
	}
	for (; s != NULL; s = s->n)
		for (q = p; q != NULL; q = q->n)
			if (match(q->w, q->m, s->w))
				return TRUE;
	return FALSE;
}


/*
   Globs a list; checks to see if each element in the list has a metacharacter. If it
   does, it is globbed, and the output is sorted.
*/

extern List *glob(List *s, bool must_exist) {
	List *top, *r;
	bool meta;
	for (r = s, meta = FALSE; r != NULL; r = r->n)
		if (r->m != NULL)
			meta = TRUE;
	if (!meta)
		return s; /* don't copy lists with no metacharacters in them */
	for (top = r = NULL; s != NULL; s = s->n) {
		if (s->m == NULL) { /* no metacharacters; just tack on to the return list */
			if (top == NULL)
				top = r = nnew(List);
			else
				r = r->n = nnew(List);
			r->w = s->w;
		} else {
			if (top == NULL)
				top = r = sort(doglob(s->w, s->m, must_exist));
			else
				r->n = sort(doglob(s->w, s->m, must_exist));
			while (r->n != NULL)
				r = r->n;
		}
	}
	r->n = NULL;
	return top;
}

/* Matches a pattern p against the contents of directory d */

static List *dmatch(char *d, char *p, char *m, bool dir_only) {
	bool matched = FALSE;
	List *top, *r;
	static DIR *dirp;
	static struct dirent *dp;
	static struct stat s;
	int len;
	char *statname = NULL;

#ifndef AIX
	extern DIR *opendir(const char *);
#endif
	int i;

	top = r = NULL;
	/* opendir succeeds on regular files on some systems, so the stat() call is necessary (sigh) */
#ifndef S_ISDIR
	if (stat (d, &s) == 0 && (s.st_mode & S_IFMT) == S_IFDIR) {
#else
	if (stat (d, &s) == 0 && S_ISDIR (s.st_mode)) {
#endif
		if ((dirp = opendir (d)) == NULL) {
			fprint (2,"opendir failed");
			matched = TRUE;
			if (m != NULL) {
				for (i = 0; p [i] != '\0'; i ++) {
					if (m [i]) {
						matched = FALSE;
						break;
					}
				}
			}
			if (matched) {
				top = nnew (List);
				top->w = ncpy(p);
				top->m = NULL;
				top->n = NULL;
				return top;
			} else {
				return NULL;
			}
		}
	} else {
		return NULL;
	}
	len = strlen (d);
	while ((dp = readdir(dirp)) != NULL) {
		if (match (p, m, dp->d_name) && ((!streq (dp->d_name, ".") && !streq (dp->d_name, "..")) || streq (dp->d_name, p))) { /* only match "." and ".." explicitly */
			if (dir_only) {
				statname = ealloc (len + strlen (dp->d_name) + 2);
				strcpy (statname, d);
				strcat (statname, "/");
				strcat (statname, dp->d_name);
			}
#ifndef S_ISDIR
			if (!dir_only || (stat (statname, &s) == 0 && (s.st_mode & S_IFMT) == S_IFDIR)) {
#else
			if (!dir_only || (stat (statname, &s) == 0 && S_ISDIR (s.st_mode))) {
#endif
				matched = TRUE;
				if (top == NULL) {
					top = r = nnew(List);
				} else {
					r = r->n = nnew(List);
				}
				r->w = ncpy(dp->d_name);
				r->m = NULL;
			}
			if (dir_only) efree (statname);
		}
	}
	closedir(dirp);
	if (!matched) return NULL;
	r->n = NULL;
	return top;
}

/*
   lglob() globs a pattern agains a list of directory roots. e.g., (/tmp /usr /bin) "*"
   will return a list with all the files in /tmp, /usr, and /bin. NULL on no match.
   slashcount indicates the number of slashes to stick between the directory and the
   matched name. e.g., for matching ////tmp/////foo*
*/

static List *lglob(List *s, char *p, char *m, size_t slashcount, bool dir_only) {
	List *q, *r, *top, foo;
	static struct {
		List l;
		size_t size;
	} slash;

	if (slashcount + 1 > slash.size) {
		slash.size = 2*(slashcount+1);
		slash.l.w = erealloc(slash.l.w, slash.size);
	}
	slash.l.w[slashcount] = '\0';
	while (slashcount > 0)
		slash.l.w[--slashcount] = '/';
	for (top = r = NULL; s != NULL; s = s->n) {
		q = dmatch(s->w, p, m, dir_only);
		if (q != NULL) {
			foo.w = s->w;
			foo.m = NULL;
			foo.n = NULL;
			if (!(s->w[0] == '/' && s->w[1] == '\0')) { /* need to separate */
				q = concat(&slash.l, q);		  /* dir/name with slash */
			}
			q = concat(&foo, q);
			if (r == NULL) {
				top = r = q;
			} else {
				r->n = q;
			}
			while (r->n != NULL)
				r = r->n;
		}
	}
	return top;
}

/*
   Doglob globs a pathname in pattern form against a unix path. Returns the original
   pattern (cleaned of metacharacters if must_exist FALSE) on failure, or the globbed string(s).
*/

static List *doglob(char *w, char *m, bool must_exist) {
	static char *dir = NULL, *pattern = NULL, *metadir = NULL, *metapattern = NULL;
	static size_t dsize = 0;
	char *d, *p, *md, *mp;
	size_t psize;
	char *s = w;
	List firstdir;
	List *matched;

	char * newpath = NULL; /* the dotpath after being converted into a normal UNIX path. */
	int dots; /* number of dots at the start of the thing to be globbed. */

	if ((psize = strlen(w) + 1) > dsize || dir == NULL) {
		efree(dir); efree(pattern); efree(metadir); efree(metapattern);
		dir = ealloc(psize);
		pattern = ealloc(psize);
		metadir = ealloc(psize);
		metapattern = ealloc(psize);
		dsize = psize;
	}
	d = dir;
	p = pattern;
	md = metadir;
	mp = metapattern;
	if (*s == '/')
		while (*s == '/')
			*d++ = *s++, *md++ = *m++;
	else
		while (*s != '/' && *s != '\0')
			*d++ = *s++, *md++ = *m++; /* get first directory component */
	*d = '\0';

	/*
	   Special case: no slashes in the pattern, i.e., open the current directory.
	   Remember that w cannot consist of slashes alone (the other way *s could be
	   zero) since doglob gets called iff there's a metacharacter to be matched
	*/
	if (*s == '\0') {
		matched = dmatch(".", dir, metadir, FALSE);
		goto end;
	}

	/* MAD -- please read. i don't understand how this works. */
	/* MAD -- please read. i don't understand how this works. */
	/* MAD -- please read. i don't understand how this works. */
	/* MAD -- please read. i don't understand how this works. */
	/* MAD -- please read. i don't understand how this works. */
	/* MAD -- please read. i don't understand how this works. */

	if ( (dots = isdotpath(w)) > 2 ) {
		char * p = w + dots + 1;
		char * q;

		newpath = ealloc(3*(dots-1)+strlen(p));
		q = newpath;
		do {
			*(q++) = '.'; *(q++) = '.'; *(q++) = '/';
		} while ( dots-- > 2 );
		*(--q) = '\0';

		firstdir.w = newpath;
		firstdir.m = "";
		firstdir.n = NULL;
		matched = &firstdir;
	} else if ( *w == '~' ) {
/*		List * lookup = NULL;
		struct passwd * pwent;
		char * start, * end, * dir;

		start = w+1;
		end = strchr (start,'/');
		if ( end == start || end == NULL ) {
			lookup = varlookup("home");
			if ( lookup != NULL ) dir = lookup->w;
		} else {
			if ( end != NULL ) *end = '\0';
		}

*/
	} else if (isabsolute (w)) {
		firstdir.w = dir;
		firstdir.m = metadir;
		firstdir.n = NULL;
		matched = &firstdir;
	} else {
		/*
		   we must glob against current directory,
		   since the first character is not a slash.
		*/
		matched = dmatch(".", dir, metadir, *(s + 1) == '\0' ? TRUE : FALSE);
		if (*(s + 1) == '\0') goto end;
	}

	do {
		size_t slashcount;
		sigchk ();
		for (slashcount = 0; *s == '/'; s++, m++)
			slashcount++; /* skip slashes */
		while (*s != '/' && *s != '\0')
			*p++ = *s++, *mp++ = *m++; /* get pattern */
		*p = '\0';
		matched = lglob(matched, pattern, metapattern, slashcount, *s == '\0' || *(s + 1) != '\0' ? FALSE : TRUE);
		p = pattern, mp = metapattern;
	} while (*s != '\0' && *(s + 1) != '\0');

	efree (newpath);

end:	if (matched == NULL) {
		matched = nnew(List);
		matched->w = w;
		if (must_exist) {
			matched->m = m;
		} else {
			matched->m = NULL;
		}
		matched->n = NULL;
	}
	return matched;
}

static List *sort(List *s) {
	size_t nel = listnel(s);
	if (nel > 1) {
		char **a;
		List *t;
		qsort(a = list2array(s, FALSE), nel, sizeof(char *), starstrcmp);
		for (t = s; t != NULL; t = t->n)
			t->w = *a++;
	}
	return s;
}
