1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2013 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 <glenn.s.fowler@gmail.com>                *
18 *                    David Korn <dgkorn@gmail.com>                     *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23  * Glenn Fowler
24  * AT&T Research
25  *
26  * xargs -- construct arg list and exec -- use tw instead
27  */
28 
29 static const char usage[] =
30 "[-?\n@(#)$Id: xargs (AT&T Research) 2013-09-19 $\n]"
31 USAGE_LICENSE
32 "[--plugin?ksh]"
33 "[+NAME?xargs - construct arg list and execute command]"
34 "[+DESCRIPTION?\bxargs\b constructs a command line consisting of the "
35     "\acommand\a and \aargument\a operands specified followed by as many "
36     "arguments read in sequence from standard input as will fit in length "
37     "and number constraints specified by the options and the local system. "
38     "\axargs\a executes the constructed command and waits for its "
39     "completion. This sequence is repeated until an end-of-file condition is "
40     "detected on standard input or an invocation of a constructed command "
41     "line returns an exit status of 255. If \acommand\a is omitted then the "
42     "equivalent of \b/bin/echo\b is used.]"
43 "[+?Arguments in the standard input must be separated by unquoted blank "
44     "characters, or unescaped blank characters or newline characters. A "
45     "string of zero or more non-double-quote and non-newline characters can "
46     "be quoted by enclosing them in double-quotes. A string of zero or more "
47     "non-apostrophe and non-newline characters can be quoted by enclosing "
48     "them in apostrophes. Any unquoted character can be escaped by preceding "
49     "it with a backslash. The utility will be executed one or more times "
50     "until the end-of-file is reached. The results are unspecified if "
51     "\acommand\a attempts to read from its standard input.]"
52 "[e:eof?Set the end of file string. The first input line matching this "
53     "string terminates the input list. There is no eof string if \astring\a "
54     "is omitted. The default eof string is \b_\b if neither \b--eof\b nor "
55     "\b-E\b are specified. For backwards compatibility \astring\a must "
56     "immediately follow the \b-e\b option flag; \b-E\b follows standard "
57     "option syntax.]:?[string]"
58 "[i:insert|replace?Replace occurences of \astring\a in the command "
59     "arguments with names read from the standard input. Implies \b--exit\b "
60     "and \b--lines=1\b. For backwards compatibility \astring\a must "
61     "immediately follow the \b-i\b option flag; \b-I\b follows standard "
62     "option syntax.]:?[string:={}]"
63 "[l:lines|max-lines?Use at most \alines\a lines from the standard input. "
64     "Lines with trailing blanks logically continue onto the next line. For "
65     "backwards compatibility \alines\a must immediately follow the \b-l\b "
66     "option flag; \b-L\b follows standard option syntax.]#?[lines:=1]"
67 "[n:args|max-args?Use at most \aargs\a arguments per command line. Fewer "
68     "than \aargs\a will be used if \b--size\b is exceeded.]#[args]"
69 "[p:interactive|prompt?Prompt to determine if each command should "
70     "execute. A \by\b or \bY\b recsponse executes, otherwise the command is "
71     "skipped. Implies \b--verbose\b.]"
72 "[N|0:null?The file name list is NUL terminated; there is no other "
73     "special treatment of the list.]"
74 "[s:size|max-chars?Use at most \achars\a characters per command. The "
75     "default is as large as possible.]#[chars]"
76 "[t:trace|verbose?Print the command line on the standard error before "
77     "executing it.]"
78 "[x:exit?Exit if \b--size\b is exceeded.]"
79 "[X:exact?If \b--args=\b\aargs\a was specified then terminate before the "
80     "last command if it would run with less than \aargs\a arguments.]"
81 "[z:nonempty|no-run-if-empty?If no file names are found then do not "
82     "execute the command. By default the command is executed at least once.]"
83 "[E?Equivalent to \b--eof=string\b.]:[string]"
84 "[I?Equivalent to \b--insert=string\b.]:[string]"
85 "[L?Equivalent to \b--lines=number\b.]#[number]"
86 
87 "\n"
88 "\n[ command [ argument ... ] ]\n"
89 "\n"
90 
91 "[+EXIT STATUS]"
92     "{"
93         "[+0?All invocations of \acommand\a returned exit status 0.]"
94         "[+1-125?A command line meeting the specified requirements could "
95             "not be assembled, one or more of the invocations of \acommand\a "
96             "returned non-0 exit status, or some other error occurred.]"
97         "[+126?\acommand\a was found but could not be executed.]"
98         "[+127?\acommand\a was not found.]"
99     "}"
100 "[+SEE ALSO?\bfind\b(1), \btw\b(1)]"
101 ;
102 
103 #include <cmd.h>
104 #include <cmdarg.h>
105 #include <ctype.h>
106 
107 typedef struct Xargs_s
108 {
109 	Cmddisc_t		disc;
110 	Cmdarg_t*		cmd;
111 	Shbltin_t*		context;
112 } Xargs_t;
113 
114 static int
run(int argc,char ** argv,Cmddisc_t * disc)115 run(int argc, char** argv, Cmddisc_t* disc)
116 {
117 	if (((Xargs_t*)disc)->context)
118 		return sh_run(((Xargs_t*)disc)->context, argc, argv);
119 }
120 
121 int
b_xargs(int argc,register char ** argv,Shbltin_t * context)122 b_xargs(int argc, register char** argv, Shbltin_t* context)
123 {
124 	register int		c;
125 	register int		q;
126 	register char*		s;
127 	register Sfio_t*	sp;
128 
129 	int			argmax = 0;
130 	char*			eof = "_";
131 	char*			insert = 0;
132 	int			lines = 0;
133 	size_t			size = 0;
134 	int			term = -1;
135 
136 	Xargs_t			xargs;
137 
138 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
139 	CMDDISC(&xargs.disc, CMD_CHECKED|CMD_EMPTY, errorf);
140 	xargs.disc.runf = run;
141 	xargs.context = context;
142 	for (;;)
143 	{
144 		switch (optget(argv, usage))
145 		{
146 		case 'e':
147 			/*
148 			 * backwards compatibility requires no space between
149 			 * option and value
150 			 */
151 
152 			if (opt_info.arg == argv[opt_info.index - 1])
153 			{
154 				opt_info.arg = 0;
155 				opt_info.index--;
156 			}
157 			/*FALLTHROUGH*/
158 		case 'E':
159 			eof = opt_info.arg;
160 			continue;
161 		case 'i':
162 			/*
163 			 * backwards compatibility requires no space between
164 			 * option and value
165 			 */
166 
167 			if (opt_info.arg == argv[opt_info.index - 1])
168 			{
169 				opt_info.arg = 0;
170 				opt_info.index--;
171 			}
172 			/*FALLTHROUGH*/
173 		case 'I':
174 			insert = opt_info.arg ? opt_info.arg : "{}";
175 			xargs.disc.flags |= CMD_INSERT;
176 			term = '\n';
177 			continue;
178 		case 'l':
179 			/*
180 			 * backwards compatibility requires no space between
181 			 * option and value
182 			 */
183 
184 			if (opt_info.arg == argv[opt_info.index - 1])
185 			{
186 				opt_info.arg = 0;
187 				opt_info.index--;
188 			}
189 			/*FALLTHROUGH*/
190 		case 'L':
191 			argmax = opt_info.num ? opt_info.num : 1;
192 			lines = 1;
193 			continue;
194 		case 'n':
195 			argmax = opt_info.num;
196 			continue;
197 		case 'p':
198 			xargs.disc.flags |= CMD_QUERY;
199 			continue;
200 		case 's':
201 			size = opt_info.num;
202 			continue;
203 		case 't':
204 			xargs.disc.flags |= CMD_TRACE;
205 			continue;
206 		case 'x':
207 			xargs.disc.flags |= CMD_MINIMUM;
208 			continue;
209 		case 'z':
210 			xargs.disc.flags &= ~CMD_EMPTY;
211 			continue;
212 		case 'D':
213 			error_info.trace = -opt_info.num;
214 			continue;
215 		case 'N':
216 			term = 0;
217 			continue;
218 		case 'X':
219 			xargs.disc.flags |= CMD_EXACT;
220 			continue;
221 		case '?':
222 			error(ERROR_USAGE|4, "%s", opt_info.arg);
223 			continue;
224 		case ':':
225 			error(2, "%s", opt_info.arg);
226 			continue;
227 		}
228 		break;
229 	}
230 	argv += opt_info.index;
231 	if (error_info.errors)
232 		error(ERROR_USAGE|4, "%s", optusage(NiL));
233 	if (argv[0])
234 	{
235 		if (context)
236 		{
237 			char*	av[4];
238 
239 			av[0] = "whence";
240 			av[1] = "-q";
241 			av[2] = argv[0];
242 			av[3] = 0;
243 			if (run(3, av, &xargs.disc))
244 				error(3, "%s: command not found", argv[0]);
245 		}
246 		else
247 		{
248 			char	buf[PATH_MAX];
249 
250 			if (!pathpath(argv[0], NiL, PATH_REGULAR|PATH_EXECUTE, buf, sizeof(buf)))
251 				error(3, "%s: command not found", argv[0]);
252 		}
253 	}
254 	if (xargs.cmd = cmdopen(argv, argmax, size, insert, &xargs.disc))
255 	{
256 		sfopen(sfstdin, NiL, "rt");
257 		error_info.line = 1;
258 		if (term >= 0)
259 			while (!sh_checksig(context))
260 			{
261 				if (!(s = sfgetr(sfstdin, term, 0)))
262 				{
263 					if (sfvalue(sfstdin) > 0)
264 						error(2, "last argument incomplete");
265 					break;
266 				}
267 				error_info.line++;
268 				if ((c = sfvalue(sfstdin) - 1) && (s[c-1] != '\r' || --c))
269 					cmdarg(xargs.cmd, s, c);
270 			}
271 		else if (!(sp = sfstropen()))
272 			error(ERROR_SYSTEM|2, "out of space [arg]");
273 		else
274 		{
275 			while (!sh_checksig(context))
276 			{
277 				switch (c = sfgetc(sfstdin))
278 				{
279 				case '"':
280 				case '\'':
281 					q = c;
282 					while ((c = sfgetc(sfstdin)) != q)
283 					{
284 						if (c == EOF)
285 							goto arg;
286 						if (c == '\n')
287 						{
288 							error(1, "missing %c quote", q);
289 							error_info.line++;
290 							goto arg;
291 						}
292 						sfputc(sp, c);
293 					}
294 					continue;
295 				case '\\':
296 					if ((c = sfgetc(sfstdin)) == EOF)
297 					{
298 						if (sfstrtell(sp))
299 							goto arg;
300 						break;
301 					}
302 					if (c == '\n')
303 						error_info.line++;
304 					sfputc(sp, c);
305 					continue;
306 				case EOF:
307 					if (sfstrtell(sp))
308 						goto arg;
309 					break;
310 				case '\n':
311 					error_info.line++;
312 				arg:
313 					c = sfstrtell(sp);
314 					if (!(s = sfstruse(sp)))
315 						error(ERROR_SYSTEM|3, "out of space");
316 					if (eof && streq(s, eof))
317 						break;
318 					if (c || insert)
319 					{
320 						if (lines && c > 1 && isspace(s[c - 2]))
321 							cmdarg(xargs.cmd, 0, -1);
322 						cmdarg(xargs.cmd, s, c);
323 					}
324 					continue;
325 				default:
326 					if (isspace(c))
327 						goto arg;
328 					sfputc(sp, c);
329 					continue;
330 				}
331 				break;
332 			}
333 			sfclose(sp);
334 		}
335 		if (sferror(sfstdin))
336 			error(ERROR_SYSTEM|2, "input read error");
337 		cmdclose(xargs.cmd);
338 	}
339 	else if (!error_info.errors)
340 		error(ERROR_SYSTEM|2, "out of space");
341 	return error_info.errors != 0;
342 }
343