1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/*
22 *	File name expansion
23 *
24 *	David Korn
25 *	AT&T Labs
26 *
27 */
28
29#if KSHELL
30#   include	"defs.h"
31#   include	"variables.h"
32#   include	"test.h"
33#else
34#   include	<ast.h>
35#   include	<ctype.h>
36#   include	<setjmp.h>
37#endif /* KSHELL */
38#include	<glob.h>
39#include	<ls.h>
40#include	<stak.h>
41#include	<ast_dir.h>
42#include	"io.h"
43#include	"path.h"
44
45#if !SHOPT_BRACEPAT
46#   define SHOPT_BRACEPAT	0
47#endif
48
49#if KSHELL
50#   define argbegin	argnxt.cp
51    static	const char	*sufstr;
52    static	int		suflen;
53    static int scantree(Dt_t*,const char*, struct argnod**);
54#else
55#   define sh_sigcheck()	(0)
56#   define sh_access		access
57#   define suflen		0
58#endif /* KSHELL */
59
60
61/*
62 * This routine builds a list of files that match a given pathname
63 * Uses external routine strgrpmatch() to match each component
64 * A leading . must match explicitly
65 *
66 */
67
68#ifndef GLOB_AUGMENTED
69#   define GLOB_AUGMENTED	0
70#endif
71
72#define GLOB_RESCAN 1
73#define globptr()	((struct glob*)membase)
74
75static struct glob	 *membase;
76
77#if GLOB_VERSION >= 20010916L
78static char *nextdir(glob_t *gp, char *dir)
79{
80	Pathcomp_t *pp = (Pathcomp_t*)gp->gl_handle;
81	if(!dir)
82		pp = path_get("");
83	else
84		pp = pp->next;
85	gp->gl_handle = (void*)pp;
86	if(pp)
87		return(pp->name);
88	return(0);
89}
90#endif
91
92int path_expand(const char *pattern, struct argnod **arghead)
93{
94	Shell_t	*shp = &sh;
95	glob_t gdata;
96	register struct argnod *ap;
97	register glob_t *gp= &gdata;
98	register int flags,extra=0;
99#if SHOPT_BASH
100	register int off;
101	register char *sp, *cp, *cp2;
102#endif
103	sh_stats(STAT_GLOBS);
104	memset(gp,0,sizeof(gdata));
105	flags = GLOB_AUGMENTED|GLOB_NOCHECK|GLOB_NOSORT|GLOB_STACK|GLOB_LIST|GLOB_DISC;
106	if(sh_isoption(SH_MARKDIRS))
107		flags |= GLOB_MARK;
108	if(sh_isoption(SH_GLOBSTARS))
109		flags |= GLOB_STARSTAR;
110#if SHOPT_BASH
111#if 0
112	if(sh_isoption(SH_BASH) && !sh_isoption(SH_EXTGLOB))
113		flags &= ~GLOB_AUGMENTED;
114#endif
115	if(sh_isoption(SH_NULLGLOB))
116		flags &= ~GLOB_NOCHECK;
117	if(sh_isoption(SH_NOCASEGLOB))
118		flags |= GLOB_ICASE;
119#endif
120	if(sh_isstate(SH_COMPLETE))
121	{
122#if KSHELL
123		extra += scantree(shp->alias_tree,pattern,arghead);
124		extra += scantree(shp->fun_tree,pattern,arghead);
125#   if GLOB_VERSION >= 20010916L
126		gp->gl_nextdir = nextdir;
127#   endif
128#endif /* KSHELL */
129		flags |= GLOB_COMPLETE;
130		flags &= ~GLOB_NOCHECK;
131	}
132#if SHOPT_BASH
133	if(off = staktell())
134		sp = stakfreeze(0);
135	if(sh_isoption(SH_BASH))
136	{
137		/*
138		 * For bash, FIGNORE is a colon separated list of suffixes to
139		 * ignore when doing filename/command completion.
140		 * GLOBIGNORE is similar to ksh FIGNORE, but colon separated
141		 * instead of being an augmented shell pattern.
142		 * Generate shell patterns out of those here.
143		 */
144		if(sh_isstate(SH_FCOMPLETE))
145			cp=nv_getval(sh_scoped(shp,FIGNORENOD));
146		else
147		{
148			static Namval_t *GLOBIGNORENOD;
149			if(!GLOBIGNORENOD)
150				GLOBIGNORENOD = nv_open("GLOBIGNORE",shp->var_tree,0);
151			cp=nv_getval(sh_scoped(shp,GLOBIGNORENOD));
152		}
153		if(cp)
154		{
155			flags |= GLOB_AUGMENTED;
156			stakputs("@(");
157			if(!sh_isstate(SH_FCOMPLETE))
158			{
159				stakputs(cp);
160				for(cp=stakptr(off); *cp; cp++)
161					if(*cp == ':')
162						*cp='|';
163			}
164			else
165			{
166				cp2 = strtok(cp, ":");
167				if(!cp2)
168					cp2=cp;
169				do
170				{
171					stakputc('*');
172					stakputs(cp2);
173					if(cp2 = strtok(NULL, ":"))
174					{
175						*(cp2-1)=':';
176						stakputc('|');
177					}
178				} while(cp2);
179			}
180			stakputc(')');
181			gp->gl_fignore = stakfreeze(1);
182		}
183		else if(!sh_isstate(SH_FCOMPLETE) && sh_isoption(SH_DOTGLOB))
184			gp->gl_fignore = "";
185	}
186	else
187#endif
188	gp->gl_fignore = nv_getval(sh_scoped(shp,FIGNORENOD));
189	if(suflen)
190		gp->gl_suffix = sufstr;
191	gp->gl_intr = &shp->trapnote;
192	suflen = 0;
193	if(memcmp(pattern,"~(N",3)==0)
194		flags &= ~GLOB_NOCHECK;
195	glob(pattern, flags, 0, gp);
196#if SHOPT_BASH
197	if(off)
198		stakset(sp,off);
199	else
200		stakseek(0);
201#endif
202	sh_sigcheck();
203	for(ap= (struct argnod*)gp->gl_list; ap; ap = ap->argnxt.ap)
204	{
205		ap->argchn.ap = ap->argnxt.ap;
206		if(!ap->argnxt.ap)
207			ap->argchn.ap = *arghead;
208	}
209	if(gp->gl_list)
210		*arghead = (struct argnod*)gp->gl_list;
211	return(gp->gl_pathc+extra);
212}
213
214#if KSHELL
215
216/*
217 * scan tree and add each name that matches the given pattern
218 */
219static int scantree(Dt_t *tree, const char *pattern, struct argnod **arghead)
220{
221	register Namval_t *np;
222	register struct argnod *ap;
223	register int nmatch=0;
224	register char *cp;
225	np = (Namval_t*)dtfirst(tree);
226	for(;np && !nv_isnull(np);(np = (Namval_t*)dtnext(tree,np)))
227	{
228		if(strmatch(cp=nv_name(np),pattern))
229		{
230			ap = (struct argnod*)stakseek(ARGVAL);
231			stakputs(cp);
232			ap = (struct argnod*)stakfreeze(1);
233			ap->argbegin = NIL(char*);
234			ap->argchn.ap = *arghead;
235			ap->argflag = ARG_RAW|ARG_MAKE;
236			*arghead = ap;
237			nmatch++;
238		}
239	}
240	return(nmatch);
241}
242
243/*
244 * file name completion
245 * generate the list of files found by adding an suffix to end of name
246 * The number of matches is returned
247 */
248
249int path_complete(const char *name,register const char *suffix, struct argnod **arghead)
250{
251	sufstr = suffix;
252	suflen = strlen(suffix);
253	return(path_expand(name,arghead));
254}
255
256#endif
257
258#if SHOPT_BRACEPAT
259
260static int checkfmt(Sfio_t* sp, void* vp, Sffmt_t* fp)
261{
262	return -1;
263}
264
265int path_generate(struct argnod *todo, struct argnod **arghead)
266/*@
267	assume todo!=0;
268	return count satisfying count>=1;
269@*/
270{
271	register char *cp;
272	register int brace;
273	register struct argnod *ap;
274	struct argnod *top = 0;
275	struct argnod *apin;
276	char *pat, *rescan;
277	char *format;
278	char comma, range=0;
279	int first, last, incr, count = 0;
280	char tmp[32], end[1];
281	todo->argchn.ap = 0;
282again:
283	apin = ap = todo;
284	todo = ap->argchn.ap;
285	cp = ap->argval;
286	range = comma = brace = 0;
287	/* first search for {...,...} */
288	while(1) switch(*cp++)
289	{
290		case '{':
291			if(brace++==0)
292				pat = cp;
293			break;
294		case '}':
295			if(--brace>0)
296				break;
297			if(brace==0 && comma && *cp!='(')
298				goto endloop1;
299			comma = brace = 0;
300			break;
301		case '.':
302			if(brace==1 && *cp=='.')
303			{
304				char *endc;
305				incr = 1;
306				if(isdigit(*pat) || *pat=='+' || *pat=='-')
307				{
308					first = strtol(pat,&endc,0);
309					if(endc==(cp-1))
310					{
311						last = strtol(cp+1,&endc,0);
312						if(*endc=='.' && endc[1]=='.')
313							incr = strtol(endc+2,&endc,0);
314						else if(last<first)
315							incr = -1;
316						if(incr)
317						{
318							if(*endc=='%')
319							{
320								Sffmt_t	fmt;
321								memset(&fmt, 0, sizeof(fmt));
322								fmt.version = SFIO_VERSION;
323								fmt.form = endc;
324								fmt.extf = checkfmt;
325								sfprintf(sfstdout, "%!", &fmt);
326								if(!(fmt.flags&(SFFMT_LLONG|SFFMT_LDOUBLE)))
327									switch (fmt.fmt)
328									{
329									case 'c':
330									case 'd':
331									case 'i':
332									case 'o':
333									case 'u':
334									case 'x':
335									case 'X':
336										format = endc;
337										endc = fmt.form;
338										break;
339									}
340							}
341							else
342								format = "%d";
343							if(*endc=='}')
344							{
345								cp = endc+1;
346								range = 2;
347								goto endloop1;
348							}
349						}
350					}
351				}
352				else if((cp[2]=='}' || cp[2]=='.' && cp[3]=='.') && ((*pat>='a'  && *pat<='z' && cp[1]>='a' && cp[1]<='z') || (*pat>='A'  && *pat<='Z' && cp[1]>='A' && cp[1]<='Z')))
353				{
354					first = *pat;
355					last = cp[1];
356					cp += 2;
357					if(*cp=='.')
358					{
359						incr = strtol(cp+2,&endc,0);
360						cp = endc;
361					}
362					else if(first>last)
363						incr = -1;
364					if(incr && *cp=='}')
365					{
366						cp++;
367						range = 1;
368						goto endloop1;
369					}
370				}
371				cp++;
372			}
373			break;
374		case ',':
375			if(brace==1)
376				comma = 1;
377			break;
378		case '\\':
379			cp++;
380			break;
381		case 0:
382			/* insert on stack */
383			ap->argchn.ap = top;
384			top = ap;
385			if(todo)
386				goto again;
387			for(; ap; ap=apin)
388			{
389				apin = ap->argchn.ap;
390				if(!sh_isoption(SH_NOGLOB))
391					brace=path_expand(ap->argval,arghead);
392				else
393				{
394					ap->argchn.ap = *arghead;
395					*arghead = ap;
396					brace=1;
397				}
398				if(brace)
399				{
400					count += brace;
401					(*arghead)->argflag |= ARG_MAKE;
402				}
403			}
404			return(count);
405	}
406endloop1:
407	rescan = cp;
408	cp = pat-1;
409	*cp = 0;
410	while(1)
411	{
412		brace = 0;
413		if(range)
414		{
415			if(range==1)
416			{
417				pat[0] = first;
418				cp = &pat[1];
419			}
420			else
421			{
422				*(rescan - 1) = 0;
423				sfsprintf(pat=tmp,sizeof(tmp),format,first);
424				*(rescan - 1) = '}';
425				*(cp = end) = 0;
426			}
427			if(incr*(first+incr) > last*incr)
428				*cp = '}';
429			else
430				first += incr;
431		}
432		/* generate each pattern and put on the todo list */
433		else while(1) switch(*++cp)
434		{
435			case '\\':
436				cp++;
437				break;
438			case '{':
439				brace++;
440				break;
441			case ',':
442				if(brace==0)
443					goto endloop2;
444				break;
445			case '}':
446				if(--brace<0)
447					goto endloop2;
448		}
449	endloop2:
450		brace = *cp;
451		*cp = 0;
452		sh_sigcheck();
453		ap = (struct argnod*)stakseek(ARGVAL);
454		ap->argflag = ARG_RAW;
455		ap->argchn.ap = todo;
456		stakputs(apin->argval);
457		stakputs(pat);
458		stakputs(rescan);
459		todo = ap = (struct argnod*)stakfreeze(1);
460		if(brace == '}')
461			break;
462		if(!range)
463			pat = cp+1;
464	}
465	goto again;
466}
467
468#endif /* SHOPT_BRACEPAT */
469