1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-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*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                   Phong Vo <kpv@research.att.com>                    *
20*                                                                      *
21***********************************************************************/
22#pragma prototyped
23/*
24 * Glenn Fowler
25 * AT&T Research
26 *
27 * xargs/tw command arg list support
28 */
29
30#include <ast.h>
31#include <ctype.h>
32#include <error.h>
33#include <proc.h>
34
35#include "cmdarg.h"
36
37#ifndef ARG_MAX
38#define ARG_MAX		(64*1024)
39#endif
40#ifndef EXIT_QUIT
41#define EXIT_QUIT	255
42#endif
43
44static const char*	echo[] = { "echo", 0 };
45
46/*
47 * open a cmdarg stream
48 * initialize the command for execution
49 * argv[-1] is reserved for procrun(PROC_ARGMOD)
50 */
51
52Cmdarg_t*
53cmdopen(char** argv, int argmax, int size, const char* argpat, int flags)
54{
55	register Cmdarg_t*	cmd;
56	register int		n;
57	register char**		p;
58	register char*		s;
59	char*			sh;
60	int			c;
61	int			m;
62	int			argc;
63	long			x;
64
65	char**			post = 0;
66
67	n = sizeof(char**);
68	if (*argv)
69	{
70		for (p = argv + 1; *p; p++)
71		{
72			if ((flags & CMD_POST) && argpat && streq(*p, argpat))
73			{
74				*p = 0;
75				post = p + 1;
76				argpat = 0;
77			}
78			else
79				n += strlen(*p) + 1;
80		}
81		argc = p - argv;
82	}
83	else
84		argc = 0;
85	for (p = environ; *p; p++)
86		n += sizeof(char**) + strlen(*p) + 1;
87	if ((x = strtol(astconf("ARG_MAX", NiL, NiL), NiL, 0)) <= 0)
88		x = ARG_MAX;
89	if (size <= 0 || size > x)
90		size = x;
91	sh = pathshell();
92	m = n + (argc + 4) * sizeof(char**) + strlen(sh) + 1;
93	m = roundof(m, sizeof(char**));
94	if (size < m)
95	{
96		error(2, "size must be at least %d", m);
97		return 0;
98	}
99	if ((m = x / 10) > 2048)
100		m = 2048;
101	if (size > (x - m))
102		size = x - m;
103	n = size - n;
104	m = ((flags & CMD_INSERT) && argpat) ? (strlen(argpat) + 1) : 0;
105	if (!(cmd = newof(0, Cmdarg_t, 1, n + m)))
106	{
107		error(ERROR_SYSTEM|2, "out of space");
108		return 0;
109	}
110	c = n / sizeof(char**);
111	if (argmax <= 0 || argmax > c)
112		argmax = c;
113	s = cmd->buf;
114	if (!argv[0])
115	{
116		argv = (char**)echo;
117		cmd->echo = 1;
118	}
119	else if (streq(argv[0], echo[0]))
120	{
121		cmd->echo = 1;
122		flags &= ~CMD_NEWLINE;
123	}
124	else if (!(flags & CMD_CHECKED))
125	{
126		if (!pathpath(s, argv[0], NiL, PATH_REGULAR|PATH_EXECUTE))
127		{
128			if (!(flags & CMD_SILENT))
129			{
130				error(ERROR_SYSTEM|2, "%s: command not found", argv[0]);
131				exit(EXIT_NOTFOUND);
132			}
133			free(cmd);
134			return 0;
135		}
136		argv[0] = s;
137	}
138	s += strlen(s) + 1;
139	if (m)
140	{
141		cmd->insert = strcpy(s, argpat);
142		cmd->insertlen = m - 1;
143		s += m;
144	}
145	s += sizeof(char**) - (s - cmd->buf) % sizeof(char**);
146	p = (char**)s;
147	n -= strlen(*p++ = sh) + 1;
148	cmd->argv = p;
149	while (*p = *argv++)
150		p++;
151	if (m)
152	{
153		argmax = 1;
154		*p++ = 0;
155		cmd->insertarg = p;
156		argv = cmd->argv;
157		c = *cmd->insert;
158		while (s = *argv)
159		{
160			while ((s = strchr(s, c)) && strncmp(cmd->insert, s, cmd->insertlen))
161				s++;
162			*p++ = s ? *argv : (char*)0;
163			argv++;
164		}
165		*p++ = 0;
166	}
167	cmd->firstarg = cmd->nextarg = p;
168	cmd->laststr = cmd->nextstr = cmd->buf + n;
169	cmd->argmax = argmax;
170	cmd->flags = flags;
171	cmd->offset = ((cmd->postarg = post) ? (argc - (post - argv)) : 0) + 3;
172	return cmd;
173}
174
175/*
176 * flush outstanding command file args
177 */
178
179int
180cmdflush(register Cmdarg_t* cmd)
181{
182	register char*	s;
183	register char**	p;
184	register int	n;
185
186	if (cmd->flags & CMD_EMPTY)
187		cmd->flags &= ~CMD_EMPTY;
188	else if (cmd->nextarg <= cmd->firstarg)
189		return 0;
190	if ((cmd->flags & CMD_MINIMUM) && cmd->argcount < cmd->argmax)
191	{
192		if (!(cmd->flags & CMD_SILENT))
193			error(2, "%d arg command would be too long", cmd->argcount);
194		return -1;
195	}
196	cmd->total.args += cmd->argcount;
197	cmd->total.commands++;
198	cmd->argcount = 0;
199	if (p = cmd->postarg)
200		while (*cmd->nextarg++ = *p++);
201	else
202		*cmd->nextarg = 0;
203	if (s = cmd->insert)
204	{
205		char*	a;
206		char*	b;
207		char*	e;
208		char*	t;
209		char*	u;
210		int	c;
211		int	m;
212
213		a = cmd->firstarg[0];
214		b = (char*)&cmd->nextarg[1];
215		e = cmd->nextstr;
216		c = *s;
217		m = cmd->insertlen;
218		for (n = 1; cmd->argv[n]; n++)
219			if (t = cmd->insertarg[n])
220			{
221				cmd->argv[n] = b;
222				for (;;)
223				{
224					if (!(u = strchr(t, c)))
225					{
226						b += sfsprintf(b, e - b, "%s", t);
227						break;
228					}
229					if (!strncmp(s, u, m))
230					{
231						b += sfsprintf(b, e - b, "%-.*s%s", u - t, t, a);
232						t = u + m;
233					}
234					else if (b >= e)
235						break;
236					else
237					{
238						*b++ = *u++;
239						t = u;
240					}
241				}
242				if (b < e)
243					*b++ = 0;
244			}
245		if (b >= e)
246		{
247			if (!(cmd->flags & CMD_SILENT))
248				error(2, "%s: command too large after insert", a);
249			return -1;
250		}
251	}
252	cmd->nextarg = cmd->firstarg;
253	cmd->nextstr = cmd->laststr;
254	if (cmd->flags & (CMD_QUERY|CMD_TRACE))
255	{
256		p = cmd->argv;
257		sfprintf(sfstderr, "+ %s", *p);
258		while (s = *++p)
259			sfprintf(sfstderr, " %s", s);
260		if (!(cmd->flags & CMD_QUERY))
261			sfprintf(sfstderr, "\n");
262		else if (astquery(1, "? "))
263			return 0;
264	}
265	if (cmd->echo)
266	{
267		n = (cmd->flags & CMD_NEWLINE) ? '\n' : ' ';
268		for (p = cmd->argv + 1; s = *p++;)
269			sfputr(sfstdout, s, *p ? n : '\n');
270		n = 0;
271	}
272	else if ((n = procrun(*cmd->argv, cmd->argv, PROC_ARGMOD|PROC_IGNOREPATH)) == -1)
273	{
274		if (!(cmd->flags & CMD_SILENT))
275		{
276			error(ERROR_SYSTEM|2, "%s: command exec error", *cmd->argv);
277			exit(EXIT_NOTFOUND - 1);
278		}
279		return -1;
280	}
281	else if (n >= EXIT_NOTFOUND - 1)
282	{
283		if (!(cmd->flags & CMD_SILENT))
284			exit(n);
285	}
286	else if (!(cmd->flags & CMD_IGNORE))
287	{
288		if (n == EXIT_QUIT && !(cmd->flags & CMD_SILENT))
289			exit(2);
290		if (n)
291			error_info.errors++;
292	}
293	return n;
294}
295
296/*
297 * add file to the command arg list
298 */
299
300int
301cmdarg(register Cmdarg_t* cmd, const char* file, register int len)
302{
303	int	i;
304	int	r;
305
306	r = 0;
307	if (len)
308	{
309		while ((cmd->nextstr -= len + 1) < (char*)(cmd->nextarg + cmd->offset))
310		{
311			if (cmd->nextarg == cmd->firstarg)
312			{
313				error(2, "%s: path too long for exec args", file);
314				return -1;
315			}
316			if (i = cmdflush(cmd))
317			{
318				if (r < i)
319					r = i;
320				if (!(cmd->flags & CMD_IGNORE))
321					return r;
322			}
323		}
324		*cmd->nextarg++ = cmd->nextstr;
325		memcpy(cmd->nextstr, file, len);
326		cmd->nextstr[len] = 0;
327		cmd->argcount++;
328		if (cmd->argcount >= cmd->argmax && (i = cmdflush(cmd)) > r)
329			r = i;
330	}
331	return r;
332}
333
334/*
335 * close a cmdarg stream
336 */
337
338int
339cmdclose(Cmdarg_t* cmd)
340{
341	int	n;
342
343	if ((cmd->flags & CMD_EXACT) && cmd->argcount < cmd->argmax)
344	{
345		if (!(cmd->flags & CMD_SILENT))
346			error(2, "only %d arguments for last command", cmd->argcount);
347		return -1;
348	}
349	cmd->flags &= ~CMD_MINIMUM;
350	n = cmdflush(cmd);
351	free(cmd);
352	return n;
353}
354