root/usr/src/cmd/cmd-inet/usr.bin/ftp/glob.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 *      Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 *      Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*      All Rights Reserved     */

/*
 *      University Copyright- Copyright (c) 1982, 1986, 1988
 *      The Regents of the University of California
 *      All Rights Reserved
 *
 *      University Acknowledgment- Portions of this document are derived from
 *      software developed by the University of California, Berkeley, and its
 *      contributors.
 */

/*
 * C-shell glob for random programs.
 */

#include "ftp_var.h"

#ifndef NCARGS
#define NCARGS  5120
#endif

#define QUOTE 0200
#define TRIM 0177
#define eq(a, b)        (strcmp(a, b) == 0)

/*
 * According to the person who wrote the C shell "glob" code, a reasonable
 * limit on number of arguments would seem to be the maximum number of
 * characters in an arg list / 6.
 *
 * XXX: With the new VM system, NCARGS has become enormous, making
 *      it impractical to allocate arrays with NCARGS / 6 entries on
 *      the stack.  The proper fix is to revamp code elsewhere (in
 *      sh.dol.c and sh.glob.c) to use a different technique for handling
 *      command line arguments.  In the meantime, we simply fall back
 *      on using the old value of NCARGS.
 */
#ifdef  notyet
#define GAVSIZ  (NCARGS / 6)
#else   /* notyet */
#define GAVSIZ  (10240 / 6)
#endif  /* notyet */

static  char **gargv;           /* Pointer to the (stack) arglist */
static  char **agargv;
static  int agargv_size;
static  long gargc;             /* Number args in gargv */
static  short gflag;
static char *strspl();
static char *strend(char *cp);
static char *strspl(char *cp, char *dp);
static int tglob(char c);
static char **copyblk(char **v);
static void ginit(char **agargv);
static void addpath(char c);
static int any(int c, char *s);
static void Gcat(char *s1, char *s2);
static void collect(char *as);
static void acollect(char *as);
static void sort(void);
static void expand(char *as);
static void matchdir(char *pattern);
static int execbrc(char *p, char *s);
static int ftp_fnmatch(wchar_t t_ch, wchar_t t_fch, wchar_t t_lch);
static int gethdir(char *home);
static void xfree(char *cp);
static void rscan(char **t, int (*f)(char));
static int letter(char c);
static int digit(char c);
static int match(char *s, char *p);
static int amatch(char *s, char *p);
static int blklen(char **av);
static char **blkcpy(char **oav, char **bv);

static  int globcnt;

static char     *globchars = "`{[*?";

static  char *gpath, *gpathp, *lastgpathp;
static  int globbed;
static  char *entp;
static  char **sortbas;

char **
glob(char *v)
{
        char agpath[FTPBUFSIZ];
        char *vv[2];

        if (agargv == NULL) {
                agargv = (char **)malloc(GAVSIZ * sizeof (char *));
                agargv_size = GAVSIZ;
                if (agargv == NULL) {
                        globerr = "Arguments too long.";
                        return (0);
                }
        }
        vv[0] = v;
        vv[1] = 0;
        globerr = 0;
        gflag = 0;
        rscan(vv, tglob);
        if (gflag == 0)
                return (copyblk(vv));

        gpath = agpath;
        gpathp = gpath;
        *gpathp = 0;
        lastgpathp = &gpath[sizeof (agpath) - 2];
        ginit(agargv);
        globcnt = 0;
        collect(v);
        if (globcnt == 0 && (gflag&1)) {
                blkfree(gargv);
                if (gargv == agargv)
                        agargv = 0;
                gargv = 0;
                return (0);
        } else
                return (gargv = copyblk(gargv));
}

static void
ginit(char **agargv)
{

        agargv[0] = 0;
        gargv = agargv;
        sortbas = agargv;
        gargc = 0;
}

static void
collect(char *as)
{
        if (eq(as, "{") || eq(as, "{}")) {
                Gcat(as, "");
                sort();
        } else
                acollect(as);
}

static void
acollect(char *as)
{
        register long ogargc = gargc;

        gpathp = gpath; *gpathp = 0; globbed = 0;
        expand(as);
        if (gargc != ogargc)
                sort();
}

static void
sort(void)
{
        register char **p1, **p2, *c;
        char **Gvp = &gargv[gargc];

        p1 = sortbas;
        while (p1 < Gvp-1) {
                p2 = p1;
                while (++p2 < Gvp)
                        if (strcmp(*p1, *p2) > 0)
                                c = *p1, *p1 = *p2, *p2 = c;
                p1++;
        }
        sortbas = Gvp;
}

