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