+
+
+/*
+ * Expand shell metacharacters. At this point, the only control characters
+ * should be escapes. The results are stored in the list exparg.
+ */
+
+char *expdir;
+
+
+STATIC void
+expandmeta(str)
+ struct strlist *str;
+ {
+ char *p;
+ struct strlist **savelastp;
+ struct strlist *sp;
+ char c;
+
+ while (str) {
+ if (fflag)
+ goto nometa;
+ p = str->text;
+#if UDIR
+ if (p[0] == '/' && p[1] == 'u' && p[2] == '/')
+ str->text = p = expudir(p);
+#endif
+ for (;;) { /* fast check for meta chars */
+ if ((c = *p++) == '\0')
+ goto nometa;
+ if (c == '*' || c == '?' || c == '[' || c == '!')
+ break;
+ }
+ savelastp = exparg.lastp;
+ INTOFF;
+ if (expdir == NULL)
+ expdir = ckmalloc(1024); /* I hope this is big enough */
+ expmeta(expdir, str->text);
+ ckfree(expdir);
+ expdir = NULL;
+ INTON;
+ if (exparg.lastp == savelastp) {
+ if (! zflag) {
+nometa:
+ *exparg.lastp = str;
+ rmescapes(str->text);
+ exparg.lastp = &str->next;
+ }
+ } else {
+ *exparg.lastp = NULL;
+ *savelastp = sp = expsort(*savelastp);
+ while (sp->next != NULL)
+ sp = sp->next;
+ exparg.lastp = &sp->next;
+ }
+ str = str->next;
+ }
+}
+
+
+#if UDIR
+/*
+ * Expand /u/username into the home directory for the specified user.
+ * We could use the getpw stuff here, but then we would have to load
+ * in stdio and who knows what else.
+ */
+
+#define MAXLOGNAME 32
+#define MAXPWLINE 128
+
+char *pfgets();
+
+
+STATIC char *
+expudir(path)
+ char *path;
+ {
+ register char *p, *q, *r;
+ char name[MAXLOGNAME];
+ char line[MAXPWLINE];
+ int i;
+
+ r = path; /* result on failure */
+ p = r + 3; /* the 3 skips "/u/" */
+ q = name;
+ while (*p && *p != '/') {
+ if (q >= name + MAXLOGNAME - 1)
+ return r; /* fail, name too long */
+ *q++ = *p++;
+ }
+ *q = '\0';
+ setinputfile("/etc/passwd", 1);
+ q = line + strlen(name);
+ while (pfgets(line, MAXPWLINE) != NULL) {
+ if (line[0] == name[0] && prefix(name, line) && *q == ':') {
+ /* skip to start of home directory */
+ i = 4;
+ do {
+ while (*++q && *q != ':');
+ } while (--i > 0);
+ if (*q == '\0')
+ break; /* fail, corrupted /etc/passwd */
+ q++;
+ for (r = q ; *r && *r != '\n' && *r != ':' ; r++);
+ *r = '\0'; /* nul terminate home directory */
+ i = r - q; /* i = strlen(q) */
+ r = stalloc(i + strlen(p) + 1);
+ scopy(q, r);
+ scopy(p, r + i);
+ TRACE(("expudir converts %s to %s\n", path, r));
+ didudir = 1;
+ path = r; /* succeed */
+ break;
+ }
+ }
+ popfile();
+ return r;
+}
+#endif
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+STATIC void
+expmeta(enddir, name)
+ char *enddir;
+ char *name;
+ {
+ register char *p;
+ char *q;
+ char *start;
+ char *endname;
+ int metaflag;
+ struct stat statb;
+ DIR *dirp;
+ struct dirent *dp;
+ int atend;
+ int matchdot;
+
+ metaflag = 0;
+ start = name;
+ for (p = name ; ; p++) {
+ if (*p == '*' || *p == '?')
+ metaflag = 1;
+ else if (*p == '[') {
+ q = p + 1;
+ if (*q == '!')
+ q++;
+ for (;;) {
+ if (*q == CTLESC)
+ q++;
+ if (*q == '/' || *q == '\0')
+ break;
+ if (*++q == ']') {
+ metaflag = 1;
+ break;
+ }
+ }
+ } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) {
+ metaflag = 1;
+ } else if (*p == '\0')
+ break;
+ else if (*p == CTLESC)
+ p++;
+ if (*p == '/') {
+ if (metaflag)
+ break;
+ start = p + 1;
+ }
+ }
+ if (metaflag == 0) { /* we've reached the end of the file name */
+ if (enddir != expdir)
+ metaflag++;
+ for (p = name ; ; p++) {
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p;
+ if (*p == '\0')
+ break;
+ }
+ if (metaflag == 0 || stat(expdir, &statb) >= 0)
+ addfname(expdir);
+ return;
+ }
+ endname = p;
+ if (start != name) {
+ p = name;
+ while (p < start) {
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p++;
+ }
+ }
+ if (enddir == expdir) {
+ p = ".";
+ } else if (enddir == expdir + 1 && *expdir == '/') {
+ p = "/";
+ } else {
+ p = expdir;
+ enddir[-1] = '\0';
+ }
+ if ((dirp = opendir(p)) == NULL)
+ return;
+ if (enddir != expdir)
+ enddir[-1] = '/';
+ if (*endname == 0) {
+ atend = 1;
+ } else {
+ atend = 0;
+ *endname++ = '\0';
+ }
+ matchdot = 0;
+ if (start[0] == '.' || start[0] == CTLESC && start[1] == '.')
+ matchdot++;
+ while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' && ! matchdot)
+ continue;
+ if (patmatch(start, dp->d_name)) {
+ if (atend) {
+ scopy(dp->d_name, enddir);
+ addfname(expdir);
+ } else {
+ char *q;
+ for (p = enddir, q = dp->d_name ; *p++ = *q++ ;);
+ p[-1] = '/';
+ expmeta(p, endname);
+ }
+ }
+ }
+ closedir(dirp);
+ if (! atend)
+ endname[-1] = '/';
+}
+
+
+/*
+ * Add a file name to the list.
+ */
+
+STATIC void
+addfname(name)
+ char *name;
+ {
+ char *p;
+ struct strlist *sp;
+
+ p = stalloc(strlen(name) + 1);
+ scopy(name, p);
+ sp = (struct strlist *)stalloc(sizeof *sp);
+ sp->text = p;
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
+}
+
+
+/*
+ * Sort the results of file name expansion. It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+STATIC struct strlist *
+expsort(str)
+ struct strlist *str;
+ {
+ int len;
+ struct strlist *sp;
+
+ len = 0;
+ for (sp = str ; sp ; sp = sp->next)
+ len++;
+ return msort(str, len);
+}
+
+
+STATIC struct strlist *
+msort(list, len)
+ struct strlist *list;
+ {
+ struct strlist *p, *q;
+ struct strlist **lpp;
+ int half;
+ int n;
+
+ if (len <= 1)
+ return list;
+ half = len >> 1;
+ p = list;
+ for (n = half ; --n >= 0 ; ) {
+ q = p;
+ p = p->next;
+ }
+ q->next = NULL; /* terminate first half of list */
+ q = msort(list, half); /* sort first half of list */
+ p = msort(p, len - half); /* sort second half */
+ lpp = &list;
+ for (;;) {
+ if (strcmp(p->text, q->text) < 0) {
+ *lpp = p;
+ lpp = &p->next;
+ if ((p = *lpp) == NULL) {
+ *lpp = q;
+ break;
+ }
+ } else {
+ *lpp = q;
+ lpp = &q->next;
+ if ((q = *lpp) == NULL) {
+ *lpp = p;
+ break;
+ }
+ }
+ }
+ return list;
+}
+
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+int
+patmatch(pattern, string)
+ char *pattern;
+ char *string;
+ {
+ if (pattern[0] == '!' && pattern[1] == '!')
+ return 1 - pmatch(pattern + 2, string);
+ else
+ return pmatch(pattern, string);
+}
+
+
+STATIC int
+pmatch(pattern, string)
+ char *pattern;
+ char *string;
+ {
+ register char *p, *q;
+ register char c;
+
+ p = pattern;
+ q = string;
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ goto breakloop;
+ case CTLESC:
+ if (*q++ != *p++)
+ return 0;
+ break;
+ case '?':
+ if (*q++ == '\0')
+ return 0;
+ break;
+ case '*':
+ c = *p;
+ if (c != CTLESC && c != '?' && c != '*' && c != '[') {
+ while (*q != c) {
+ if (*q == '\0')
+ return 0;
+ q++;
+ }
+ }
+ do {
+ if (pmatch(p, q))
+ return 1;
+ } while (*q++ != '\0');
+ return 0;
+ case '[': {
+ char *endp;
+ int invert, found;
+ char chr;
+
+ endp = p;
+ if (*endp == '!')
+ endp++;
+ for (;;) {
+ if (*endp == '\0')
+ goto dft; /* no matching ] */
+ if (*endp == CTLESC)
+ endp++;
+ if (*++endp == ']')
+ break;
+ }
+ invert = 0;
+ if (*p == '!') {
+ invert++;
+ p++;
+ }
+ found = 0;
+ chr = *q++;
+ c = *p++;
+ do {
+ if (c == CTLESC)
+ c = *p++;
+ if (*p == '-' && p[1] != ']') {
+ p++;
+ if (*p == CTLESC)
+ p++;
+ if (chr >= c && chr <= *p)
+ found = 1;
+ p++;
+ } else {
+ if (chr == c)
+ found = 1;
+ }
+ } while ((c = *p++) != ']');
+ if (found == invert)
+ return 0;
+ break;
+ }
+dft: default:
+ if (*q++ != c)
+ return 0;
+ break;
+ }
+ }
+breakloop:
+ if (*q != '\0')
+ return 0;
+ return 1;
+}
+
+
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+
+void
+rmescapes(str)
+ char *str;
+ {
+ register char *p, *q;
+
+ p = str;
+ while (*p != CTLESC) {
+ if (*p++ == '\0')
+ return;
+ }
+ q = p;
+ while (*p) {
+ if (*p == CTLESC)
+ p++;
+ *q++ = *p++;
+ }
+ *q = '\0';
+}
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+int
+casematch(pattern, val)
+ union node *pattern;
+ char *val;
+ {
+ struct stackmark smark;
+ int result;
+ char *p;
+
+ setstackmark(&smark);
+ argbackq = pattern->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifslastp = NULL;
+ argstr(pattern->narg.text, 0);
+ STPUTC('\0', expdest);
+ p = grabstackstr(expdest);
+ result = patmatch(p, val);
+ popstackmark(&smark);
+ return result;
+}