static void
expand(char *as)
{
        register char *cs;
        register char *sgpathp, *oldcs;
        struct stat stb;

        sgpathp = gpathp;
        cs = as;
        if (*cs == '~' && gpathp == gpath) {
                addpath('~');
                cs++;
                while (letter(*cs) || digit(*cs) || *cs == '-')
                        addpath(*cs++);
                if (!*cs || *cs == '/') {
                        if (gpathp != gpath + 1) {
                                *gpathp = 0;
                                if (gethdir(gpath + 1))
                                        globerr = "Unknown user name after ~";
                                (void) strcpy(gpath, gpath + 1);
                        } else
                                (void) strcpy(gpath, home);
                        gpathp = strend(gpath);
                }
        }
        while (!any(*cs, globchars)) {
                if (*cs == 0) {
                        if (!globbed)
                                Gcat(gpath, "");
                        else if (stat(gpath, &stb) >= 0) {
                                Gcat(gpath, "");
                                globcnt++;
                        }
                        goto endit;
                }
                addpath(*cs++);
        }
        oldcs = cs;
        while (cs > as && *cs != '/')
                cs--, gpathp--;
        if (*cs == '/')
                cs++, gpathp++;
        *gpathp = 0;
        if (*oldcs == '{') {
                (void) execbrc(cs, ((char *)0));
                return;
        }
        matchdir(cs);
endit:
        gpathp = sgpathp;
        *gpathp = 0;
}

static void
matchdir(char *pattern)
{
        struct stat stb;
        register struct dirent *dp;
        DIR *dirp;

        /*
         * BSD/SunOS open() system call maps a null pathname into
         * "." while System V does not.
         */
        if (*gpath == (char)0) {
                dirp = opendir(".");
        } else
                dirp = opendir(gpath);
        if (dirp == NULL) {
                if (globbed)
                        return;
                goto patherr2;
        }
        if (fstat(dirp->dd_fd, &stb) < 0)
                goto patherr1;
        if (!S_ISDIR(stb.st_mode)) {
                errno = ENOTDIR;
                goto patherr1;
        }
        while ((dp = readdir(dirp)) != NULL) {
                if (dp->d_ino == 0)
                        continue;
                if (match(dp->d_name, pattern)) {
                        Gcat(gpath, dp->d_name);
                        globcnt++;
                }
        }
        closedir(dirp);
        return;

patherr1:
        closedir(dirp);
patherr2:
        globerr = "Bad directory components";
}

static int
execbrc(char *p, char *s)
{
        char restbuf[FTPBUFSIZ + 2];
        register char *pe, *pm, *pl;
        int brclev = 0;
        char *lm, savec, *sgpathp;
        int     len;

        for (lm = restbuf; *p != '{'; *lm += len, p += len) {
                if ((len = mblen(p, MB_CUR_MAX)) <= 0)
                        len = 1;
                memcpy(lm, p, len);
        }

        for (pe = ++p; *pe; pe += len) {
                if ((len = mblen(pe, MB_CUR_MAX)) <= 0)
                        len = 1;

                switch (*pe) {

                case '{':
                        brclev++;
                        continue;

                case '}':
                        if (brclev == 0)
                                goto pend;
                        brclev--;
                        continue;

                case '[':
                        for (pe++; *pe && *pe != ']'; pe += len) {
                                if ((len = mblen(pe, MB_CUR_MAX)) <= 0)
                                        len = 1;
                        }
                        len = 1;
                        continue;
                }
        }
pend:
        brclev = 0;
        for (pl = pm = p; pm <= pe; pm += len) {
                if ((len = mblen(pm, MB_CUR_MAX)) <= 0)
                        len = 1;

                switch (*pm & (QUOTE|TRIM)) {

                case '{':
                        brclev++;
                        continue;

                case '}':
                        if (brclev) {
                                brclev--;
                                continue;
                        }
                        goto doit;

                case ','|QUOTE:
                case ',':
                        if (brclev)
                                continue;
doit:
                        savec = *pm;
                        *pm = 0;
                        (void) strcpy(lm, pl);
                        (void) strcat(restbuf, pe + 1);
                        *pm = savec;
                        if (s == 0) {
                                sgpathp = gpathp;
                                expand(restbuf);
                                gpathp = sgpathp;
                                *gpathp = 0;
                        } else if (amatch(s, restbuf))
                                return (1);
                        sort();
                        pl = pm + 1;
                        if (brclev)
                                return (0);
                        continue;

                case '[':
                        for (pm++; *pm && *pm != ']'; pm += len) {
                                if ((len = mblen(pm, MB_CUR_MAX)) <= 0)
                                        len = 1;
                        }
                        len = 1;
                        if (!*pm)
                                pm--;
                        continue;
                }
        }
        if (brclev)
                goto doit;
        return (0);
}

