1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7 /* This file is ALSO:
8 * Copyright 2001-2004 David Abrahams.
9 * Copyright 2015 Artur Shepilko.
10 * Distributed under the Boost Software License, Version 1.0.
11 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
12 */
13
14
15 /*
16 * execvms.c - execute a shell script, ala VMS.
17 *
18 * The approach is this:
19 *
20 * If the command is a single line, and shorter than WRTLEN (what we believe to
21 * be the maximum line length), we just system() it.
22 *
23 * If the command is multi-line, or longer than WRTLEN, we write the command
24 * block to a temp file, splitting long lines (using "-" at the end of the line
25 * to indicate contiuation), and then source that temp file. We use special
26 * logic to make sure we do not continue in the middle of a quoted string.
27 *
28 * 05/04/94 (seiwald) - async multiprocess interface; noop on VMS
29 * 12/20/96 (seiwald) - rewritten to handle multi-line commands well
30 * 01/14/96 (seiwald) - do not put -'s between "'s
31 * 01/19/15 (shepilko)- adapt for jam-3.1.19
32 */
33
34 #include "jam.h"
35 #include "lists.h"
36 #include "execcmd.h"
37 #include "output.h"
38
39 #ifdef OS_VMS
40
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <ctype.h>
45 #include <times.h>
46 #include <unistd.h>
47 #include <errno.h>
48
49
50 #define WRTLEN 240
51
52 #define MIN( a, b ) ((a) < (b) ? (a) : (b))
53
54 #define CHAR_DQUOTE '"'
55
56 #define VMS_PATH_MAX 1024
57 #define VMS_COMMAND_MAX 1024
58
59 #define VMS_WARNING 0
60 #define VMS_SUCCESS 1
61 #define VMS_ERROR 2
62 #define VMS_FATAL 4
63
64 char commandbuf[ VMS_COMMAND_MAX ] = { 0 };
65
66
67 static int get_status(int vms_status);
68 static clock_t get_cpu_time();
69
70 /*
71 * exec_check() - preprocess and validate the command.
72 */
73
exec_check(string const * command,LIST ** pShell,int * error_length,int * error_max_length)74 int exec_check
75 (
76 string const * command,
77 LIST * * pShell,
78 int * error_length,
79 int * error_max_length
80 )
81 {
82 int const is_raw_cmd = 1;
83
84 /* We allow empty commands for non-default shells since we do not really
85 * know what they are going to do with such commands.
86 */
87 if ( !command->size && ( is_raw_cmd || list_empty( *pShell ) ) )
88 return EXEC_CHECK_NOOP;
89
90 return is_raw_cmd
91 ? EXEC_CHECK_OK
92 : check_cmd_for_too_long_lines( command->value, MAXLINE, error_length,
93 error_max_length );
94 }
95
96
97 /*
98 * exec_cmd() - execute system command.
99 */
100
exec_cmd(string const * command,int flags,ExecCmdCallback func,void * closure,LIST * shell)101 void exec_cmd
102 (
103 string const * command,
104 int flags,
105 ExecCmdCallback func,
106 void * closure,
107 LIST * shell
108 )
109 {
110 char * s;
111 char * e;
112 char * p;
113 int vms_status;
114 int status;
115 int rstat = EXEC_CMD_OK;
116 int exit_reason = EXIT_OK;
117 timing_info time_info;
118 timestamp start_dt;
119 struct tms start_time;
120 struct tms end_time;
121 char * cmd_string = command->value;
122
123
124 /* Start the command */
125
126 timestamp_current( &time_info.start );
127 times( &start_time );
128
129 /* See if command is more than one line discounting leading/trailing white
130 * space.
131 */
132 for ( s = cmd_string; *s && isspace( *s ); ++s );
133
134 e = p = strchr( s, '\n' );
135
136 while ( p && isspace( *p ) )
137 ++p;
138
139 /* If multi line or long, write to com file. Otherwise, exec directly. */
140 if ( ( p && *p ) || ( e - s > WRTLEN ) )
141 {
142 FILE * f;
143
144 /* Create temp file invocation. */
145
146 if ( !*commandbuf )
147 {
148 OBJECT * tmp_filename = 0;
149
150 tmp_filename = path_tmpfile();
151
152
153 /* Get tmp file name is VMS-format. */
154 {
155 string os_filename[ 1 ];
156 string_new( os_filename );
157 path_translate_to_os( object_str( tmp_filename ), os_filename );
158 object_free( tmp_filename );
159 tmp_filename = object_new( os_filename->value );
160 string_free( os_filename );
161 }
162
163 commandbuf[0] = '@';
164 strncat( commandbuf + 1, object_str( tmp_filename ),
165 VMS_COMMAND_MAX - 2);
166 }
167
168
169 /* Open tempfile. */
170 if ( !( f = fopen( commandbuf + 1, "w" ) ) )
171 {
172 err_printf( "[errno %d] failed to wite cmd_string file '%s': %s",
173 errno, commandbuf + 1, strerror(errno) );
174 rstat = EXEC_CMD_FAIL;
175 exit_reason = EXIT_FAIL;
176
177 times( &end_time );
178
179 timestamp_current( &time_info.end );
180 time_info.system = (double)( end_time.tms_cstime -
181 start_time.tms_cstime ) / 100.;
182 time_info.user = (double)( end_time.tms_cutime -
183 start_time.tms_cutime ) / 100.;
184
185 (*func)( closure, rstat, &time_info, "" , "", exit_reason );
186 return;
187 }
188
189
190 /* Running from TMP, so explicitly set default to CWD. */
191 {
192 char * cwd = NULL;
193 int cwd_buf_size = VMS_PATH_MAX;
194
195 while ( !(cwd = getcwd( NULL, cwd_buf_size ) ) /* alloc internally */
196 && errno == ERANGE )
197 {
198 cwd_buf_size += VMS_PATH_MAX;
199 }
200
201 if ( !cwd )
202 {
203 perror( "can not get current working directory" );
204 exit( EXITBAD );
205 }
206
207 fprintf( f, "$ SET DEFAULT %s\n", cwd);
208
209 free( cwd );
210 }
211
212
213 /* For each line of the command. */
214 while ( *cmd_string )
215 {
216 char * s = strchr( cmd_string,'\n' );
217 int len = s ? s + 1 - cmd_string : strlen( cmd_string );
218
219 fputc( '$', f );
220
221 /* For each chunk of a line that needs to be split. */
222 while ( len > 0 )
223 {
224 char * q = cmd_string;
225 char * qe = cmd_string + MIN( len, WRTLEN );
226 char * qq = q;
227 int quote = 0;
228
229 /* Look for matching "s -- expected in the same line. */
230 for ( ; q < qe; ++q )
231 if ( ( *q == CHAR_DQUOTE ) && ( quote = !quote ) )
232 qq = q;
233
234 /* When needs splitting and is inside an open quote,
235 * back up to opening quote and split off at it.
236 * When the quoted string spans over a chunk,
237 * pass string as a whole.
238 * If no matching quote found, dump the rest of command.
239 */
240 if ( len > WRTLEN && quote )
241 {
242 q = qq;
243
244 if ( q == cmd_string )
245 {
246 for ( q = qe; q < ( cmd_string + len )
247 && *q != CHAR_DQUOTE ; ++q) {}
248 q = ( *q == CHAR_DQUOTE) ? ( q + 1 ) : ( cmd_string + len );
249 }
250 }
251
252 fwrite( cmd_string, ( q - cmd_string ), 1, f );
253
254 len -= ( q - cmd_string );
255 cmd_string = q;
256
257 if ( len )
258 {
259 fputc( '-', f );
260 fputc( '\n', f );
261 }
262 }
263 }
264
265 fclose( f );
266
267 if ( DEBUG_EXECCMD )
268 {
269 FILE * f;
270 char buf[ WRTLEN + 1 ] = { 0 };
271
272 if ( (f = fopen( commandbuf + 1, "r" ) ) )
273 {
274 int nbytes;
275 printf( "Command file: %s\n", commandbuf + 1 );
276
277 do
278 {
279 nbytes = fread( buf, sizeof( buf[0] ), sizeof( buf ) - 1, f );
280
281 if ( nbytes ) fwrite(buf, sizeof( buf[0] ), nbytes, stdout);
282 }
283 while ( !feof(f) );
284
285 fclose(f);
286 }
287 }
288
289 /* Execute command file */
290 vms_status = system( commandbuf );
291 status = get_status( vms_status );
292
293 unlink( commandbuf + 1 );
294 }
295 else
296 {
297 /* Execute single line command. Strip trailing newline before execing.
298 * TODO:Call via popen() with capture of the output may be better here.
299 */
300 if ( e ) *e = 0;
301
302 status = VMS_SUCCESS; /* success on empty command */
303 if ( *s )
304 {
305 vms_status = system( s );
306 status = get_status( vms_status );
307 }
308 }
309
310
311 times( &end_time );
312
313 timestamp_current( &time_info.end );
314 time_info.system = (double)( end_time.tms_cstime -
315 start_time.tms_cstime ) / 100.;
316 time_info.user = (double)( end_time.tms_cutime -
317 start_time.tms_cutime ) / 100.;
318
319
320 /* Fail for error or fatal error. OK on OK, warning or info exit. */
321 if ( ( status == VMS_ERROR ) || ( status == VMS_FATAL ) )
322 {
323 rstat = EXEC_CMD_FAIL;
324 exit_reason = EXIT_FAIL;
325 }
326
327 (*func)( closure, rstat, &time_info, "" , "", exit_reason );
328 }
329
330
exec_wait()331 void exec_wait()
332 {
333 return;
334 }
335
336
337 /* get_status() - returns status of the VMS command execution.
338 - Map VMS status to its severity (lower 3-bits)
339 - W-DCL-IVVERB is returned on unrecognized command -- map to general ERROR
340 */
get_status(int vms_status)341 int get_status( int vms_status )
342 {
343 #define VMS_STATUS_DCL_IVVERB 0x00038090
344
345 int status;
346
347 switch (vms_status)
348 {
349 case VMS_STATUS_DCL_IVVERB:
350 status = VMS_ERROR;
351 break;
352
353 default:
354 status = vms_status & 0x07; /* $SEVERITY bits */
355 }
356
357 return status;
358 }
359
360
361 #define __NEW_STARLET 1
362
363 #include <stdio.h>
364 #include <stdlib.h>
365 #include <time.h>
366 #include <ssdef.h>
367 #include <stsdef.h>
368 #include <jpidef.h>
369 #include <efndef.h>
370 #include <iosbdef.h>
371 #include <iledef.h>
372 #include <lib$routines.h>
373 #include <starlet.h>
374
375
376 /*
377 * get_cpu_time() - returns CPU time in CLOCKS_PER_SEC since process start.
378 * on error returns (clock_t)-1.
379 *
380 * Intended to emulate (system + user) result of *NIX times(), if CRTL times()
381 * is not available.
382 * However, this accounts only for the current process. To account for child
383 * processes, these need to be directly spawned/forked via exec().
384 * Moreover, child processes should be running a C main program or a program
385 * that calls VAXC$CRTL_INIT or DECC$CRTL_INIT.
386 */
387
get_cpu_time()388 clock_t get_cpu_time()
389 {
390 clock_t result = (clock_t) 0;
391
392 IOSB iosb;
393 int status;
394 long cputime = 0;
395
396
397 ILE3 jpi_items[] = {
398 { sizeof( cputime ), JPI$_CPUTIM, &cputime, NULL }, /* longword int, 10ms */
399 { 0 },
400 };
401
402 status = sys$getjpiw (EFN$C_ENF, 0, 0, jpi_items, &iosb, 0, 0);
403
404 if ( !$VMS_STATUS_SUCCESS( status ) )
405 {
406 lib$signal( status );
407
408 result = (clock_t) -1;
409 return result;
410 }
411
412
413 result = ( cputime / 100 ) * CLOCKS_PER_SEC;
414
415 return result;
416 }
417
418
419 # endif /* VMS */
420
421