source: trunk/third/tcsh/tw.parse.c @ 9006

Revision 9006, 43.4 KB checked in by ghudson, 28 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r9005, which included commits to RCS files with non-trunk default branches.
Line 
1/* $Header: /afs/dev.mit.edu/source/repository/third/tcsh/tw.parse.c,v 1.1.1.1 1996-10-02 06:09:24 ghudson Exp $ */
2/*
3 * tw.parse.c: Everyone has taken a shot in this futile effort to
4 *             lexically analyze a csh line... Well we cannot good
5 *             a job as good as sh.lex.c; but we try. Amazing that
6 *             it works considering how many hands have touched this code
7 */
8/*-
9 * Copyright (c) 1980, 1991 The Regents of the University of California.
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 *    must display the following acknowledgement:
22 *      This product includes software developed by the University of
23 *      California, Berkeley and its contributors.
24 * 4. Neither the name of the University nor the names of its contributors
25 *    may be used to endorse or promote products derived from this software
26 *    without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40#include "sh.h"
41
42RCSID("$Id: tw.parse.c,v 1.1.1.1 1996-10-02 06:09:24 ghudson Exp $")
43
44#include "tw.h"
45#include "ed.h"
46#include "tc.h"
47
48#define EVEN(x) (((x) & 1) != 1)
49
50#define DOT_NONE        0       /* Don't display dot files              */
51#define DOT_NOT         1       /* Don't display dot or dot-dot         */
52#define DOT_ALL         2       /* Display all dot files                */
53
54/*  TW_NONE,           TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,      */
55/*  TW_FILE,           TW_DIRECTORY,   TW_VARLIST,     TW_USER,         */
56/*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,       */
57/*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL        */
58/*  TW_JOB,            TW_EXPLAIN,     TW_PATHNAME,    TW_TEXT          */
59static void (*tw_start_entry[]) __P((DIR *, Char *)) = {
60    tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start,
61    tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start,
62    tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,     
63    tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
64    tw_job_start,      tw_file_start,  tw_file_start,  tw_file_start
65};
66
67static Char * (*tw_next_entry[]) __P((Char *, int *)) = {
68    tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next, 
69    tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next, 
70    tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,   
71    tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
72    tw_job_next,       tw_file_next,   tw_file_next,   tw_file_next
73};
74
75static void (*tw_end_entry[]) __P((void)) = {
76    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
77    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
78    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
79    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
80    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end
81};
82
83/* #define TDEBUG */
84
85/* Set to TRUE if recexact is set and an exact match is found
86 * along with other, longer, matches.
87 */
88int non_unique_match = FALSE;
89static bool SearchNoDirErr = 0; /* t_search returns -2 if dir is unreadable */
90
91/* state so if a completion is interrupted, the input line doesn't get
92   nuked */
93int InsideCompletion = 0;
94
95/* do the expand or list on the command line -- SHOULD BE REPLACED */
96
97extern Char NeedsRedraw;        /* from ed.h */
98extern int Tty_raw_mode;
99extern int TermH;               /* from the editor routines */
100extern int lbuffed;             /* from sh.print.c */
101
102static  void     extract_dir_and_name   __P((Char *, Char *, Char *));
103static  Char    *quote_meta             __P((Char *, int, bool));
104static  Char    *dollar                 __P((Char *, Char *));
105static  Char    *tilde                  __P((Char *, Char *));
106static  int      expand_dir             __P((Char *, Char *, DIR  **, COMMAND));
107static  bool     nostat                 __P((Char *));
108static  Char     filetype               __P((Char *, Char *));
109static  int      t_glob                 __P((Char ***, int));
110static  int      c_glob                 __P((Char ***));
111static  int      is_prefix              __P((Char *, Char *));
112static  int      is_suffix              __P((Char *, Char *));
113static  int      recognize              __P((Char *, Char *, int, int));
114static  int      ignored                __P((Char *));
115static  int      isadirectory           __P((Char *, Char *));
116static  int      tw_collect_items       __P((COMMAND, int, Char *, Char *,
117                                             Char *, Char *, int));
118static  int      tw_collect             __P((COMMAND, int, Char *, Char *,
119                                             Char *, Char *, int, DIR *));
120static  Char     tw_suffix              __P((int, Char *, Char *, Char *,
121                                             Char *));
122static  void     tw_fixword             __P((int, Char *, Char *, Char *, int));
123static  void     tw_list_items          __P((int, int, int));
124
125#ifdef notdef
126/*
127 * If we find a set command, then we break a=b to a= and word becomes
128 * b else, we don't break a=b. [don't use that; splits words badly and
129 * messes up tw_complete()]
130 */
131#define isaset(c, w) ((w)[-1] == '=' && \
132                      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
133                       ((c[3] == ' ' || (c)[3] == '\t'))))
134#endif
135
136#define QLINESIZE (INBUFSIZE + 1)
137
138/* tenematch():
139 *      Return:
140 *              > 1:    No. of items found
141 *              = 1:    Exactly one match / spelling corrected
142 *              = 0:    No match / spelling was correct
143 *              < 0:    Error (incl spelling correction impossible)
144 */
145int
146tenematch(inputline, num_read, command)
147    Char   *inputline;          /* match string prefix */
148    int     num_read;           /* # actually in inputline */
149    COMMAND command;            /* LIST or RECOGNIZE or PRINT_HELP */
150
151{
152    Char    qline[QLINESIZE];
153    Char    qu = 0, *pat = STRNULL;
154    Char   *str_end, *cp, *wp, *wordp;
155    Char   *cmd_start, *word_start, *word;
156    Char   *ocmd_start = NULL, *oword_start = NULL, *oword = NULL;
157    int     suf = 0;
158    int     space_left;
159    int     looking;            /* what we are looking for              */
160    int     search_ret;         /* what search returned for debugging   */
161    int     backq = 0;
162
163    if (num_read > QLINESIZE - 1)
164        return -1;
165    str_end = &inputline[num_read];
166
167    word_start = inputline;
168    word = cmd_start = wp = qline;
169    for (cp = inputline; cp < str_end; cp++) {
170        if (!cmap(qu, _ESC)) {
171            if (cmap(*cp, _Q|_ESC)) {
172                if (qu == 0 || qu == *cp) {
173                    qu ^= *cp;
174                    continue;
175                }
176            }
177            if (qu != '\'' && cmap(*cp, _Q1)) {
178                if (backq ^= 1) {
179                    ocmd_start = cmd_start;
180                    oword_start = word_start;
181                    oword = word;
182                    word_start = cp + 1;
183                    word = cmd_start = wp + 1;
184                }
185                else {
186                    cmd_start = ocmd_start;
187                    word_start = oword_start;
188                    word = oword;
189                }
190                *wp++ = *cp;
191                continue;
192            }
193        }
194        if (iscmdmeta(*cp))
195            cmd_start = wp + 1;
196        /* Don't quote '/' to make the recognize stuff work easily */
197        /* Don't quote '$' in double quotes */
198        *wp = *cp | (qu && *cp != '/'  && (*cp != '$' || qu != '"') ?
199                     QUOTE : 0);
200        if (ismetahash(*wp) /* || isaset(cmd_start, wp + 1) */)
201            word = wp + 1, word_start = cp + 1;
202        wp++;
203        if (cmap(qu, _ESC))
204            qu = 0;
205      }
206    *wp = 0;
207
208    /* move the word_start after the quote */
209    if (*word_start == qu)
210        word_start++;
211
212#ifdef masscomp
213    /*
214     * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning
215     * the "overuse of registers". According to the compiler release notes,
216     * incorrect code may be produced unless the offending expression is
217     * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
218     */
219    space_left = QLINESIZE - 1;
220    space_left -= word - qline;
221#else
222    space_left = QLINESIZE - 1 - (word - qline);
223#endif
224
225    looking = starting_a_command(word - 1, qline) ?
226        TW_COMMAND : TW_ZERO;
227    wordp = word;
228
229#ifdef TDEBUG
230    xprintf("starting_a_command %d\n", looking);
231    xprintf("\ncmd_start:%S:\n", cmd_start);
232    xprintf("qline:%S:\n", qline);
233    xprintf("qline:");
234    for (wp = qline; *wp; wp++)
235        xprintf("%c", *wp & QUOTE ? '-' : ' ');
236    xprintf(":\n");
237    xprintf("word:%S:\n", word);
238    xprintf("word:");
239    /* Must be last, so wp is still pointing to the end of word */
240    for (wp = word; *wp; wp++)
241        xprintf("%c", *wp & QUOTE ? '-' : ' ');
242    xprintf(":\n");
243#endif
244
245    if (command == RECOGNIZE || command == LIST || command == SPELL) {
246#ifdef TDEBUG
247        xprintf("complete %d ", looking);
248#endif
249        looking = tw_complete(cmd_start, &wordp, &pat, looking, &suf);
250#ifdef TDEBUG
251        xprintf("complete %d %S\n", looking, pat);
252#endif
253    }
254
255    switch ((int) command) {
256        Char    buffer[FILSIZ + 1], *bptr;
257        Char   *slshp;
258        Char   *items[2], **ptr;
259        int     i, count;
260
261    case RECOGNIZE:
262    case RECOGNIZE_ALL:
263        if (adrof(STRautocorrect)) {
264            if ((slshp = Strrchr(wordp, '/')) != NULL && slshp[1] != '\0') {
265                SearchNoDirErr = 1;
266                for (bptr = wordp; bptr < slshp; bptr++) {
267                    /*
268                     * do not try to correct spelling of words containing
269                     * globbing characters
270                     */
271                    if (isglob(*bptr)) {
272                        SearchNoDirErr = 0;
273                        break;
274                    }
275                }
276            }
277        }
278        else
279            slshp = STRNULL;
280        search_ret = t_search(wordp, wp, RECOGNIZE, space_left, looking, 1,
281                              pat, suf);
282        SearchNoDirErr = 0;
283
284        if (search_ret == -2) {
285            Char    rword[FILSIZ + 1];
286
287            (void) Strcpy(rword, slshp);
288            if (slshp != STRNULL)
289                *slshp = '\0';
290            search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking);
291            if (search_ret == 1) {
292                /* get rid of old word */
293                DeleteBack(str_end - word_start);
294                (void) Strcat(wordp, rword);
295                /* insert newly spelled word */
296                if (InsertStr(quote_meta(wordp, qu, 0)) < 0)   
297                    return -1;  /* error inserting */
298                wp = wordp + Strlen(wordp);
299                search_ret = t_search(wordp, wp, RECOGNIZE, space_left,
300                                      looking, 1, pat, suf);
301            }
302        }
303
304        /*
305         * Change by Christos Zoulas: if the name has metachars in it, quote
306         * the metachars, but only if we are outside quotes.
307         * We don't quote the last space if we had a unique match and
308         * addsuffix was set. Otherwise the last space was part of a word.
309         */
310        if (*wp && InsertStr(quote_meta(wp, qu, search_ret == 1 &&
311                                             (bool) is_set(STRaddsuffix))) < 0)
312            /* put it in the input buffer */
313            return -1;          /* error inserting */
314        return search_ret;
315
316    case SPELL:
317        for (bptr = word_start; bptr < str_end; bptr++) {
318            /*
319             * do not try to correct spelling of words containing globbing
320             * characters
321             */
322            if (isglob(*bptr))
323                return 0;
324        }
325        search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking);
326        if (search_ret == 1) {
327            /* get rid of old word */
328            DeleteBack(str_end - word_start);   
329            /* insert newly spelled word */
330#ifdef notdef
331            /*
332             * We don't want to quote spelling stuff, otherwise
333             * $OHME -> \$HOME
334             */
335            if (InsertStr(quote_meta(wordp, qu, 0)) < 0)
336                return -1;      /* error inserting */
337#endif
338            if (InsertStr(wordp) < 0)
339                return -1;      /* error inserting */
340        }
341        return search_ret;
342
343    case PRINT_HELP:
344        do_help(cmd_start);
345        return 1;
346
347    case GLOB:
348    case GLOB_EXPAND:
349        (void) Strncpy(buffer, wordp, FILSIZ + 1);
350        items[0] = buffer;
351        items[1] = NULL;
352        ptr = items;
353        count = (looking == TW_COMMAND && Strchr(wordp, '/') == 0) ?
354                c_glob(&ptr) :
355                t_glob(&ptr, looking == TW_COMMAND);
356        if (count > 0) {
357            if (command == GLOB)
358                print_by_column(STRNULL, ptr, count, 0);
359            else {
360                DeleteBack(str_end - word_start);/* get rid of old word */
361                for (i = 0; i < count; i++)
362                    if (ptr[i] && *ptr[i]) {
363                        if (InsertStr(quote_meta(ptr[i], qu, 0)) < 0 ||
364                            InsertStr(STRspace) < 0) {
365                            blkfree(ptr);
366                            return (-1);
367                        }
368                    }
369            }
370            blkfree(ptr);
371        }
372        return count;
373
374    case VARS_EXPAND:
375        if (dollar(buffer, wordp)) {
376            DeleteBack(str_end - word_start);
377            if (InsertStr(quote_meta(buffer, qu, 0)) < 0)
378                return (-1);
379            return (1);
380        }
381        return (0);
382
383    case PATH_NORMALIZE:
384        if ((bptr = dnormalize(wordp, symlinks == SYM_IGNORE ||
385                                      symlinks == SYM_EXPAND)) != NULL) {
386            (void) Strcpy(buffer, bptr);
387            xfree((ptr_t) bptr);
388            DeleteBack(str_end - word_start);
389            if (InsertStr(quote_meta(buffer, qu, 0)) < 0)
390                return (-1);
391            return (1);
392        }
393        return (0);
394
395    case LIST:
396    case LIST_ALL:
397        search_ret = t_search(wordp, wp, LIST, space_left, looking, 1,
398                              pat, suf);
399        return search_ret;
400
401    default:
402        xprintf("tcsh: Internal match error.\n");
403        return 1;
404
405    }
406} /* end tenematch */
407
408
409/* t_glob():
410 *      Return a list of files that match the pattern
411 */
412static int
413t_glob(v, cmd)
414    register Char ***v;
415    int cmd;
416{
417    jmp_buf_t osetexit;
418
419    if (**v == 0)
420        return (0);
421    gflag = 0, tglob(*v);
422    if (gflag) {
423        getexit(osetexit);      /* make sure to come back here */
424        if (setexit() == 0)
425            *v = globall(*v);
426        resexit(osetexit);
427        gargv = 0;
428        if (haderr) {
429            haderr = 0;
430            NeedsRedraw = 1;
431            return (-1);
432        }
433        if (*v == 0)
434            return (0);
435    }
436    else
437        return (0);
438
439    if (cmd) {
440        Char **av = *v, *p;
441        int fwd, i, ac = gargc;
442
443        for (i = 0, fwd = 0; i < ac; i++)
444            if (!executable(NULL, av[i], 0)) {
445                fwd++;         
446                p = av[i];
447                av[i] = NULL;
448                xfree((ptr_t) p);
449            }
450            else if (fwd)
451                av[i - fwd] = av[i];
452
453        if (fwd)
454            av[i - fwd] = av[i];
455        gargc -= fwd;
456        av[gargc] = NULL;
457    }
458
459    return (gargc);
460} /* end t_glob */
461
462
463/* c_glob():
464 *      Return a list of commands that match the pattern
465 */
466static int
467c_glob(v)
468    register Char ***v;
469{
470    Char *pat = **v, *cmd, **av;
471    Char dir[MAXPATHLEN+1];
472    int flag, at, ac;
473
474    if (pat == NULL)
475        return (0);
476
477    ac = 0;
478    at = 10;
479    av = (Char **) xmalloc((size_t) (at * sizeof(Char *)));
480    av[ac] = NULL;
481
482    tw_cmd_start(NULL, NULL);
483    while ((cmd = tw_cmd_next(dir, &flag)) != NULL)
484        if (Gmatch(cmd, pat)) {
485            if (ac + 1 >= at) {
486                at += 10;
487                av = (Char **) xrealloc((ptr_t) av,
488                                        (size_t) (at * sizeof(Char *)));
489            }
490            av[ac++] = Strsave(cmd);
491            av[ac] = NULL;
492        }
493    tw_dir_end();
494    *v = av;
495
496    return (ac);
497} /* end c_glob */
498
499
500/* quote_meta():
501 *      quote (\) the meta-characters in a word
502 *      except trailing space if trail_space is set
503 *      return pointer to quoted word in static storage
504 */
505static Char *
506quote_meta(word, qu, trail_space)
507    Char   *word;
508    int     qu;
509    bool    trail_space;
510{
511    static Char buffer[2 * FILSIZ + 1], *bptr, *wptr;
512
513    for (bptr = buffer, wptr = word; *wptr != '\0';) {
514        if (bptr > buffer + 2 * FILSIZ - 4)
515            break;
516         
517        if (qu && *wptr == qu) {
518            *bptr++ = qu;
519            *bptr++ = '\\';
520            *bptr++ = qu;
521        }
522        if (!qu &&
523            (cmap(*wptr, _META | _DOL | _Q | _Q1 | _ESC | _GLOB) ||
524             *wptr == HIST || *wptr == HISTSUB) &&
525            (*wptr != ' ' || !trail_space || *(wptr + 1) != '\0') &&
526             *wptr != '#')
527            *bptr++ = '\\';
528        *bptr++ = *wptr++;
529        if (cmap(qu, _ESC))
530          qu = 0;
531    }
532    *bptr = '\0';
533    return (buffer);
534} /* end quote_meta */
535
536
537
538/* is_prefix():
539 *      return true if check matches initial chars in template
540 *      This differs from PWB imatch in that if check is null
541 *      it matches anything
542 */
543static int
544is_prefix(check, template)
545    register Char *check, *template;
546{
547    for (; *check; check++, template++)
548        if ((*check & TRIM) != (*template & TRIM))
549            return (FALSE);
550    return (TRUE);
551} /* end is_prefix */
552
553
554/* is_suffix():
555 *      Return true if the chars in template appear at the
556 *      end of check, I.e., are it's suffix.
557 */
558static int
559is_suffix(check, template)
560    register Char *check, *template;
561{
562    register Char *t, *c;
563
564    for (t = template; *t++;)
565        continue;
566    for (c = check; *c++;)
567        continue;
568    for (;;) {
569        if (t == template)
570            return 1;
571        --t;
572        --c;
573        if (c == check || (*t & TRIM) != (*c & TRIM))
574            return 0;
575    }
576} /* end is_suffix */
577
578
579/* ignored():
580 *      Return true if this is an ignored entry
581 */
582static int
583ignored(entry)
584    register Char *entry;
585{
586    struct varent *vp;
587    register Char **cp;
588
589    if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
590        return (FALSE);
591    for (; *cp != NULL; cp++)
592        if (is_suffix(entry, *cp))
593            return (TRUE);
594    return (FALSE);
595} /* end ignored */
596
597
598
599/* starting_a_command():
600 *      return true if the command starting at wordstart is a command
601 */
602int
603starting_a_command(wordstart, inputline)
604    register Char *wordstart, *inputline;
605{
606    register Char *ptr, *ncmdstart;
607    int     count;
608    static  Char
609            cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
610            cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
611
612    /*
613     * Find if the number of backquotes is odd or even.
614     */
615    for (ptr = wordstart, count = 0;
616         ptr >= inputline;
617         count += (*ptr-- == '`'))
618        continue;
619    /*
620     * if the number of backquotes is even don't include the backquote char in
621     * the list of command starting delimiters [if it is zero, then it does not
622     * matter]
623     */
624    ncmdstart = cmdstart + EVEN(count);
625
626    /*
627     * look for the characters previous to this word if we find a command
628     * starting delimiter we break. if we find whitespace and another previous
629     * word then we are not a command
630     *
631     * count is our state machine: 0 looking for anything 1 found white-space
632     * looking for non-ws
633     */
634    for (count = 0; wordstart >= inputline; wordstart--) {
635        if (*wordstart == '\0')
636            continue;
637        if (Strchr(ncmdstart, *wordstart))
638            break;
639        /*
640         * found white space
641         */
642        if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
643            count = 1;
644        if (count == 1 && !ptr)
645            return (FALSE);
646    }
647
648    if (wordstart > inputline)
649        switch (*wordstart) {
650        case '&':               /* Look for >& */
651            while (wordstart > inputline &&
652                   (*--wordstart == ' ' || *wordstart == '\t'))
653                continue;
654            if (*wordstart == '>')
655                return (FALSE);
656            break;
657        case '(':               /* check for foreach, if etc. */
658            while (wordstart > inputline &&
659                   (*--wordstart == ' ' || *wordstart == '\t'))
660                continue;
661            if (!iscmdmeta(*wordstart) &&
662                (*wordstart != ' ' && *wordstart != '\t'))
663                return (FALSE);
664            break;
665        default:
666            break;
667        }
668    return (TRUE);
669} /* end starting_a_command */
670
671
672/* recognize():
673 *      Object: extend what user typed up to an ambiguity.
674 *      Algorithm:
675 *      On first match, copy full entry (assume it'll be the only match)
676 *      On subsequent matches, shorten exp_name to the first
677 *      character mismatch between exp_name and entry.
678 *      If we shorten it back to the prefix length, stop searching.
679 */
680static int
681recognize(exp_name, entry, name_length, numitems)
682    Char   *exp_name, *entry;
683    int     name_length, numitems;
684{
685    if (numitems == 1)          /* 1st match */
686        copyn(exp_name, entry, MAXNAMLEN);
687    else {                      /* 2nd and subsequent matches */
688        register Char *x, *ent;
689        register int len = 0;
690
691        for (x = exp_name, ent = entry;
692             *x && (*x & TRIM) == (*ent & TRIM); x++, len++, ent++)
693            continue;
694        *x = '\0';              /* Shorten at 1st char diff */
695        if (len == name_length) /* Ambiguous to prefix? */
696            return (-1);        /* So stop now and save time */
697    }
698    return (0);
699} /* end recognize */
700
701
702/* tw_collect_items():
703 *      Collect items that match target.
704 *      SPELL command:
705 *              Returns the spelling distance of the closest match.
706 *      else
707 *              Returns the number of items found.
708 *              If none found, but some ignored items were found,
709 *              It returns the -number of ignored items.
710 */
711static int
712tw_collect_items(command, looking, exp_dir, exp_name, target, pat, flags)
713    COMMAND command;
714    int looking;
715    Char *exp_dir, *exp_name, *target, *pat;
716    int flags;
717
718{
719    int done = FALSE;                    /* Search is done */
720    int showdots;                        /* Style to show dot files */
721    int nignored = 0;                    /* Number of fignored items */
722    int numitems = 0;                    /* Number of matched items */
723    int name_length = Strlen(target);    /* Length of prefix (file name) */
724    int exec_check = flags & TW_EXEC_CHK;/* need to check executability */
725    int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
726    int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
727    int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
728    int gpat       = flags & TW_PAT_OK;  /* Match against a pattern */
729    int ignoring   = flags & TW_IGN_OK;  /* Use fignore? */
730    int d = 4, nd;                       /* Spelling distance */
731    Char *entry, *ptr;
732    Char buf[MAXPATHLEN+1];
733    struct varent *vp;
734    int len;
735    flags = 0;
736
737    if ((vp = adrof(STRshowdots)) != NULL) {
738        if (vp->vec[0][0] == '-' && vp->vec[0][1] == 'A' &&
739            vp->vec[0][2] == '\0')
740            showdots = DOT_NOT;
741        else
742            showdots = DOT_ALL;
743    }
744    else
745        showdots = DOT_NONE;
746
747    while (!done && (entry = (*tw_next_entry[looking])(exp_dir, &flags))) {
748#ifdef TDEBUG
749        xprintf("entry = %S\n", entry);
750#endif
751        switch (looking) {
752        case TW_FILE:
753        case TW_DIRECTORY:
754        case TW_TEXT:
755            /*
756             * Don't match . files on null prefix match
757             */
758            if (showdots == DOT_NOT && (ISDOT(entry) || ISDOTDOT(entry)))
759                done = TRUE;
760            if (name_length == 0 && entry[0] == '.' && showdots == DOT_NONE)
761                done = TRUE;
762            break;
763
764        case TW_COMMAND:
765            exec_check = flags & TW_EXEC_CHK;
766            dir_ok = flags & TW_DIR_OK;
767            break;
768
769        default:
770            break;
771        }
772
773        if (done) {
774            done = FALSE;
775            continue;
776        }
777
778        switch (command) {
779
780        case SPELL:             /* correct the spelling of the last bit */
781            if (name_length == 0) {/* zero-length word can't be misspelled */
782                exp_name[0] = '\0';/* (not trying is important for ~) */
783                d = 0;
784                done = TRUE;
785                break;
786            }
787            if (gpat && !Gmatch(entry, pat))
788                break;
789            nd = spdist(entry, target); /* test the entry against original */
790            if (nd <= d && nd != 4) {
791                if (!(exec_check && !executable(exp_dir, entry, dir_ok))) {
792                    (void) Strcpy(exp_name, entry);
793                    d = nd;
794                    if (d == 0) /* if found it exactly */
795                        done = TRUE;
796                }
797            }
798            else if (nd == 4) {
799                if (spdir(exp_name, exp_dir, entry, target)) {
800                    if (exec_check && !executable(exp_dir, exp_name, dir_ok))
801                        break;
802#ifdef notdef
803                    /*
804                     * We don't want to stop immediately, because
805                     * we might find an exact/better match later.
806                     */
807                    d = 0;
808                    done = TRUE;
809#endif
810                    d = 3;
811                }
812            }
813            break;
814
815        case LIST:
816        case RECOGNIZE:
817
818            if (!is_prefix(target, entry))
819                break;
820
821            if (exec_check && !executable(exp_dir, entry, dir_ok))
822                break;
823
824            if (dir_check && !isadirectory(exp_dir, entry))
825                break;
826           
827            if (text_check && isadirectory(exp_dir, entry))
828                break;
829
830            if (gpat && !Gmatch(entry, pat) && !isadirectory(exp_dir, entry))
831                break;
832
833            /*
834             * Remove duplicates in command listing and completion
835             */
836            if (looking == TW_COMMAND || command == LIST) {
837                copyn(buf, entry, MAXPATHLEN);
838                len = Strlen(buf);
839                switch (looking) {
840                case TW_COMMAND:
841                    if (!(dir_ok && exec_check))
842                        break;
843                    if (filetype(exp_dir, entry) == '/') {
844                        buf[len++] = '/';
845                        buf[len] = '\0';
846                    }
847                    break;
848
849                case TW_FILE:
850                case TW_DIRECTORY:
851                    buf[len++] = filetype(exp_dir, entry);
852                    buf[len] = '\0';
853                    break;
854
855                default:
856                    break;
857                }
858                if (looking == TW_COMMAND && tw_item_find(buf))
859                    break;
860                else {
861                    /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
862                    ptr = tw_item_add(len + 3);
863                    copyn(ptr, buf, MAXPATHLEN);
864                    if (command == LIST)
865                        numitems++;
866                }
867            }
868                   
869            if (command == RECOGNIZE) {
870                if (ignoring && ignored(entry)) {
871                    nignored++;
872                    break;
873                }
874                if (is_set(STRrecexact)) {
875                    if (StrQcmp(target, entry) == 0) {  /* EXACT match */
876                        copyn(exp_name, entry, MAXNAMLEN);
877                        numitems = 1;   /* fake into expanding */
878                        non_unique_match = TRUE;
879                        done = TRUE;
880                        break;
881                    }
882                }
883                if (recognize(exp_name, entry, name_length, ++numitems))
884                    done = TRUE;
885            }
886            break;
887
888        default:
889            break;
890        }
891#ifdef TDEBUG
892        xprintf("done entry = %S\n", entry);
893#endif
894    }
895    if (command == SPELL)
896        return d;
897    else {
898        if (ignoring && numitems == 0 && nignored > 0)
899            return -nignored;
900        else
901            return numitems;
902    }
903}
904
905
906/* tw_suffix():
907 *      Find and return the appropriate suffix character
908 */
909static Char
910tw_suffix(looking, exp_dir, exp_name, target, name)
911    int looking;
912    Char *exp_dir, *exp_name, *target, *name;
913{   
914    Char *ptr;
915    struct varent *vp;
916
917    switch (looking) {
918
919    case TW_LOGNAME:
920        return '/';
921
922    case TW_VARIABLE:
923        /*
924         * Don't consider array variables or empty variables
925         */
926        if ((vp = adrof(exp_name)) != NULL) {
927            if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
928                vp->vec[1] != NULL)
929                return ' ';
930        }
931        else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
932            return ' ';
933        *--target = '\0';
934        (void) Strcat(exp_dir, name);
935        return isadirectory(exp_dir, ptr) ? '/' : ' ';
936
937
938    case TW_DIRECTORY:
939        return '/';
940
941    case TW_COMMAND:
942    case TW_FILE:
943        return isadirectory(exp_dir, exp_name) ? '/' : ' ';
944
945    case TW_ALIAS:
946    case TW_VARLIST:
947    case TW_WORDLIST:
948    case TW_SHELLVAR:
949    case TW_ENVVAR:
950    case TW_USER:
951    case TW_BINDING:
952    case TW_LIMIT:
953    case TW_SIGNAL:
954    case TW_JOB:
955    case TW_COMPLETION:
956    case TW_TEXT:
957        return ' ';
958
959    default:
960        return '\0';
961    }
962} /* end tw_suffix */
963
964
965/* tw_fixword():
966 *      Repair a word after a spalling or a recognizwe
967 */
968static void
969tw_fixword(looking, word, dir, exp_name, max_word_length)
970    int looking;
971    Char *word, *dir, *exp_name;
972    int max_word_length;
973{
974    Char *ptr;
975
976    switch (looking) {
977    case TW_LOGNAME:
978        copyn(word, STRtilde, 1);
979        break;
980   
981    case TW_VARIABLE:
982        if ((ptr = Strrchr(word, '$')) != NULL)
983            *++ptr = '\0';      /* Delete after the dollar */
984        else
985            word[0] = '\0';
986        break;
987
988    case TW_DIRECTORY:
989    case TW_FILE:
990    case TW_TEXT:
991        copyn(word, dir, max_word_length);      /* put back dir part */
992        break;
993
994    default:
995        word[0] = '\0';
996        break;
997    }
998
999    catn(word, exp_name, max_word_length);      /* add extended name */
1000} /* end tw_fixword */
1001
1002
1003/* tw_collect():
1004 *      Collect items. Return -1 in case we were interrupted or
1005 *      the return value of tw_collect
1006 *      This is really a wrapper for tw_collect_items, serving two
1007 *      purposes:
1008 *              1. Handles interrupt cleanups.
1009 *              2. Retries if we had no matches, but there were ignored matches
1010 */
1011static int
1012tw_collect(command, looking, exp_dir, exp_name, target, pat, flags, dir_fd)
1013    COMMAND command;
1014    int looking;
1015    Char *exp_dir, *exp_name, *target, *pat;
1016    int flags;
1017    DIR *dir_fd;
1018{
1019    static int ni;      /* static so we don't get clobbered */
1020    jmp_buf_t osetexit;
1021
1022#ifdef TDEBUG
1023    xprintf("target = %S\n", target);
1024#endif
1025    ni = 0;
1026    getexit(osetexit);
1027    for (;;) {
1028        (*tw_start_entry[looking])(dir_fd, pat);
1029        InsideCompletion = 1;
1030        if (setexit()) {
1031            /* interrupted, clean up */
1032            resexit(osetexit);
1033            InsideCompletion = 0;
1034            haderr = 0;
1035            (*tw_end_entry[looking])();
1036            /* flag error */
1037            return(-1);
1038        }
1039        if ((ni = tw_collect_items(command, looking, exp_dir, exp_name,
1040                                   target, pat,
1041                                   ni >= 0 ? flags :
1042                                        flags & ~TW_IGN_OK)) >= 0) {
1043            resexit(osetexit);
1044            InsideCompletion = 0;
1045            (*tw_end_entry[looking])();
1046            return(ni);
1047        }
1048    }
1049} /* end tw_collect */
1050
1051
1052/* tw_list_items():
1053 *      List the items that were found
1054 */
1055static void
1056tw_list_items(looking, numitems, list_max)
1057    int looking, numitems, list_max;
1058{
1059    Char *ptr;
1060    int max_items = 0;
1061
1062    if ((ptr = value(STRlistmax)) != STRNULL) {
1063        while (*ptr) {
1064            if (!Isdigit(*ptr)) {
1065                max_items = 0;
1066                break;
1067            }
1068            max_items = max_items * 10 + *ptr++ - '0';
1069        }
1070    }
1071
1072    if ((max_items > 0) && (numitems > max_items) && list_max) {
1073        char    tc;
1074
1075        xprintf("There are %d items, list them anyway? [n/y] ", numitems);
1076        flush();
1077        /* We should be in Rawmode here, so no \n to catch */
1078        (void) read(SHIN, &tc, 1);
1079        xprintf("%c\r\n", tc);  /* echo the char, do a newline */
1080        if ((tc != 'y') && (tc != 'Y'))
1081            return;
1082    }
1083
1084    if (looking != TW_SIGNAL)
1085        qsort((ptr_t) tw_item_get(), (size_t) numitems, sizeof(Char *),
1086              (int (*) __P((const void *, const void *))) fcompare);
1087    if (looking != TW_JOB)
1088        print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
1089    else {
1090        /*
1091         * print one item on every line because jobs can have spaces
1092         * and it is confusing.
1093         */
1094        int i;
1095        Char **w = tw_item_get();
1096
1097        for (i = 0; i < numitems; i++) {
1098            xprintf("%S", w[i]);
1099            if (Tty_raw_mode)
1100                xputchar('\r');
1101            xputchar('\n');
1102        }
1103    }
1104} /* end tw_list_items */
1105
1106
1107/* t_search():
1108 *      Perform a RECOGNIZE, LIST or SPELL command on string "word".
1109 *
1110 *      Return value:
1111 *              >= 0:   SPELL command: "distance" (see spdist())
1112 *                              other: No. of items found
1113 *               < 0:   Error (message or beep is output)
1114 */
1115/*ARGSUSED*/
1116int
1117t_search(word, wp, command, max_word_length, looking, list_max, pat, suf)
1118    Char   *word, *wp;          /* original end-of-word */
1119    COMMAND command;
1120    int     max_word_length, looking, list_max;
1121    Char   *pat;
1122    int     suf;
1123{
1124    int     numitems,                   /* Number of items matched */
1125            flags = 0,                  /* search flags */
1126            gpat = pat[0] != '\0',      /* Glob pattern search */
1127            nd;                         /* Normalized directory return */
1128    Char    exp_dir[FILSIZ + 1],        /* dir after ~ expansion */
1129            dir[FILSIZ + 1],            /* /x/y/z/ part in /x/y/z/f */
1130            exp_name[MAXNAMLEN + 1],    /* the recognized (extended) */
1131            name[MAXNAMLEN + 1],        /* f part in /d/d/d/f name */
1132           *target;                     /* Target to expand/correct/list */
1133    DIR    *dir_fd = NULL;     
1134
1135    /*
1136     * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
1137     * dump core when interrupted
1138     */
1139    tw_item_free();
1140
1141    non_unique_match = FALSE;   /* See the recexact code below */
1142
1143    extract_dir_and_name(word, dir, name);
1144    exp_dir[0] = '\0';
1145
1146    /*
1147     * Try to figure out what we should be looking for
1148     */
1149
1150    switch (looking) {
1151    case TW_NONE:
1152        return -1;
1153
1154    case TW_ZERO:
1155        looking = TW_FILE;
1156        break;
1157
1158    case TW_COMMAND:
1159        if (Strchr(word, '/')) {
1160            looking = TW_FILE;
1161            flags |= TW_EXEC_CHK;
1162            flags |= TW_DIR_OK;
1163        }
1164#ifdef notdef
1165        /* PWP: don't even bother when doing ALL of the commands */
1166        if (looking == TW_COMMAND && (*word == '\0'))
1167            return (-1);
1168#endif
1169        break;
1170    case TW_PATHNAME:
1171        gpat = 0;       /* pattern holds the pathname to be used */
1172        copyn(exp_dir, pat, MAXNAMLEN);
1173        catn(exp_dir, dir, MAXNAMLEN);
1174        dir[0] = '\0';
1175        break;
1176
1177    case TW_VARLIST:
1178    case TW_WORDLIST:
1179        gpat = 0;       /* pattern holds the name of the variable */
1180        break;
1181
1182    case TW_EXPLAIN:
1183        if (command == LIST && pat != NULL) {
1184            xprintf("%S", pat);
1185            if (Tty_raw_mode)
1186                xputchar('\r');
1187            xputchar('\n');
1188        }
1189        return 2;
1190
1191    default:
1192        break;
1193    }
1194
1195    /*
1196     * let fignore work only when we are not using a pattern
1197     */
1198    flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
1199
1200    if ((*word == '~') && (Strchr(word, '/') == NULL)) {
1201        looking = TW_LOGNAME;
1202        target = name;
1203    }
1204    else if ((target = Strrchr(name, '$')) != 0 &&
1205             (Strchr(name, '/') == NULL)) {
1206        target++;
1207        looking = TW_VARIABLE;
1208    }
1209    else
1210        target = name;
1211
1212#ifdef TDEBUG
1213    xprintf("looking = %d\n", looking);
1214#endif
1215
1216    switch (looking) {
1217    case TW_ALIAS:
1218    case TW_SHELLVAR:
1219    case TW_ENVVAR:
1220    case TW_BINDING:
1221    case TW_LIMIT:
1222    case TW_SIGNAL:
1223    case TW_JOB:
1224    case TW_COMPLETION:
1225        break;
1226
1227
1228    case TW_VARIABLE:
1229        if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1230            return nd;
1231        break;
1232
1233    case TW_DIRECTORY:
1234        flags |= TW_DIR_CHK;
1235
1236#ifdef notyet
1237        /*
1238         * This is supposed to expand the directory stack.
1239         * Problems:
1240         * 1. Slow
1241         * 2. directories with the same name
1242         */
1243        flags |= TW_DIR_OK;
1244#endif
1245#ifdef notyet
1246        /*
1247         * Supposed to do delayed expansion, but it is inconsistent
1248         * from a user-interface point of view, since it does not
1249         * immediately obey addsuffix
1250         */
1251        if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1252            return nd;
1253        if (isadirectory(exp_dir, name)) {
1254            if (exp_dir[0] != '\0' || name[0] != '\0') {
1255                catn(dir, name, MAXNAMLEN);
1256                if (dir[Strlen(dir) - 1] != '/')
1257                    catn(dir, STRslash, MAXNAMLEN);
1258                if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1259                    return nd;
1260                if (word[Strlen(word) - 1] != '/')
1261                    catn(word, STRslash, MAXNAMLEN);
1262                name[0] = '\0';
1263            }
1264        }
1265#endif
1266        if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1267            return nd;
1268        break;
1269
1270    case TW_TEXT:
1271        flags |= TW_TEXT_CHK;
1272        /*FALLTHROUGH*/
1273    case TW_FILE:
1274        if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1275            return nd;
1276        break;
1277    case TW_PATHNAME:
1278        if ((dir_fd = opendir(short2str(exp_dir))) == NULL) {
1279            xprintf("%S: %s\n", exp_dir, strerror(errno));
1280            return -1;
1281        }
1282        looking = TW_FILE;
1283        break;
1284
1285    case TW_LOGNAME:
1286        word++;
1287        /*FALLTHROUGH*/
1288    case TW_USER:
1289        /*
1290         * Check if the spelling was already correct
1291         * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
1292         */
1293        if (command == SPELL && getpwnam(short2str(word)) != NULL) {
1294#ifdef YPBUGS
1295            fix_yp_bugs();
1296#endif /* YPBUGS */
1297            return (0);
1298        }
1299        copyn(name, word, MAXNAMLEN);   /* name sans ~ */
1300        if (looking == TW_LOGNAME)
1301            word--;
1302        break;
1303
1304    case TW_COMMAND:
1305    case TW_VARLIST:
1306    case TW_WORDLIST:
1307        copyn(target, word, MAXNAMLEN); /* so it can match things */
1308        break;
1309
1310    default:
1311        xprintf("\ntcsh internal error: I don't know what I'm looking for!\n");
1312        NeedsRedraw = 1;
1313        return (-1);
1314    }
1315
1316    numitems = tw_collect(command, looking, exp_dir, exp_name,
1317                          target, pat, flags, dir_fd);
1318    if (numitems == -1)
1319        return -1;
1320
1321    switch (command) {
1322    case RECOGNIZE:
1323        if (numitems <= 0)
1324            return (numitems);
1325
1326        tw_fixword(looking, word, dir, exp_name, max_word_length);
1327
1328        if (is_set(STRaddsuffix) && numitems == 1) {
1329            Char suffix[2];
1330
1331            suffix[1] = '\0';
1332            switch (suf) {
1333            case 0:     /* Automatic suffix */
1334                suffix[0] = tw_suffix(looking, exp_dir, exp_name, target, name);
1335                break;
1336
1337            case -1:    /* No suffix */
1338                return numitems;
1339
1340            default:    /* completion specified suffix */
1341                suffix[0] = suf;
1342                break;
1343            }
1344            catn(word, suffix, max_word_length);
1345        }
1346        return numitems;
1347
1348    case LIST:
1349        tw_list_items(looking, numitems, list_max);
1350        tw_item_free();
1351        return (numitems);
1352
1353    case SPELL:
1354        tw_fixword(looking, word, dir, exp_name, max_word_length);
1355        return (numitems);
1356
1357    default:
1358        xprintf("Bad tw_command\n");
1359        return (0);
1360    }
1361} /* end t_search */
1362
1363
1364/* extract_dir_and_name():
1365 *      parse full path in file into 2 parts: directory and file names
1366 *      Should leave final slash (/) at end of dir.
1367 */
1368static void
1369extract_dir_and_name(path, dir, name)
1370    Char   *path, *dir, *name;
1371{
1372    register Char *p;
1373
1374    p = Strrchr(path, '/');
1375    if (p == NULL) {
1376        copyn(name, path, MAXNAMLEN);
1377        dir[0] = '\0';
1378    }
1379    else {
1380        p++;
1381        copyn(name, p, MAXNAMLEN);
1382        copyn(dir, path, p - path);
1383    }
1384} /* end extract_dir_and_name */
1385
1386
1387/* dollar():
1388 *      expand "/$old1/$old2/old3/"
1389 *      to "/value_of_old1/value_of_old2/old3/"
1390 */
1391static Char *
1392dollar(new, old)
1393    Char   *new, *old;
1394{
1395    Char   *var, *val, *p, save;
1396    int     space;
1397
1398    for (space = FILSIZ, p = new; *old && space > 0;)
1399        if (*old != '$') {
1400            *p++ = *old++;
1401            space--;
1402        }
1403        else {
1404            struct varent *vp;
1405
1406            /* found a variable, expand it */
1407            for (var = ++old; alnum(*old); old++)
1408                continue;
1409            save = *old;
1410            *old = '\0';
1411            vp = adrof(var);
1412            val = (!vp) ? tgetenv(var) : NULL;
1413            *old = save;
1414            if (vp) {
1415                int i;
1416                for (i = 0; vp->vec[i] != NULL; i++) {
1417                    for (val = vp->vec[i]; space > 0 && *val; space--)
1418                        *p++ = *val++;
1419                    if (vp->vec[i+1] && space > 0) {
1420                        *p++ = ' ';
1421                        space--;
1422                    }
1423                }
1424            }
1425            else if (val) {
1426                for (;space > 0 && *val; space--)
1427                    *p++ = *val++;
1428            }
1429            else {
1430                *new = '\0';
1431                return (NULL);
1432            }
1433        }
1434    *p = '\0';
1435    return (new);
1436} /* end dollar */
1437
1438
1439/* tilde():
1440 *      expand ~person/foo to home_directory_of_person/foo
1441 *      or =<stack-entry> to <dir in stack entry>
1442 */
1443static Char *
1444tilde(new, old)
1445    Char   *new, *old;
1446{
1447    register Char *o, *p;
1448
1449    switch (old[0]) {
1450    case '~':
1451        for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++)
1452            continue;
1453        *p = '\0';
1454        if (gethdir(new)) {
1455            new[0] = '\0';
1456            return NULL;
1457        }
1458        (void) Strcat(new, o);
1459        return new;
1460
1461    case '=':
1462        if ((p = globequal(new, old)) == NULL) {
1463            *new = '\0';
1464            return NULL;
1465        }
1466        if (p == new)
1467            return new;
1468        /*FALLTHROUGH*/
1469
1470    default:
1471        (void) Strcpy(new, old);
1472        return new;
1473    }
1474} /* end tilde */
1475
1476
1477/* expand_dir():
1478 *      Open the directory given, expanding ~user and $var
1479 *      Optionally normalize the path given
1480 */
1481static int
1482expand_dir(dir, edir, dfd, cmd)
1483    Char   *dir, *edir;
1484    DIR   **dfd;
1485    COMMAND cmd;
1486{
1487    Char   *nd = NULL;
1488    Char    tdir[MAXPATHLEN + 1];
1489
1490    if ((dollar(tdir, dir) == 0) ||
1491        (tilde(edir, tdir) == 0) ||
1492        !(nd = dnormalize(*edir ? edir : STRdot, symlinks == SYM_IGNORE ||
1493                                                 symlinks == SYM_EXPAND)) ||
1494        ((*dfd = opendir(short2str(nd))) == NULL)) {
1495        xfree((ptr_t) nd);
1496        if (cmd == SPELL || SearchNoDirErr)
1497            return (-2);
1498        /*
1499         * From: Amos Shapira <amoss@cs.huji.ac.il>
1500         * Print a better message when completion fails
1501         */
1502        xprintf("\n%S %s\n",
1503                *edir ? edir :
1504                (*tdir ? tdir : dir),
1505                (errno == ENOTDIR ? "not a directory" :
1506                (errno == ENOENT ? "not found" : "unreadable")));
1507        NeedsRedraw = 1;
1508        return (-1);
1509    }
1510    if (nd) {
1511        if (*dir != '\0') {
1512            Char   *s, *d, *p;
1513
1514            /*
1515             * Copy and append a / if there was one
1516             */
1517            for (p = edir; *p; p++)
1518                continue;
1519            if (*--p == '/') {
1520                for (p = nd; *p; p++)
1521                    continue;
1522                if (*--p != '/')
1523                    p = NULL;
1524            }
1525            for (d = edir, s = nd; (*d++ = *s++) != '\0';)
1526                continue;
1527            if (!p) {
1528                *d-- = '\0';
1529                *d = '/';
1530            }
1531        }
1532        xfree((ptr_t) nd);
1533    }
1534    return 0;
1535} /* end expand_dir */
1536
1537
1538/* nostat():
1539 *      Returns true if the directory should not be stat'd,
1540 *      false otherwise.
1541 *      This way, things won't grind to a halt when you complete in /afs
1542 *      or very large directories.
1543 */
1544static bool
1545nostat(dir)
1546     Char *dir;
1547{
1548    struct varent *vp;
1549    register Char **cp;
1550
1551    if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
1552        return FALSE;
1553    for (; *cp != NULL; cp++) {
1554        if (Strcmp(*cp, STRstar) == 0)
1555            return TRUE;
1556        if (Gmatch(dir, *cp))
1557            return TRUE;
1558    }
1559    return FALSE;
1560} /* end nostat */
1561
1562
1563/* filetype():
1564 *      Return a character that signifies a filetype
1565 *      symbology from 4.3 ls command.
1566 */
1567static  Char
1568filetype(dir, file)
1569    Char   *dir, *file;
1570{
1571    if (dir) {
1572        Char    path[512];
1573        char   *ptr;
1574        struct stat statb;
1575
1576        if (nostat(dir)) return(' ');
1577
1578        (void) Strcpy(path, dir);
1579        catn(path, file, sizeof(path) / sizeof(Char));
1580
1581        if (lstat(ptr = short2str(path), &statb) != -1)
1582            /* see above #define of lstat */
1583        {
1584#ifdef S_ISLNK
1585            if (S_ISLNK(statb.st_mode)) {       /* Symbolic link */
1586                if (adrof(STRlistlinks)) {
1587                    if (stat(ptr, &statb) == -1)
1588                        return ('&');
1589                    else if (S_ISDIR(statb.st_mode))
1590                        return ('>');
1591                    else
1592                        return ('@');
1593                }
1594                else
1595                    return ('@');
1596            }
1597#endif
1598#ifdef S_ISSOCK
1599            if (S_ISSOCK(statb.st_mode))        /* Socket */
1600                return ('=');
1601#endif
1602#ifdef S_ISFIFO
1603            if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
1604                return ('|');
1605#endif
1606#ifdef S_ISHIDDEN
1607            if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
1608                return ('+');
1609#endif
1610#ifdef S_ISCDF 
1611            if (S_ISCDF(statb.st_mode)) /* Context Dependent Files [hpux] */
1612                return ('+');
1613#endif
1614#ifdef S_ISNWK
1615            if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
1616                return (':');
1617#endif
1618#ifdef S_ISCHR
1619            if (S_ISCHR(statb.st_mode)) /* char device */
1620                return ('%');
1621#endif
1622#ifdef S_ISBLK
1623            if (S_ISBLK(statb.st_mode)) /* block device */
1624                return ('#');
1625#endif
1626#ifdef S_ISDIR
1627            if (S_ISDIR(statb.st_mode)) /* normal Directory */
1628                return ('/');
1629#endif
1630            if (statb.st_mode & 0111)
1631                return ('*');
1632        }
1633    }
1634    return (' ');
1635} /* end filetype */
1636
1637
1638/* isadirectory():
1639 *      Return trus if the file is a directory
1640 */
1641static int
1642isadirectory(dir, file)         /* return 1 if dir/file is a directory */
1643    Char   *dir, *file;         /* uses stat rather than lstat to get dest. */
1644{
1645    if (dir) {
1646        Char    path[MAXPATHLEN];
1647        struct stat statb;
1648
1649        (void) Strcpy(path, dir);
1650        catn(path, file, sizeof(path) / sizeof(Char));
1651        if (stat(short2str(path), &statb) >= 0) {       /* resolve through
1652                                                         * symlink */
1653#ifdef S_ISSOCK
1654            if (S_ISSOCK(statb.st_mode))        /* Socket */
1655                return 0;
1656#endif
1657#ifdef S_ISFIFO
1658            if (S_ISFIFO(statb.st_mode))        /* Named Pipe */
1659                return 0;
1660#endif
1661            if (S_ISDIR(statb.st_mode)) /* normal Directory */
1662                return 1;
1663        }
1664    }
1665    return 0;
1666} /* end isadirectory */
1667
1668
1669/* print_by_column():
1670 *      Print sorted down columns
1671 */
1672void
1673print_by_column(dir, items, count, no_file_suffix)
1674    register Char *dir, *items[];
1675    int     count, no_file_suffix;
1676{
1677    register int i, r, c, columns, rows;
1678    unsigned int w, maxwidth = 0;
1679
1680    lbuffed = 0;                /* turn off line buffering */
1681
1682    for (i = 0; i < count; i++) /* find widest string */
1683        maxwidth = max(maxwidth, Strlen(items[i]));
1684
1685    maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */
1686    columns = (TermH + 1) / maxwidth;   /* PWP: terminal size change */
1687    if (!columns)
1688        columns = 1;
1689    rows = (count + (columns - 1)) / columns;
1690
1691    for (r = 0; r < rows; r++) {
1692        for (c = 0; c < columns; c++) {
1693            i = c * rows + r;
1694
1695            if (i < count) {
1696                w = Strlen(items[i]);
1697
1698                if (no_file_suffix) {
1699                    /* Print the command name */
1700                    xprintf("%S", items[i]);
1701                }
1702                else {
1703                    /* Print filename followed by '/' or '*' or ' ' */
1704                    xprintf("%S%c", items[i],
1705                            filetype(dir, items[i]));
1706                    w++;
1707                }
1708
1709                if (c < (columns - 1))  /* Not last column? */
1710                    for (; w < maxwidth; w++)
1711                        xputchar(' ');
1712            }
1713        }
1714        if (Tty_raw_mode)
1715            xputchar('\r');
1716        xputchar('\n');
1717    }
1718
1719    lbuffed = 1;                /* turn back on line buffering */
1720    flush();
1721} /* end print_by_column */
1722
1723
1724/* StrQcmp():
1725 *      Compare strings ignoring the quoting chars
1726 */
1727int
1728StrQcmp(str1, str2)
1729    register Char *str1, *str2;
1730{
1731    for (; *str1 && (*str1 & TRIM) == (*str2 & TRIM); str1++, str2++)
1732        continue;
1733    /*
1734     * The following case analysis is necessary so that characters which look
1735     * negative collate low against normal characters but high against the
1736     * end-of-string NUL.
1737     */
1738    if (*str1 == '\0' && *str2 == '\0')
1739        return (0);
1740    else if (*str1 == '\0')
1741        return (-1);
1742    else if (*str2 == '\0')
1743        return (1);
1744    else
1745        return ((*str1 & TRIM) - (*str2 & TRIM));
1746} /* end StrQcmp */
1747
1748
1749/* fcompare():
1750 *      Comparison routine for qsort
1751 */
1752int
1753fcompare(file1, file2)
1754    Char  **file1, **file2;
1755{
1756    return (int) collate(*file1, *file2);
1757} /* end fcompare */
1758
1759
1760/* catn():
1761 *      Concatenate src onto tail of des.
1762 *      Des is a string whose maximum length is count.
1763 *      Always null terminate.
1764 */
1765void
1766catn(des, src, count)
1767    register Char *des, *src;
1768    register count;
1769{
1770    while (--count >= 0 && *des)
1771        des++;
1772    while (--count >= 0)
1773        if ((*des++ = *src++) == 0)
1774            return;
1775    *des = '\0';
1776} /* end catn */
1777
1778
1779/* copyn():
1780 *       like strncpy but always leave room for trailing \0
1781 *       and always null terminate.
1782 */
1783void
1784copyn(des, src, count)
1785    register Char *des, *src;
1786    register count;
1787{
1788    while (--count >= 0)
1789        if ((*des++ = *src++) == 0)
1790            return;
1791    *des = '\0';
1792} /* end copyn */
1793
1794
1795/* tgetenv():
1796 *      like it's normal string counter-part
1797 *      [apollo uses that in tc.os.c, so it cannot be static]
1798 */
1799Char *
1800tgetenv(str)
1801    Char   *str;
1802{
1803    Char  **var;
1804    int     len, res;
1805
1806    len = Strlen(str);
1807    for (var = STR_environ; var != NULL && *var != NULL; var++)
1808        if ((*var)[len] == '=') {
1809            (*var)[len] = '\0';
1810            res = StrQcmp(*var, str);
1811            (*var)[len] = '=';
1812            if (res == 0)
1813                return (&((*var)[len + 1]));
1814        }
1815    return (NULL);
1816} /* end tgetenv */
Note: See TracBrowser for help on using the repository browser.