static int
match(char *s, char *p)
{
        register int c;
        register char *sentp;
        char sglobbed = globbed;

        if (*s == '.' && *p != '.')
                return (0);
        sentp = entp;
        entp = s;
        c = amatch(s, p);
        entp = sentp;
        globbed = sglobbed;
        return (c);
}

static int
amatch(char *s, char *p)
{
        wchar_t scc;
        int ok;
        wchar_t lc1, lc2;
        char *sgpathp;
        struct stat stb;
        wchar_t c, cc;
        int     len_s, len_p;

        globbed = 1;
        for (;;) {
                if ((len_s = mbtowc(&scc, s, MB_CUR_MAX)) <= 0) {
                        scc = (unsigned char)*s;
                        len_s = 1;
                }
                /* scc = *s++ & TRIM; */
                s += len_s;

                if ((len_p = mbtowc(&c, p, MB_CUR_MAX)) <= 0) {
                        c = (unsigned char)*p;
                        len_p = 1;
                }
                p += len_p;
                switch (c) {

                case '{':
                        return (execbrc(p - len_p, s - len_s));

                case '[':
                        ok = 0;
                        lc1 = 0;
                        while ((cc = *p) != '\0') {
                                if ((len_p = mbtowc(&cc, p, MB_CUR_MAX)) <= 0) {
                                        cc = (unsigned char)*p;
                                        len_p = 1;
                                }
                                p += len_p;
                                if (cc == ']') {
                                        if (ok)
                                                break;
                                        return (0);
                                }
                                if (cc == '-') {
                                        if ((len_p = mbtowc(&lc2, p,
                                            MB_CUR_MAX)) <= 0) {
                                                lc2 = (unsigned char)*p;
                                                len_p = 1;
                                        }
                                        p += len_p;
                                        if (ftp_fnmatch(scc, lc1, lc2))
                                                ok++;
                                } else
                                        if (scc == (lc1 = cc))
                                                ok++;
                        }
                        if (cc == 0)
                                if (!ok)
                                        return (0);
                        continue;

                case '*':
                        if (!*p)
                                return (1);
                        if (*p == '/') {
                                p++;
                                goto slash;
                        }
                        s -= len_s;
                        do {
                                if (amatch(s, p))
                                        return (1);
                        } while (*s++);
                        return (0);

                case 0:
                        return (scc == 0);

                default:
                        if (c != scc)
                                return (0);
                        continue;

                case '?':
                        if (scc == 0)
                                return (0);
                        continue;

                case '/':
                        if (scc)
                                return (0);
slash:
                        s = entp;
                        sgpathp = gpathp;
                        while (*s)
                                addpath(*s++);
                        addpath('/');
                        if (stat(gpath, &stb) == 0 && S_ISDIR(stb.st_mode))
                                if (*p == 0) {
                                        Gcat(gpath, "");
                                        globcnt++;
                                } else
                                        expand(p);
                        gpathp = sgpathp;
                        *gpathp = 0;
                        return (0);
                }
        }
}

#ifdef notdef
static
Gmatch(s, p)
        register char *s, *p;
{
        register int scc;
        int ok, lc;
        int c, cc;

        for (;;) {
                scc = *s++ & TRIM;
                switch (c = *p++) {

                case '[':
                        ok = 0;
                        lc = 077777;
                        while (cc = *p++) {
                                if (cc == ']') {
                                        if (ok)
                                                break;
                                        return (0);
                                }
                                if (cc == '-') {
                                        if (lc <= scc && scc <= *p++)
                                                ok++;
                                } else
                                        if (scc == (lc = cc))
                                                ok++;
                        }
                        if (cc == 0)
                                if (ok)
                                        p--;
                                else
                                        return (0);
                        continue;

                case '*':
                        if (!*p)
                                return (1);
                        for (s--; *s; s++)
                                if (Gmatch(s, p))
                                        return (1);
                        return (0);

                case 0:
                        return (scc == 0);

                default:
                        if ((c & TRIM) != scc)
                                return (0);
                        continue;

                case '?':
                        if (scc == 0)
                                return (0);
                        continue;

                }
        }
}
#endif

