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