1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 2003-2011 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 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 
22 /*
23  * jcl program exec and script loop
24  */
25 
26 #include "jcllib.h"
27 
28 #include <coshell.h>
29 #include <errno.h>
30 #include <ls.h>
31 #include <tm.h>
32 #include <tmx.h>
33 
34 /*
35  * if name contains invalid export id chars then
36  * return a new name with those chars converted to '_<XX>_'
37  */
38 
39 static char*
fmtexport(const char * name)40 fmtexport(const char* name)
41 {
42 	register int		c;
43 	register const char*	s;
44 	register char*		t;
45 	register int		n;
46 	char*			b;
47 
48 	s = name;
49 	c = *s++;
50 	if (isalpha(c) || c == '_')
51 		do
52 		{
53 			if (!(c = *s++))
54 				return (char*)name;
55 		} while (isalnum(c) || c == '_');
56 	n = 1;
57 	while (c = *s++)
58 		if (!isalnum(c) && c != '_')
59 			n++;
60 	t = b = fmtbuf(strlen(name) + 4 * n + 1);
61 	for (s = name; c = *s++;)
62 		if (!isalnum(c) && c != '_')
63 			t += sfsprintf(t, 5, "_%02X_", c);
64 		else
65 			*t++ = c;
66 	*t = 0;
67 	return b;
68 }
69 
70 /*
71  * output dd dsname with &path => /tmp file map
72  */
73 
74 static char*
dsn(Jcl_t * jcl,Jcldd_t * dd,const char * path,int mark)75 dsn(Jcl_t* jcl, Jcldd_t* dd, const char* path, int mark)
76 {
77 	char*	s;
78 
79 	if (mark && dd->disp[0] == JCL_DISP_MOD && !(dd->flags & JCL_DD_DIR))
80 		sfprintf(jcl->vp, "+");
81 	if (*path == '&')
82 	{
83 		if (*++path == '&')
84 			path++;
85 		sfprintf(jcl->vp, jcl->tmp);
86 	}
87 	sfprintf(jcl->vp, "%s", fmtquote(path, "\"", "\"", strlen(path), FMT_SHELL|FMT_PARAM));
88 	if (!(s = sfstruse(jcl->vp)))
89 		nospace(jcl, NiL);
90 	return s;
91 }
92 
93 /*
94  * create dd dir if it doesn't exist
95  */
96 
97 static void
checkdir(Jcl_t * jcl,Jcldd_t * dd)98 checkdir(Jcl_t* jcl, Jcldd_t* dd)
99 {
100 	register char*	s;
101 	Jcldir_t*	dir;
102 
103 	if (!dtmatch(jcl->outdir, dd->path))
104 	{
105 		if (dir = vmnewof(jcl->vm, NiL, Jcldir_t, 1, strlen(dd->path)))
106 		{
107 			strcpy(dir->name, dd->path);
108 			dtinsert(jcl->outdir, dir);
109 		}
110 		s = dsn(jcl, dd, dd->path, 0);
111 		sfprintf(jcl->tp, "[[ ! -d %s && ! -f %s ]] && mkdir -p %s\n", s, s, s);
112 	}
113 }
114 
115 /*
116  * execution loop
117  */
118 
119 int
jclrun(Jcl_t * scope)120 jclrun(Jcl_t* scope)
121 {
122 	register Jcl_t*		jcl;
123 	register Jclstep_t*	step;
124 	register Jcldd_t*	dd;
125 	register Jclcat_t*	cat;
126 	register Jclsym_t*	sym;
127 	register char*		s;
128 	char*			arg;
129 	char*			t;
130 	Coshell_t*		co;
131 	Cojob_t*		cj;
132 	Jcldd_t*		xx;
133 	Jcldd_t*		std[4];
134 	Dtlink_t		k;
135 	Jcl_t*			top;
136 	int			code;
137 	int			del;
138 	int			n;
139 	int			i;
140 	double			pct;
141 	double			real;
142 	double			user;
143 	double			sys;
144 	Time_t			start;
145 	unsigned long		flags;
146 	char			subdir[64];
147 
148 	if (!(jcl = jclopen(scope, scope->step->command, scope->flags|scope->step->flags, scope->disc)))
149 		return -1;
150 	if (jcl->flags & JCL_EXEC)
151 	{
152 		if (!(co = coopen(pathshell(), (jcl->flags & (JCL_EXEC|JCL_TRACE)) == (JCL_EXEC|JCL_TRACE) ? CO_ANY : (CO_ANY|CO_SILENT), NiL)))
153 		{
154 			if (jcl->disc->errorf)
155 				(*jcl->disc->errorf)(NiL, jcl->disc, 2, "%s: cannot connect to coshell", jcl->name);
156 			jclclose(jcl);
157 			return -1;
158 		}
159 		start = tmxgettime();
160 		user = co->user;
161 		sys = co->sys;
162 	}
163 	code = 0;
164 	if (jcl->name && (!scope || !scope->scope || (scope->scope->flags & JCL_SCOPE)))
165 	{
166 		top = jcl;
167 		if (!(jcl->flags & JCL_EXEC))
168 		{
169 			if (jcl->flags & JCL_VERBOSE)
170 				sfprintf(sfstdout, ": JOB %s\nexport %sJOBNAME=%s\ncode=0\n", jcl->name, JCL_AUTO, jcl->name);
171 			if (jcl->flags & JCL_TRACE)
172 				sfprintf(sfstderr, "+ : JOB %s\n", jcl->name);
173 		}
174 		if (jcl->flags & JCL_SUBDIR)
175 		{
176 			if (jcl->flags & JCL_EXEC)
177 			{
178 				t = fmttime("%y-%m-%d", time(NiL));
179 				n = 0;
180 				for (;;)
181 				{
182 					sfsprintf(subdir, sizeof(subdir), "%s.%s.%d", jcl->name, t, ++n);
183 					if (!mkdir(subdir, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH))
184 						break;
185 					if (errno != EEXIST)
186 					{
187 						if (jcl->disc->errorf)
188 							(*jcl->disc->errorf)(NiL, jcl->disc, ERROR_SYSTEM|2, "%s: cannot create job subdirectory", subdir);
189 						jclclose(jcl);
190 						return -1;
191 					}
192 				}
193 				if (chdir(subdir))
194 				{
195 					if (jcl->disc->errorf)
196 						(*jcl->disc->errorf)(NiL, jcl->disc, ERROR_SYSTEM|2, "%s: cannot run in job subdirectory", subdir);
197 					jclclose(jcl);
198 					return -1;
199 				}
200 				sfsync(sfstdout);
201 				sfsync(sfstderr);
202 				for (i = 0; i < elementsof(redirect); i++)
203 					if (jcl->redirect[i] > 0)
204 					{
205 						close(redirect[i].fd);
206 						if ((n = open(redirect[i].file, O_CREAT|O_TRUNC|O_WRONLY|O_APPEND|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != redirect[i].fd)
207 						{
208 							close(n);
209 							dup2(jcl->redirect[i], redirect[i].fd);
210 						}
211 					}
212 				sfset(sfstdout, SF_LINE, 1);
213 				sfset(sfstderr, SF_LINE, 1);
214 				if (jcl->flags & JCL_TRACE)
215 					sfprintf(sfstderr, "+ mkdir %s\n+ cd %s\n", subdir, subdir);
216 				if (jcl->flags & JCL_VERBOSE)
217 					sfprintf(sfstdout, "STARTED AT %s\n", fmttime("%K", time(NiL)));
218 			}
219 			else if (jcl->flags & JCL_VERBOSE)
220 			{
221 				sfprintf(sfstdout, "n=0\nt=$(date +%%y-%%m-%%d)\nwhile	:\ndo	d=%s.$t.$((++n))\n	[[ -d $d ]] || break\ndone\nmkdir $d && cd $d || exit 1\n", jcl->name);
222 				sfputr(sfstdout, "exec > SYSOUT 2> SYSERR\nTIMEFORMAT='USAGE CPU=%P%% REAL=%R USR=%U SYS=%S'\ntime {\ndate +'STARTED AT %K'", '\n');
223 			}
224 		}
225 	}
226 	else
227 		for (top = scope; !top->name && !(top->flags & JCL_SCOPE) && top->scope; top = top->scope);
228 	while (step = jclstep(jcl))
229 	{
230 		for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
231 			if (dd->flags & JCL_DD_ALIAS)
232 			{
233 				dd->flags &= ~JCL_DD_ALIAS;
234 				xx = 0;
235 				t = fmtbuf(n = strlen(step->name) + strlen(dd->path) + 2);
236 				sfsprintf(t, n, "%s.%s", step->name, dd->path);
237 				for (scope = jcl; scope; scope = scope->scope)
238 					if ((xx = (Jcldd_t*)dtmatch(scope->step->dd, t)) ||
239 					    (xx = (Jcldd_t*)dtmatch(scope->step->dd, dd->path)))
240 						break;
241 				if (!xx)
242 				{
243 					if (jcl->disc->errorf)
244 						(*jcl->disc->errorf)(NiL, jcl->disc, 2, "%s: DD not defined", dd->path);
245 					code = -1;
246 					goto bad;
247 				}
248 				k = dd->link;
249 				s = dd->name;
250 				*dd = *xx;
251 				dd->link = k;
252 				dd->name = s;
253 			}
254 		if (!jcl->tmp)
255 		{
256 			for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
257 			{
258 				if (dd->path && *dd->path == '&')
259 					break;
260 				for (cat = dd->cat; cat; cat = cat->next)
261 					if (cat->path && *cat->path == '&')
262 						break;
263 			}
264 			if (dd)
265 			{
266 				if (jcl->flags & JCL_EXEC)
267 				{
268 					if (!(s = getenv("TMPDIR")))
269 						s = "/tmp";
270 					sfprintf(jcl->vp, "%s/job.%s.%lu.", s, jcl->name, (unsigned long)getpid());
271 				}
272 				else
273 					sfprintf(jcl->vp, "${TMPDIR:-/tmp}/job.%s.$$.", jcl->name);
274 				if (!(s = sfstruse(jcl->vp)))
275 					nospace(jcl, NiL);
276 				jcl->tmp = stash(jcl, jcl->vm, s, 0);
277 				if ((jcl->flags & (JCL_VERBOSE|JCL_EXEC)) == JCL_VERBOSE)
278 					sfprintf(sfstdout, "trap 'code=$?; rm -rf %s*; exit $code' 0 1 2\n", jcl->tmp);
279 			}
280 		}
281 		jcl->steps++;
282 		if (jcl->flags & (JCL_LISTEXEC|JCL_LISTINPUTS|JCL_LISTOUTPUTS|JCL_LISTPROGRAMS|JCL_LISTSCRIPTS))
283 		{
284 			if (jcl->flags & JCL_LISTEXEC)
285 				uniq(jcl->name, step->command, 0, jcl->disc);
286 			else if (jcl->flags & (JCL_LISTINPUTS|JCL_LISTOUTPUTS))
287 			{
288 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
289 				{
290 					flags = dd->disp[0] == JCL_DISP_NEW ? JCL_LISTOUTPUTS : JCL_LISTINPUTS;
291 					if (dd->path && *dd->path != '&')
292 						uniq(dd->path, NiL, flags, jcl->disc);
293 					for (cat = dd->cat; cat; cat = cat->next)
294 						if (*cat->path != '&')
295 							uniq(cat->path, NiL, flags, jcl->disc);
296 				}
297 			}
298 			else if (jcl->flags & JCL_LISTPROGRAMS)
299 			{
300 				if (step->flags & JCL_PGM)
301 					uniq(step->command, NiL, 0, jcl->disc);
302 			}
303 			else if (jcl->flags & JCL_LISTSCRIPTS)
304 			{
305 				if (!(step->flags & JCL_PGM) && (s = jclfind(jcl, step->command, 0, 0, NiL)))
306 					uniq(s, NiL, 0, jcl->disc);
307 			}
308 		}
309 		if (!jcleval(jcl, jcl->cond, code))
310 			break;
311 		if (jcleval(jcl, step->cond, code))
312 		{
313 			if (step->flags & JCL_PGM)
314 			{
315 				std[0] = std[1] = std[2] = std[3] = 0;
316 				sfprintf(jcl->tp, ": %s\n", step->name);
317 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
318 					switch (dd->flags & (JCL_DD_DIR|JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE))
319 					{
320 					case JCL_DD_DIR:
321 						if (dd->disp[0] == JCL_DISP_NEW || dd->disp[0] == JCL_DISP_MOD)
322 							checkdir(jcl, dd);
323 						break;
324 					case JCL_DD_SYSIN:
325 						if (dd->path)
326 							std[0] = dd;
327 						if (dd->here)
328 							std[3] = dd;
329 						break;
330 					case JCL_DD_SYSOUT:
331 						std[1] = dd;
332 						break;
333 					case JCL_DD_SYSERR:
334 						std[2] = dd;
335 						break;
336 					default:
337 						if (!(dd->flags & JCL_DD_DUMMY) && (jcl->flags & (JCL_EXEC|JCL_VERBOSE)) && dd->path && *dd->path != '&' && dd->disp[0] == JCL_DISP_NEW && (*dd->path != '$' || *(dd->path + 1) != '(') && (s = strrchr(dd->path, '/')))
338 						{
339 							*s = 0;
340 							checkdir(jcl, dd);
341 							*s = '/';
342 						}
343 						break;
344 					}
345 				for (dd = (Jcldd_t*)dtfirst(jcl->dd); dd; dd = (Jcldd_t*)dtnext(jcl->dd, dd))
346 					if ((dd->flags & (JCL_DD_MARKED|JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) == JCL_DD_MARKED && (dd->disp[0] == JCL_DISP_NEW || dd->disp[0] == JCL_DISP_MOD))
347 					{
348 						s = dsn(jcl, dd, dd->path, 0);
349 						sfprintf(jcl->tp, "[[ -e %s ]] || > %s\n", s, s);
350 					}
351 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
352 					if ((dd->flags & (JCL_DD_MARKED|JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) == JCL_DD_MARKED && (dd->disp[0] == JCL_DISP_NEW || dd->disp[0] == JCL_DISP_MOD))
353 					{
354 						s = dsn(jcl, dd, dd->path, 0);
355 						sfprintf(jcl->tp, "[[ -e %s ]] || > %s\n", s, s);
356 					}
357 				if ((dd = std[0]) && dd->cat)
358 				{
359 					std[0] = std[3] = 0;
360 					sfprintf(jcl->tp, "cat %s", dsn(jcl, dd, dd->path, 0));
361 					for (cat = dd->cat; cat; cat = cat->next)
362 						sfprintf(jcl->tp, " \\\n\t%s", dsn(jcl, dd, cat->path, 0));
363 					sfprintf(jcl->tp, " |\n");
364 				}
365 				if (jcl->flags & JCL_EXEC)
366 					sfprintf(jcl->tp, "%sJOBNAME=%s ", JCL_AUTO, top->name);
367 				sfprintf(jcl->tp, "%s=%s", fmtexport("STEP"), step->name);
368 				s = " \\\n";
369 				for (dd = (Jcldd_t*)dtfirst(jcl->dd); dd; dd = (Jcldd_t*)dtnext(jcl->dd, dd))
370 					if (!(dd->flags & JCL_DD_REFERENCE))
371 					{
372 						sfprintf(jcl->tp, "%s%s=%s", s, fmtexport(dd->name), dsn(jcl, dd, dd->path, 1));
373 						for (cat = dd->cat; cat; cat = cat->next)
374 							sfprintf(jcl->tp, "'\n\t'%s", dsn(jcl, dd, cat->path, 1));
375 					}
376 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
377 					if (dd->path && !(dd->flags & (JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)))
378 					{
379 						sfprintf(jcl->tp, "%s%s=%s", s, fmtexport(dd->name), dsn(jcl, dd, dd->path, 1));
380 						for (cat = dd->cat; cat; cat = cat->next)
381 							sfprintf(jcl->tp, "'\n\t'%s", dsn(jcl, dd, cat->path, 1));
382 						s = " \\\n";
383 					}
384 				del = 0;
385 				sfprintf(jcl->tp, "%sDDIN='", s);
386 				s = "";
387 				for (dd = (Jcldd_t*)dtfirst(jcl->dd); dd; dd = (Jcldd_t*)dtnext(jcl->dd, dd))
388 					if (!(dd->flags & (JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) && dd->disp[0] != JCL_DISP_NEW && dd->disp[0] != JCL_DISP_MOD)
389 					{
390 						sfprintf(jcl->tp, "%s%s", s, fmtexport(dd->name));
391 						s = " ";
392 					}
393 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
394 					if (!(dd->flags & (JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) && dd->disp[0] != JCL_DISP_NEW && dd->disp[0] != JCL_DISP_MOD)
395 					{
396 						sfprintf(jcl->tp, "%s%s", s, fmtexport(dd->name));
397 						s = " ";
398 					}
399 				sfprintf(jcl->tp, "' \\\nDDOUT='");
400 				s = "";
401 				for (dd = (Jcldd_t*)dtfirst(jcl->dd); dd; dd = (Jcldd_t*)dtnext(jcl->dd, dd))
402 					if (!(dd->flags & (JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) && (dd->disp[0] == JCL_DISP_NEW || dd->disp[0] == JCL_DISP_MOD))
403 					{
404 						sfprintf(jcl->tp, "%s%s", s, fmtexport(dd->name));
405 						s = " ";
406 					}
407 				for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
408 				{
409 					if (!(dd->flags & (JCL_DD_SYSIN|JCL_DD_SYSOUT|JCL_DD_SYSERR|JCL_DD_REFERENCE)) && (dd->disp[0] == JCL_DISP_NEW || dd->disp[0] == JCL_DISP_MOD))
410 					{
411 						sfprintf(jcl->tp, "%s%s", s, fmtexport(dd->name));
412 						s = " ";
413 					}
414 					if (dd->disp[1] == JCL_DISP_DELETE)
415 						del |= 1;
416 					if (dd->disp[2] == JCL_DISP_DELETE)
417 						del |= 2;
418 				}
419 				sfprintf(jcl->tp, "' \\\n%s", step->command);
420 				if (s = step->parm)
421 				{
422 					if (*s == '(')
423 					{
424 						arg = strcpy(fmtbuf(strlen(s) + 1), s);
425 						while (s = jclparm(&arg))
426 							sfprintf(jcl->vp, ",%s", s);
427 						if (!(s = sfstruse(jcl->vp)))
428 							nospace(jcl, NiL);
429 						if (*s == ',')
430 							s++;
431 						for (t = s + strlen(s); t > s && *--t ==  ' ';);
432 						if (*t == ')')
433 							*++t = 0;
434 						else if (*t == ',')
435 							*t = 0;
436 					}
437 					sfprintf(jcl->tp, " %s", fmtquote(s, "$'", "'", strlen(s), FMT_SHELL));
438 				}
439 				for (sym = (Jclsym_t*)dtfirst(step->syms); sym; sym = (Jclsym_t*)dtnext(step->syms, sym))
440 					sfprintf(jcl->tp, " %s=%s", fmtquote(sym->name, "'", "'", strlen(sym->name), FMT_SHELL), fmtquote(sym->value, "$'", "'", strlen(sym->value), 0));
441 				if (dd = std[0])
442 				{
443 					sfprintf(jcl->tp, " < %s", dsn(jcl, dd, dd->path, 0));
444 					std[3] = 0;
445 				}
446 				if (dd = std[1])
447 					sfprintf(jcl->tp, " > %s", dsn(jcl, dd, dd->path, 0));
448 				if (dd = std[2])
449 					sfprintf(jcl->tp, " 2> %s", dsn(jcl, dd, dd->path, 0));
450 				if (dd = std[3])
451 					sfprintf(jcl->tp, " <<%s\n%s%s", fmtquote(dd->dlm, "'", "'", strlen(dd->dlm), 1), dd->here, dd->dlm);
452 				sfprintf(jcl->tp, "\ncode=$?\n");
453 				if (del)
454 				{
455 					if (del & 1)
456 					{
457 						sfprintf(jcl->tp, "if (( ! $code ))\nthen\n\trm -rf");
458 						for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
459 							if (dd->disp[1] == JCL_DISP_DELETE)
460 								sfprintf(jcl->tp, " %s", dsn(jcl, dd, dd->path, 0));
461 					}
462 					if (del & 2)
463 					{
464 						if (del & 1)
465 							sfprintf(jcl->tp, "\nelse\n");
466 						else
467 							sfprintf(jcl->tp, "\nif (( $code ))\nthen\n");
468 						sfprintf(jcl->tp, "\trm -rf");
469 						for (dd = (Jcldd_t*)dtfirst(step->dd); dd; dd = (Jcldd_t*)dtnext(step->dd, dd))
470 							if (dd->disp[2] == JCL_DISP_DELETE)
471 								sfprintf(jcl->tp, " %s", dsn(jcl, dd, dd->path, 0));
472 					}
473 					sfprintf(jcl->tp, "\nfi\n");
474 				}
475 				if (jcl->flags & JCL_EXEC)
476 					sfprintf(jcl->tp, "exit $code\n");
477 				if (!(s = sfstruse(jcl->tp)))
478 					nospace(jcl, NiL);
479 			}
480 			else if ((jcl->flags & (JCL_VERBOSE|JCL_EXEC)) == JCL_VERBOSE)
481 				sfprintf(sfstdout, ": %s PROC %s\n", step->name, step->command);
482 			else if ((jcl->flags & (JCL_TRACE|JCL_EXEC)) == (JCL_TRACE|JCL_EXEC))
483 				sfprintf(sfstderr, "+ : %s PROC %s\n", step->name, step->command);
484 			if (step->flags & JCL_PGM)
485 			{
486 				if ((jcl->flags & (JCL_VERBOSE|JCL_EXEC)) == JCL_VERBOSE)
487 					sfputr(sfstdout, s, -1);
488 				if (sfsync(sfstdout))
489 				{
490 					if (jcl->disc->errorf)
491 						(*jcl->disc->errorf)(NiL, jcl->disc, 2, "write error");
492 					code = -1;
493 					break;
494 				}
495 				if (!(jcl->flags & JCL_EXEC))
496 					code = 0;
497 				else if (!(cj = coexec(co, s, CO_APPEND, redirect[0].file, redirect[1].file, NiL)) || !(cj = cowait(co, cj, -1)))
498 					code = 127;
499 				else
500 					code = cj->status;
501 			}
502 			else if (jcl->flags & JCL_RECURSE)
503 				code = jclrun(jcl);
504 			else
505 				code = 0;
506 			if (code)
507 				jcl->failed++;
508 			else
509 				jcl->passed++;
510 			if (jclrc(jcl, step, code) < 0)
511 				break;
512 		}
513 	}
514  bad:
515 	if (jcl->tmp && (jcl->flags & JCL_EXEC))
516 	{
517 		sfprintf(jcl->tp, "rm -rf %s*\n", jcl->tmp);
518 		if (!(s = sfstruse(jcl->tp)))
519 			nospace(jcl, NiL);
520 		if (cj = coexec(co, s, CO_APPEND, redirect[0].file, redirect[1].file, NiL))
521 			cowait(co, cj, -1);
522 	}
523 	if (jcl == top)
524 	{
525 		if (jcl->flags & JCL_GDG)
526 		{
527 			s = "gdgupdate";
528 			if (!(jcl->flags & JCL_EXEC))
529 				sfputr(sfstdout, s, '\n');
530 			else if (cj = coexec(co, s, CO_APPEND, redirect[0].file, redirect[1].file, NiL))
531 				cowait(co, cj, -1);
532 		}
533 		if ((jcl->flags & (JCL_SUBDIR|JCL_VERBOSE)) == (JCL_SUBDIR|JCL_VERBOSE))
534 		{
535 			if (jcl->flags & JCL_EXEC)
536 				sfprintf(sfstdout, "COMPLETED AT %s\n", fmttime("%K", time(NiL)));
537 			else
538 				sfputr(sfstdout, "date +'COMPLETED AT %K'\n}", '\n');
539 		}
540 		if ((jcl->flags & (JCL_EXEC|JCL_SUBDIR)) == (JCL_EXEC|JCL_SUBDIR) && chdir("..") && jcl->disc->errorf)
541 			(*jcl->disc->errorf)(NiL, jcl->disc, ERROR_SYSTEM|2, "%s: cannot chdir to job subdirectory parent", subdir);
542 		if ((jcl->flags & (JCL_EXEC|JCL_VERBOSE)) == (JCL_EXEC|JCL_VERBOSE))
543 		{
544 			start = tmxgettime() - start;
545 			user = (co->user - user) / CO_QUANT;
546 			sys = (co->sys - sys) / CO_QUANT;
547 			if (pct = (real = (double)start / 1e9))
548 				pct = 100.0 * ((user + sys) / pct);
549 			sfprintf(sfstdout, "USAGE CPU=%.2f%% REAL=%.2f USR=%.2f SYS=%.2f\n", pct, real, user, sys);
550 		}
551 	}
552 	code = jclclose(jcl);
553 	if (scope)
554 		jclrc(scope, NiL, code);
555 	return code;
556 }
557