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             printf( "can't open cmd_string file\n" );
173             rstat = EXEC_CMD_FAIL;
174             exit_reason = EXIT_FAIL;
175 
176             times( &end_time );
177 
178             timestamp_current( &time_info.end );
179             time_info.system = (double)( end_time.tms_cstime -
180                 start_time.tms_cstime ) / 100.;
181             time_info.user   = (double)( end_time.tms_cutime -
182                 start_time.tms_cutime ) / 100.;
183 
184             (*func)( closure, rstat, &time_info, "" , "", exit_reason  );
185             return;
186         }
187 
188 
189         /* Running from TMP, so explicitly set default to CWD. */
190         {
191             char * cwd = NULL;
192             int cwd_buf_size = VMS_PATH_MAX;
193 
194             while ( !(cwd = getcwd( NULL, cwd_buf_size ) ) /* alloc internally */
195                      && errno == ERANGE )
196             {
197                 cwd_buf_size += VMS_PATH_MAX;
198             }
199 
200             if ( !cwd )
201             {
202                 perror( "can not get current working directory" );
203                 exit( EXITBAD );
204             }
205 
206             fprintf( f, "$ SET DEFAULT %s\n", cwd);
207 
208             free( cwd );
209         }
210 
211 
212         /* For each line of the command. */
213         while ( *cmd_string )
214         {
215             char * s = strchr( cmd_string,'\n' );
216             int len = s ? s + 1 - cmd_string : strlen( cmd_string );
217 
218             fputc( '$', f );
219 
220             /* For each chunk of a line that needs to be split. */
221             while ( len > 0 )
222             {
223                 char * q = cmd_string;
224                 char * qe = cmd_string + MIN( len, WRTLEN );
225                 char * qq = q;
226                 int quote = 0;
227 
228                 /* Look for matching "s -- expected in the same line. */
229                 for ( ; q < qe; ++q )
230                     if ( ( *q == CHAR_DQUOTE ) && ( quote = !quote ) )
231                         qq = q;
232 
233                 /* When needs splitting and is inside an open quote,
234                  * back up to opening quote and split off at it.
235                  * When the quoted string spans over a chunk,
236                  * pass string as a whole.
237                  * If no matching quote found, dump the rest of command.
238                  */
239                 if (  len > WRTLEN && quote )
240                 {
241                     q = qq;
242 
243                     if ( q == cmd_string )
244                     {
245                         for ( q = qe; q < ( cmd_string + len )
246                                       && *q != CHAR_DQUOTE ; ++q) {}
247                         q = ( *q == CHAR_DQUOTE) ? ( q + 1 ) : ( cmd_string + len );
248                     }
249                 }
250 
251                 fwrite( cmd_string, ( q - cmd_string ), 1, f );
252 
253                 len -= ( q - cmd_string );
254                 cmd_string = q;
255 
256                 if ( len )
257                 {
258                     fputc( '-', f );
259                     fputc( '\n', f );
260                 }
261             }
262         }
263 
264         fclose( f );
265 
266         if ( DEBUG_EXECCMD )
267         {
268             FILE * f;
269             char buf[ WRTLEN + 1 ] = { 0 };
270 
271             if ( (f = fopen( commandbuf + 1, "r" ) ) )
272             {
273                 int nbytes;
274                 printf( "Command file: %s\n", commandbuf + 1 );
275 
276                 do
277                 {
278                     nbytes = fread( buf, sizeof( buf[0] ), sizeof( buf ) - 1, f );
279 
280                     if ( nbytes ) fwrite(buf, sizeof( buf[0] ), nbytes, stdout);
281                 }
282                 while ( !feof(f) );
283 
284                 fclose(f);
285             }
286         }
287 
288         /* Execute command file */
289         vms_status = system( commandbuf );
290         status = get_status( vms_status );
291 
292         unlink( commandbuf + 1 );
293     }
294     else
295     {
296         /* Execute single line command. Strip trailing newline before execing.
297          * TODO:Call via popen() with capture of the output may be better here.
298          */
299         if ( e ) *e = 0;
300 
301         status = VMS_SUCCESS;  /* success on empty command */
302         if ( *s )
303         {
304             vms_status = system( s );
305             status = get_status( vms_status );
306         }
307     }
308 
309 
310     times( &end_time );
311 
312     timestamp_current( &time_info.end );
313     time_info.system = (double)( end_time.tms_cstime -
314         start_time.tms_cstime ) / 100.;
315     time_info.user   = (double)( end_time.tms_cutime -
316         start_time.tms_cutime ) / 100.;
317 
318 
319     /* Fail for error or fatal error. OK on OK, warning or info exit. */
320     if ( ( status == VMS_ERROR ) || ( status == VMS_FATAL ) )
321     {
322         rstat = EXEC_CMD_FAIL;
323         exit_reason = EXIT_FAIL;
324     }
325 
326     (*func)( closure, rstat, &time_info, "" , "", exit_reason  );
327 }
328 
329 
exec_wait()330 void exec_wait()
331 {
332     return;
333 }
334 
335 
336 /* get_status() - returns status of the VMS command execution.
337    - Map VMS status to its severity (lower 3-bits)
338    - W-DCL-IVVERB is returned on unrecognized command -- map to general ERROR
339 */
get_status(int vms_status)340 int get_status( int vms_status )
341 {
342 #define VMS_STATUS_DCL_IVVERB 0x00038090
343 
344     int status;
345 
346     switch (vms_status)
347     {
348     case VMS_STATUS_DCL_IVVERB:
349         status = VMS_ERROR;
350         break;
351 
352     default:
353         status = vms_status & 0x07; /* $SEVERITY bits */
354     }
355 
356     return status;
357 }
358 
359 
360 #define __NEW_STARLET 1
361 
362 #include <stdio.h>
363 #include <stdlib.h>
364 #include <time.h>
365 #include <ssdef.h>
366 #include <stsdef.h>
367 #include <jpidef.h>
368 #include <efndef.h>
369 #include <iosbdef.h>
370 #include <iledef.h>
371 #include <lib$routines.h>
372 #include <starlet.h>
373 
374 
375 /*
376  * get_cpu_time() - returns CPU time in CLOCKS_PER_SEC since process start.
377  * on error returns (clock_t)-1.
378  *
379  * Intended to emulate (system + user) result of *NIX times(), if CRTL times()
380  * is not available.
381 *  However, this accounts only for the current process. To account for child
382 *  processes, these need to be directly spawned/forked via exec().
383 *  Moreover, child processes should be running a C main program or a program
384 *  that calls VAXC$CRTL_INIT or DECC$CRTL_INIT.
385 */
386 
get_cpu_time()387 clock_t get_cpu_time()
388 {
389     clock_t result = (clock_t) 0;
390 
391     IOSB iosb;
392     int status;
393     long cputime = 0;
394 
395 
396     ILE3 jpi_items[] = {
397         { sizeof( cputime ), JPI$_CPUTIM, &cputime, NULL }, /* longword int, 10ms */
398         { 0 },
399     };
400 
401     status = sys$getjpiw (EFN$C_ENF, 0, 0, jpi_items, &iosb, 0, 0);
402 
403     if ( !$VMS_STATUS_SUCCESS( status ) )
404     {
405         lib$signal( status );
406 
407         result = (clock_t) -1;
408         return result;
409     }
410 
411 
412     result = ( cputime / 100 ) * CLOCKS_PER_SEC;
413 
414     return result;
415 }
416 
417 
418 # endif /* VMS */
419 
420