1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
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#define _AST_API_H	1
31
32#include <ast.h>
33#include <cmdlib.h>
34#include <proc.h>
35
36static const char lib[] = "libast:cmdarg";
37
38static int
39cmdrun(int argc, char** argv, Cmddisc_t* disc)
40{
41	return procrun(argv[0], argv, PROC_ARGMOD|PROC_IGNOREPATH);
42}
43
44Cmdarg_t*
45cmdopen(char** argv, int argmax, int size, const char* argpat, int flags)
46{
47	Cmddisc_t	disc;
48
49	memset(&disc, 0, sizeof(disc));
50	disc.version = CMD_VERSION;
51	if (!(flags & CMD_SILENT))
52	{
53		flags |= CMD_EXIT;
54		disc.errorf = errorf;
55	}
56	disc.flags = flags;
57	return cmdopen_20120411(argv, argmax, size, argpat, &disc);
58}
59
60#undef	_AST_API_H
61
62#include <ast_api.h>
63
64#include <ctype.h>
65#include <proc.h>
66
67#ifndef ARG_MAX
68#define ARG_MAX		(64*1024)
69#endif
70#ifndef EXIT_QUIT
71#define EXIT_QUIT	255
72#endif
73
74static const char*	echo[] = { "echo", 0 };
75
76Cmdarg_t*
77cmdopen_20110505(char** argv, int argmax, int size, const char* argpat, int flags, Error_f errorf)
78{
79	Cmddisc_t	disc;
80
81	memset(&disc, 0, sizeof(disc));
82	disc.version = CMD_VERSION;
83	disc.flags = flags;
84	disc.errorf = errorf;
85	return cmdopen_20120411(argv, argmax, size, argpat, &disc);
86}
87
88/*
89 * open a cmdarg stream
90 * initialize the command for execution
91 * argv[-1] is reserved for procrun(PROC_ARGMOD)
92 */
93
94Cmdarg_t*
95cmdopen_20120411(char** argv, int argmax, int size, const char* argpat, Cmddisc_t* disc)
96{
97	register Cmdarg_t*	cmd;
98	register int		n;
99	register char**		p;
100	register char*		s;
101	char*			sh;
102	char*			exe;
103	int			c;
104	int			m;
105	int			argc;
106	long			x;
107
108	char**			post = 0;
109
110	n = sizeof(char**);
111	if (*argv)
112	{
113		for (p = argv + 1; *p; p++)
114		{
115			if ((disc->flags & CMD_POST) && argpat && streq(*p, argpat))
116			{
117				*p = 0;
118				post = p + 1;
119				argpat = 0;
120			}
121			else
122				n += strlen(*p) + 1;
123		}
124		argc = p - argv;
125	}
126	else
127		argc = 0;
128	for (p = environ; *p; p++)
129		n += sizeof(char**) + strlen(*p) + 1;
130	if ((x = strtol(astconf("ARG_MAX", NiL, NiL), NiL, 0)) <= 0)
131		x = ARG_MAX;
132	if (size <= 0 || size > x)
133		size = x;
134	sh = pathshell();
135	m = n + (argc + 4) * sizeof(char**) + strlen(sh) + 1;
136	m = roundof(m, sizeof(char**));
137	if (size < m)
138	{
139		if (disc->errorf)
140			(*disc->errorf)(NiL, sh, 2, "size must be at least %d", m);
141		return 0;
142	}
143	if ((m = x / 10) > 2048)
144		m = 2048;
145	if (size > (x - m))
146		size = x - m;
147	n = size - n;
148	m = ((disc->flags & CMD_INSERT) && argpat) ? (strlen(argpat) + 1) : 0;
149	if (!(cmd = newof(0, Cmdarg_t, 1, n + m)))
150	{
151		if (disc->errorf)
152			(*disc->errorf)(NiL, sh, ERROR_SYSTEM|2, "out of space");
153		return 0;
154	}
155	cmd->id = lib;
156	cmd->disc = disc;
157	cmd->errorf = disc->errorf;
158	if (!(cmd->runf = disc->runf))
159		cmd->runf = cmdrun;
160	c = n / sizeof(char**);
161	if (argmax <= 0 || argmax > c)
162		argmax = c;
163	s = cmd->buf;
164	if (!(exe = argv[0]))
165	{
166		exe = *(argv = (char**)echo);
167		cmd->echo = 1;
168	}
169	else if (streq(exe, echo[0]))
170	{
171		cmd->echo = 1;
172		disc->flags &= ~CMD_NEWLINE;
173	}
174	else if (!(disc->flags & CMD_CHECKED))
175	{
176		if (!pathpath(exe, NiL, PATH_REGULAR|PATH_EXECUTE, s, n + m))
177		{
178			n = EXIT_NOTFOUND;
179			if (cmd->errorf)
180				(*cmd->errorf)(NiL, cmd, ERROR_SYSTEM|2, "%s: command not found", exe);
181			if (disc->flags & CMD_EXIT)
182				(*error_info.exit)(n);
183			free(cmd);
184			return 0;
185		}
186		exe = s;
187	}
188	s += strlen(s) + 1;
189	if (m)
190	{
191		cmd->insert = strcpy(s, argpat);
192		cmd->insertlen = m - 1;
193		s += m;
194	}
195	s += sizeof(char**) - (s - cmd->buf) % sizeof(char**);
196	p = (char**)s;
197	n -= strlen(*p++ = sh) + 1;
198	cmd->argv = p;
199	*p++ = exe;
200	while (*p = *++argv)
201		p++;
202	if (m)
203	{
204		argmax = 1;
205		*p++ = 0;
206		cmd->insertarg = p;
207		argv = cmd->argv;
208		c = *cmd->insert;
209		while (s = *argv)
210		{
211			while ((s = strchr(s, c)) && strncmp(cmd->insert, s, cmd->insertlen))
212				s++;
213			*p++ = s ? *argv : (char*)0;
214			argv++;
215		}
216		*p++ = 0;
217	}
218	cmd->firstarg = cmd->nextarg = p;
219	cmd->laststr = cmd->nextstr = cmd->buf + n;
220	cmd->argmax = argmax;
221	cmd->flags = disc->flags;
222	cmd->offset = ((cmd->postarg = post) ? (argc - (post - argv)) : 0) + 3;
223	return cmd;
224}
225
226/*
227 * flush outstanding command file args
228 */
229
230int
231cmdflush(register Cmdarg_t* cmd)
232{
233	register char*	s;
234	register char**	p;
235	register int	n;
236
237	if (cmd->flags & CMD_EMPTY)
238		cmd->flags &= ~CMD_EMPTY;
239	else if (cmd->nextarg <= cmd->firstarg)
240		return 0;
241	if ((cmd->flags & CMD_MINIMUM) && cmd->argcount < cmd->argmax)
242	{
243		if (cmd->errorf)
244			(*cmd->errorf)(NiL, cmd, 2, "%d arg command would be too long", cmd->argcount);
245		return -1;
246	}
247	cmd->total.args += cmd->argcount;
248	cmd->total.commands++;
249	cmd->argcount = 0;
250	if (p = cmd->postarg)
251		while (*cmd->nextarg++ = *p++);
252	else
253		*cmd->nextarg = 0;
254	if (s = cmd->insert)
255	{
256		char*	a;
257		char*	b;
258		char*	e;
259		char*	t;
260		char*	u;
261		int	c;
262		int	m;
263
264		a = cmd->firstarg[0];
265		b = (char*)&cmd->nextarg[1];
266		e = cmd->nextstr;
267		c = *s;
268		m = cmd->insertlen;
269		for (n = 1; cmd->argv[n]; n++)
270			if (t = cmd->insertarg[n])
271			{
272				cmd->argv[n] = b;
273				for (;;)
274				{
275					if (!(u = strchr(t, c)))
276					{
277						b += sfsprintf(b, e - b, "%s", t);
278						break;
279					}
280					if (!strncmp(s, u, m))
281					{
282						b += sfsprintf(b, e - b, "%-.*s%s", u - t, t, a);
283						t = u + m;
284					}
285					else if (b >= e)
286						break;
287					else
288					{
289						*b++ = *u++;
290						t = u;
291					}
292				}
293				if (b < e)
294					*b++ = 0;
295			}
296		if (b >= e)
297		{
298			if (cmd->errorf)
299				(*cmd->errorf)(NiL, cmd, 2, "%s: command too large after insert", a);
300			return -1;
301		}
302	}
303	n = (int)(cmd->nextarg - cmd->argv);
304	cmd->nextarg = cmd->firstarg;
305	cmd->nextstr = cmd->laststr;
306	if (cmd->flags & (CMD_QUERY|CMD_TRACE))
307	{
308		p = cmd->argv;
309		sfprintf(sfstderr, "+ %s", *p);
310		while (s = *++p)
311			sfprintf(sfstderr, " %s", s);
312		if (!(cmd->flags & CMD_QUERY))
313			sfprintf(sfstderr, "\n");
314		else if (astquery(1, "? "))
315		{
316			return 0;
317		}
318	}
319	if (cmd->echo)
320	{
321		n = (cmd->flags & CMD_NEWLINE) ? '\n' : ' ';
322		for (p = cmd->argv + 1; s = *p++;)
323			sfputr(sfstdout, s, *p ? n : '\n');
324		n = 0;
325	}
326	else if ((n = (*cmd->runf)(n, cmd->argv, cmd->disc)) == -1)
327	{
328		n = EXIT_NOTFOUND - 1;
329		if (cmd->errorf)
330			(*cmd->errorf)(NiL, cmd, ERROR_SYSTEM|2, "%s: command exec error", *cmd->argv);
331		if (cmd->flags & CMD_EXIT)
332			(*error_info.exit)(n);
333	}
334	else if (n >= EXIT_NOTFOUND - 1)
335	{
336		if (cmd->flags & CMD_EXIT)
337			(*error_info.exit)(n);
338	}
339	else if (!(cmd->flags & CMD_IGNORE))
340	{
341		if (n == EXIT_QUIT && (cmd->flags & CMD_EXIT))
342			(*error_info.exit)(2);
343		if (n)
344			error_info.errors++;
345	}
346	return n;
347}
348
349/*
350 * add file to the command arg list
351 */
352
353int
354cmdarg(register Cmdarg_t* cmd, const char* file, register int len)
355{
356	int	i;
357	int	r;
358
359	r = 0;
360	if (len > 0)
361	{
362		while ((cmd->nextstr -= len + 1) < (char*)(cmd->nextarg + cmd->offset))
363		{
364			if (cmd->nextarg == cmd->firstarg)
365			{
366				if (cmd->errorf)
367					(*cmd->errorf)(NiL, cmd, 2, "%s: path too long for exec args", file);
368				return -1;
369			}
370			if (i = cmdflush(cmd))
371			{
372				if (r < i)
373					r = i;
374				if (!(cmd->flags & CMD_IGNORE))
375					return r;
376			}
377		}
378		*cmd->nextarg++ = cmd->nextstr;
379		memcpy(cmd->nextstr, file, len);
380		cmd->nextstr[len] = 0;
381		cmd->argcount++;
382		if (cmd->argcount >= cmd->argmax && (i = cmdflush(cmd)) > r)
383			r = i;
384	}
385	else
386		cmd->argcount += len;
387	return r;
388}
389
390/*
391 * close a cmdarg stream
392 */
393
394int
395cmdclose(Cmdarg_t* cmd)
396{
397	int	n;
398
399	if ((cmd->flags & CMD_EXACT) && cmd->argcount < cmd->argmax)
400	{
401		if (cmd->errorf)
402			(*cmd->errorf)(NiL, cmd, 2, "only %d arguments for last command", cmd->argcount);
403		n = -1;
404	}
405	else
406	{
407		cmd->flags &= ~CMD_MINIMUM;
408		n = cmdflush(cmd);
409	}
410	free(cmd);
411	return n;
412}
413