static void
Gcat(char *s1, char *s2)
{
        if (gargc >= agargv_size - 1) {
                char **tmp;

                if (globerr) {
                        return;
                }
                tmp = (char **)realloc(agargv,
                    (agargv_size + GAVSIZ) * sizeof (char *));
                if (tmp == NULL) {
                        globerr = "Arguments too long";
                        return;
                } else {
                        agargv = tmp;
                        agargv_size += GAVSIZ;
                }
                gargv = agargv;
                sortbas = agargv;
        }
        gargc++;
        gargv[gargc] = 0;
        gargv[gargc - 1] = strspl(s1, s2);
}

static void
addpath(char c)
{

        if (gpathp >= lastgpathp)
                globerr = "Pathname too long";
        else {
                *gpathp++ = c;
                *gpathp = 0;
        }
}

static void
rscan(char **t, int (*f)(char))
{
        register char *p, c;
        int     len;

        while (p = *t++) {
                if (f == tglob)
                        if (*p == '~')
                                gflag |= 2;
                        else if (eq(p, "{") || eq(p, "{}"))
                                continue;
                while ((c = *p) != '\0') {
                        (void) (*f)(c);
                        if ((len = mblen(p, MB_CUR_MAX)) <= 0)
                                len = 1;
                        p += len;
                }
        }
}

static int
tglob(char c)
{
        if (any(c, globchars))
                gflag |= c == '{' ? 2 : 1;
        return (c);
}

static int
letter(char c)
{
        return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_');
}

static int
digit(char c)
{
        return (c >= '0' && c <= '9');
}

static int
any(int c, char *s)
{
        int     len;

        while (*s) {
                if (*s == c)
                        return (1);
                if ((len = mblen(s, MB_CUR_MAX)) <= 0)
                        len = 1;
                s += len;
        }
        return (0);
}

static int
blklen(char **av)
{
        register int i = 0;

        while (*av++)
                i++;
        return (i);
}

static char **
blkcpy(char **oav, char **bv)
{
        register char **av = oav;

        while (*av++ = *bv++)
                continue;
        return (oav);
}

void
blkfree(char **av0)
{
        register char **av = av0;

        while (*av)
                xfree(*av++);
        free(av0);
}

static void
xfree(char *cp)
{
        extern char end[];

        if (cp >= end && cp < (char *)&cp)
                free(cp);
}

static char *
strspl(char *cp, char *dp)
{
        register char *ep = malloc((unsigned)(strlen(cp) + strlen(dp) + 1));

        if (ep == (char *)0)
                fatal("Out of memory");
        (void) strcpy(ep, cp);
        (void) strcat(ep, dp);
        return (ep);
}

static char **
copyblk(char **v)
{
        register char **nv = (char **)malloc((unsigned)((blklen(v) + 1) *
            sizeof (char **)));

        if (nv == (char **)0)
                fatal("Out of memory");

        return (blkcpy(nv, v));
}

static char *
strend(char *cp)
{

        while (*cp)
                cp++;
        return (cp);
}
/*
 * Extract a home directory from the password file
 * The argument points to a buffer where the name of the
 * user whose home directory is sought is currently.
 * We write the home directory of the user back there.
 */
static int
gethdir(char *home)
{
        register struct passwd *pp = getpwnam(home);

        if (!pp || home + strlen(pp->pw_dir) >= lastgpathp)
                return (1);
        (void) strcpy(home, pp->pw_dir);
        return (0);
}

static int
ftp_fnmatch(wchar_t t_ch, wchar_t t_fch, wchar_t t_lch)
{
        char    t_char[MB_LEN_MAX + 1];
        char    t_patan[MB_LEN_MAX * 2 + 8];
        char    *p;
        int     i;

        if ((t_ch == t_fch) || (t_ch == t_lch))
                return (1);

        p = t_patan;
        if ((i = wctomb(t_char, (wchar_t)t_ch)) <= 0)
                return (0);
        t_char[i] = 0;

        *p++ = '[';
        if ((i = wctomb(p, (wchar_t)t_fch)) <= 0)
                return (0);
        p += i;
        *p++ = '-';
        if ((i = wctomb(p, (wchar_t)t_lch)) <= 0)
                return (0);
        p += i;
        *p++ = ']';
        *p = 0;

        if (fnmatch(t_patan, t_char, FNM_NOESCAPE))
                return (0);
        return (1);
}