Imported Upstream version 4.2
[platform/upstream/make.git] / vmsjobs.c
1 /* --------------- Moved here from job.c ---------------
2    This file must be #included in job.c, as it accesses static functions.
3
4 Copyright (C) 1996-2016 Free Software Foundation, Inc.
5 This file is part of GNU Make.
6
7 GNU Make is free software; you can redistribute it and/or modify it under the
8 terms of the GNU General Public License as published by the Free Software
9 Foundation; either version 3 of the License, or (at your option) any later
10 version.
11
12 GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along with
17 this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #include <string.h>
20 #include <descrip.h>
21 #include <clidef.h>
22
23 /* TODO - VMS specific header file conditionally included in makeint.h */
24
25 #include <stsdef.h>
26 #include <ssdef.h>
27 void
28 decc$exit (int status);
29
30 /* Lowest legal non-success VMS exit code is 8 */
31 /* GNU make only defines codes 0, 1, 2 */
32 /* So assume any exit code > 8 is a VMS exit code */
33
34 #ifndef MAX_EXPECTED_EXIT_CODE
35 # define MAX_EXPECTED_EXIT_CODE 7
36 #endif
37
38
39 #if __CRTL_VER >= 70302000 && !defined(__VAX)
40 # define MAX_DCL_LINE_LENGTH 4095
41 # define MAX_DCL_CMD_LINE_LENGTH 8192
42 #else
43 # define MAX_DCL_LINE_LENGTH 255
44 # define MAX_DCL_CMD_LINE_LENGTH 1024
45 #endif
46 #define MAX_DCL_TOKEN_LENGTH 255
47 #define MAX_DCL_TOKENS 127
48
49 enum auto_pipe { nopipe, add_pipe, dcl_pipe };
50
51 char *vmsify (char *name, int type);
52
53 static int vms_jobsefnmask = 0;
54
55 /* returns whether path is assumed to be a unix like shell. */
56 int
57 _is_unixy_shell (const char *path)
58 {
59   return vms_gnv_shell;
60 }
61
62 #define VMS_GETMSG_MAX 256
63 static char vms_strsignal_text[VMS_GETMSG_MAX + 2];
64
65 char *
66 vms_strsignal (int status)
67 {
68   if (status <= MAX_EXPECTED_EXIT_CODE)
69     sprintf (vms_strsignal_text, "lib$spawn returned %x", status);
70   else
71     {
72       int vms_status;
73       unsigned short * msg_len;
74       unsigned char out[4];
75       vms_status = SYS$GETMSG (status, &msg_len,
76                                vms_strsignal_text, 7, *out);
77     }
78
79   return vms_strsignal_text;
80 }
81
82
83 /* Wait for nchildren children to terminate */
84 static void
85 vmsWaitForChildren (int *status)
86 {
87   while (1)
88     {
89       if (!vms_jobsefnmask)
90         {
91           *status = 0;
92           return;
93         }
94
95       *status = sys$wflor (32, vms_jobsefnmask);
96     }
97   return;
98 }
99
100 static int ctrlYPressed= 0;
101 /* This is called at main or AST level. It is at AST level for DONTWAITFORCHILD
102    and at main level otherwise. In any case it is called when a child process
103    terminated. At AST level it won't get interrupted by anything except a
104    inner mode level AST.
105 */
106 static int
107 vmsHandleChildTerm (struct child *child)
108 {
109   int exit_code;
110   register struct child *lastc, *c;
111   int child_failed;
112
113   /* The child efn is 0 when a built-in or null command is executed
114      successfully with out actually creating a child.
115   */
116   if (child->efn > 0)
117   {
118     vms_jobsefnmask &= ~(1 << (child->efn - 32));
119
120     lib$free_ef (&child->efn);
121   }
122   if (child->comname)
123     {
124       if (!ISDB (DB_JOBS) && !ctrlYPressed)
125         unlink (child->comname);
126       free (child->comname);
127     }
128
129   (void) sigblock (fatal_signal_mask);
130
131   /* First check to see if this is a POSIX exit status and handle */
132   if ((child->cstatus & VMS_POSIX_EXIT_MASK) == VMS_POSIX_EXIT_MASK)
133     {
134       exit_code = (child->cstatus >> 3) & 255;
135       if (exit_code != MAKE_SUCCESS)
136         child_failed = 1;
137     }
138   else
139     {
140       child_failed = !$VMS_STATUS_SUCCESS (child->cstatus);
141       if (child_failed)
142         exit_code = child->cstatus;
143     }
144
145   /* Search for a child matching the deceased one.  */
146   lastc = 0;
147 #if defined(RECURSIVEJOBS)
148   /* I've had problems with recursive stuff and process handling */
149   for (c = children; c != 0 && c != child; lastc = c, c = c->next)
150     ;
151 #else
152   c = child;
153 #endif
154
155   if ($VMS_STATUS_SUCCESS (child->vms_launch_status))
156     {
157       /* Convert VMS success status to 0 for UNIX code to be happy */
158       child->vms_launch_status = 0;
159     }
160
161   /* Set the state flag to say the commands have finished.  */
162   c->file->command_state = cs_finished;
163   notice_finished_file (c->file);
164
165   (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask));
166
167   return 1;
168 }
169
170 /* VMS:
171    Spawn a process executing the command in ARGV and return its pid. */
172
173 /* local helpers to make ctrl+c and ctrl+y working, see below */
174 #include <iodef.h>
175 #include <libclidef.h>
176 #include <ssdef.h>
177
178 static int ctrlMask= LIB$M_CLI_CTRLY;
179 static int oldCtrlMask;
180 static int setupYAstTried= 0;
181 static unsigned short int chan= 0;
182
183 static void
184 reEnableAst(void)
185 {
186   lib$enable_ctrl (&oldCtrlMask,0);
187 }
188
189 static int
190 astYHandler (void)
191 {
192   struct child *c;
193   for (c = children; c != 0; c = c->next)
194     sys$delprc (&c->pid, 0, 0);
195   ctrlYPressed= 1;
196   kill (getpid(),SIGQUIT);
197   return SS$_NORMAL;
198 }
199
200 static void
201 tryToSetupYAst(void)
202 {
203   $DESCRIPTOR(inputDsc,"SYS$COMMAND");
204   int     status;
205   struct {
206     short int       status, count;
207     int     dvi;
208   } iosb;
209   unsigned short int loc_chan;
210
211   setupYAstTried++;
212
213   if (chan)
214     loc_chan= chan;
215   else
216     {
217       status= sys$assign(&inputDsc,&loc_chan,0,0);
218       if (!(status&SS$_NORMAL))
219         {
220           lib$signal(status);
221           return;
222         }
223     }
224   status= sys$qiow (0, loc_chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0,
225                     astYHandler,0,0,0,0,0);
226   if (status==SS$_NORMAL)
227     status= iosb.status;
228   if (status!=SS$_NORMAL)
229     {
230       if (!chan)
231         sys$dassgn(loc_chan);
232       if (status!=SS$_ILLIOFUNC && status!=SS$_NOPRIV)
233         lib$signal(status);
234       return;
235     }
236
237   /* called from AST handler ? */
238   if (setupYAstTried>1)
239     return;
240   if (atexit(reEnableAst))
241     fprintf (stderr,
242              _("-warning, you may have to re-enable CTRL-Y handling from DCL.\n"));
243   status= lib$disable_ctrl (&ctrlMask, &oldCtrlMask);
244   if (!(status&SS$_NORMAL))
245     {
246       lib$signal(status);
247       return;
248     }
249   if (!chan)
250     chan = loc_chan;
251 }
252
253 /* Check if a token is too long */
254 #define INC_TOKEN_LEN_OR_RETURN(x) {token->length++; \
255   if (token->length >= MAX_DCL_TOKEN_LENGTH) \
256     { token->cmd_errno = ERANGE; return x; }}
257
258 #define INC_TOKEN_LEN_OR_BREAK {token->length++; \
259   if (token->length >= MAX_DCL_TOKEN_LENGTH) \
260     { token->cmd_errno = ERANGE; break; }}
261
262 #define ADD_TOKEN_LEN_OR_RETURN(add_len, x) {token->length += add_len; \
263   if (token->length >= MAX_DCL_TOKEN_LENGTH) \
264     { token->cmd_errno = ERANGE; return x; }}
265
266 /* Check if we are out of space for more tokens */
267 #define V_NEXT_TOKEN { if (cmd_tkn_index < MAX_DCL_TOKENS) \
268   cmd_tokens[++cmd_tkn_index] = NULL; \
269   else { token.cmd_errno = E2BIG; break; } \
270   token.length = 0;}
271
272
273 #define UPDATE_TOKEN {cmd_tokens[cmd_tkn_index] = strdup(token.text); \
274   V_NEXT_TOKEN;}
275
276 #define EOS_ERROR(x) { if (*x == 0) { token->cmd_errno = ERANGE; break; }}
277
278 struct token_info
279   {
280     char *text;       /* Parsed text */
281     int length;       /* Length of parsed text */
282     char *src;        /* Pointer to source text */
283     int cmd_errno;    /* Error status of parse */
284     int use_cmd_file; /* Force use of a command file */
285   };
286
287
288 /* Extract a Posix single quoted string from input line */
289 static char *
290 posix_parse_sq (struct token_info *token)
291 {
292   /* A Posix quoted string with no expansion unless in a string
293      Unix simulation means no lexical functions present.
294   */
295   char * q;
296   char * p;
297   q = token->text;
298   p = token->src;
299
300   *q++ = '"';
301   p++;
302   INC_TOKEN_LEN_OR_RETURN (p);
303
304   while (*p != '\'' && (token->length < MAX_DCL_TOKEN_LENGTH))
305     {
306       EOS_ERROR (p);
307       if (*p == '"')
308         {
309           /* Embedded double quotes need to be doubled */
310           *q++ = '"';
311           INC_TOKEN_LEN_OR_BREAK;
312           *q = '"';
313         }
314       else
315         *q = *p;
316
317       q++;
318       p++;
319       INC_TOKEN_LEN_OR_BREAK;
320     }
321   *q++ = '"';
322   p++;
323   INC_TOKEN_LEN_OR_RETURN (p);
324   *q = 0;
325   return p;
326 }
327
328 /* Extract a Posix double quoted string from input line */
329 static char *
330 posix_parse_dq (struct token_info *token)
331 {
332   /* Unix mode:  Any imbedded \" becomes doubled.
333                  \t is tab, \\, \$ leading character stripped.
334                  $ character replaced with \' unless escaped.
335   */
336   char * q;
337   char * p;
338   q = token->text;
339   p = token->src;
340   *q++ = *p++;
341   INC_TOKEN_LEN_OR_RETURN (p);
342   while (*p != 0)
343     {
344       if (*p == '\\')
345         {
346           switch(p[1])
347             {
348             case 't':     /* Convert tabs */
349               *q = '\t';
350               p++;
351               break;
352             case '\\':     /* Just remove leading backslash */
353             case '$':
354               p++;
355               *q = *p;
356               break;
357             case '"':
358               p++;
359               *q = *p;
360               *q++ = '"';
361               INC_TOKEN_LEN_OR_BREAK;
362             default:      /* Pass through unchanged */
363               *q++ = *p++;
364               INC_TOKEN_LEN_OR_BREAK;
365             }
366           INC_TOKEN_LEN_OR_BREAK;
367         }
368       else if (*p == '$' && isalpha (p[1]))
369         {
370           /* A symbol we should be able to substitute */
371           *q++ = '\'';
372           INC_TOKEN_LEN_OR_BREAK;
373           *q = '\'';
374           INC_TOKEN_LEN_OR_BREAK;
375           token->use_cmd_file = 1;
376         }
377       else
378         {
379           *q = *p;
380           INC_TOKEN_LEN_OR_BREAK;
381           if (*p == '"')
382             {
383               p++;
384               q++;
385               break;
386             }
387         }
388       p++;
389       q++;
390     }
391   *q = 0;
392   return p;
393 }
394
395 /* Extract a VMS quoted string or substitution string from input line */
396 static char *
397 vms_parse_quotes (struct token_info *token)
398 {
399   /* VMS mode, the \' means that a symbol substitution is starting
400      so while you might think you can just copy until the next
401      \'.  Unfortunately the substitution can be a lexical function
402      which can contain embedded strings and lexical functions.
403      Messy, so both types need to be handled together.
404   */
405   char * q;
406   char * p;
407   q = token->text;
408   p = token->src;
409   int parse_level[MAX_DCL_TOKENS + 1];
410   int nest = 0;
411
412   parse_level[0] = *p;
413   if (parse_level[0] == '\'')
414     token->use_cmd_file = 1;
415
416   *q++ = *p++;
417   INC_TOKEN_LEN_OR_RETURN (p);
418
419
420   /* Copy everything until after the next single quote at nest == 0 */
421   while (token->length < MAX_DCL_TOKEN_LENGTH)
422     {
423       EOS_ERROR (p);
424       *q = *p;
425       INC_TOKEN_LEN_OR_BREAK;
426       if ((*p == parse_level[nest]) && (p[1] != '"'))
427         {
428           if (nest == 0)
429             {
430               *q++ = *p++;
431               break;
432             }
433           nest--;
434         }
435       else
436         {
437           switch(*p)
438             {
439             case '\\':
440               /* Handle continuation on to next line */
441               if (p[1] != '\n')
442                 break;
443               p++;
444               p++;
445               *q = *p;
446               break;
447             case '(':
448               /* Parenthesis only in single quote level */
449               if (parse_level[nest] == '\'')
450                 {
451                   nest++;
452                   parse_level[nest] == ')';
453                 }
454               break;
455             case '"':
456               /* Double quotes only in parenthesis */
457               if (parse_level[nest] == ')')
458                 {
459                   nest++;
460                   parse_level[nest] == '"';
461                 }
462               break;
463             case '\'':
464               /* Symbol substitution ony in double quotes */
465               if ((p[1] == '\'') && (parse_level[nest] == '"'))
466                 {
467                   nest++;
468                   parse_level[nest] == '\'';
469                   *p++ = *q++;
470                   token->use_cmd_file = 1;
471                   INC_TOKEN_LEN_OR_BREAK;
472                   break;
473                 }
474               *q = *p;
475             }
476         }
477       p++;
478       q++;
479       /* Pass through doubled double quotes */
480       if ((*p == '"') && (p[1] == '"') && (parse_level[nest] == '"'))
481       {
482         *p++ = *q++;
483         INC_TOKEN_LEN_OR_BREAK;
484         *p++ = *q++;
485         INC_TOKEN_LEN_OR_BREAK;
486       }
487     }
488   *q = 0;
489   return p;
490 }
491
492 /* Extract a $ string from the input line */
493 static char *
494 posix_parse_dollar (struct token_info *token)
495 {
496   /* $foo becomes 'foo' */
497   char * q;
498   char * p;
499   q = token->text;
500   p = token->src;
501   token->use_cmd_file = 1;
502
503   p++;
504   *q++ = '\'';
505   INC_TOKEN_LEN_OR_RETURN (p);
506
507   while ((isalnum (*p)) || (*p == '_'))
508     {
509       *q++ = *p++;
510       INC_TOKEN_LEN_OR_BREAK;
511     }
512   *q++ = '\'';
513   while (1)
514     {
515       INC_TOKEN_LEN_OR_BREAK;
516       break;
517     }
518   *q = 0;
519   return p;
520 }
521
522 const char *vms_filechars = "0123456789abcdefghijklmnopqrstuvwxyz" \
523    "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]<>:/_-.$";
524
525 /* Simple text copy */
526 static char *
527 parse_text (struct token_info *token, int assignment_hack)
528 {
529   char * q;
530   char * p;
531   int str_len;
532   q = token->text;
533   p = token->src;
534
535   /* If assignment hack, then this text needs to be double quoted. */
536   if (vms_unix_simulation && (assignment_hack == 2))
537     {
538       *q++ = '"';
539       INC_TOKEN_LEN_OR_RETURN (p);
540     }
541
542   *q++ = *p++;
543   INC_TOKEN_LEN_OR_RETURN (p);
544
545   while (*p != 0)
546     {
547       str_len = strspn (p, vms_filechars);
548       if (str_len == 0)
549         {
550           /* Pass through backslash escapes in Unix simulation
551              probably will not work anyway.
552              All any character after a ^ otherwise to support EFS.
553           */
554           if (vms_unix_simulation && (p[0] == '\\') && (p[1] != 0))
555             str_len = 2;
556           else if ((p[0] == '^') && (p[1] != 0))
557             str_len = 2;
558           else if (!vms_unix_simulation && (p[0] == ';'))
559             str_len = 1;
560
561           if (str_len == 0)
562             {
563               /* If assignment hack, then this needs to be double quoted. */
564               if (vms_unix_simulation && (assignment_hack == 2))
565               {
566                 *q++ = '"';
567                 INC_TOKEN_LEN_OR_RETURN (p);
568               }
569               *q = 0;
570               return p;
571             }
572         }
573       if (str_len > 0)
574         {
575           ADD_TOKEN_LEN_OR_RETURN (str_len, p);
576           strncpy (q, p, str_len);
577           p += str_len;
578           q += str_len;
579           *q = 0;
580         }
581     }
582   /* If assignment hack, then this text needs to be double quoted. */
583   if (vms_unix_simulation && (assignment_hack == 2))
584     {
585       *q++ = '"';
586       INC_TOKEN_LEN_OR_RETURN (p);
587     }
588   return p;
589 }
590
591 /* single character copy */
592 static char *
593 parse_char (struct token_info *token, int count)
594 {
595   char * q;
596   char * p;
597   q = token->text;
598   p = token->src;
599
600   while (count > 0)
601     {
602       *q++ = *p++;
603       INC_TOKEN_LEN_OR_RETURN (p);
604       count--;
605     }
606   *q = 0;
607   return p;
608 }
609
610 /* Build a command string from the collected tokens
611    and process built-ins now
612 */
613 static struct dsc$descriptor_s *
614 build_vms_cmd (char **cmd_tokens,
615                enum auto_pipe use_pipe_cmd,
616                int append_token)
617 {
618   struct dsc$descriptor_s *cmd_dsc;
619   int cmd_tkn_index;
620   char * cmd;
621   int cmd_len;
622   int semicolon_seen;
623
624   cmd_tkn_index = 0;
625   cmd_dsc = xmalloc (sizeof (struct dsc$descriptor_s));
626
627   /* Empty command? */
628   if (cmd_tokens[0] == NULL)
629     {
630       cmd_dsc->dsc$a_pointer = NULL;
631       cmd_dsc->dsc$w_length = 0;
632       return cmd_dsc;
633     }
634
635   /* Max DCL command + 1 extra token and trailing space */
636   cmd = xmalloc (MAX_DCL_CMD_LINE_LENGTH + 256);
637
638   cmd[0] = '$';
639   cmd[1] = 0;
640   cmd_len = 1;
641
642   /* Handle real or auto-pipe */
643   if (use_pipe_cmd == add_pipe)
644     {
645       /* We need to auto convert to a pipe command */
646       strcat (cmd, "pipe ");
647       cmd_len += 5;
648     }
649
650   semicolon_seen = 0;
651   while (cmd_tokens[cmd_tkn_index] != NULL)
652     {
653
654       /* Check for buffer overflow */
655       if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
656         {
657           errno = E2BIG;
658           break;
659         }
660
661       /* Eliminate double ';' */
662       if (semicolon_seen && (cmd_tokens[cmd_tkn_index][0] == ';'))
663         {
664           semicolon_seen = 0;
665           free (cmd_tokens[cmd_tkn_index++]);
666           if (cmd_tokens[cmd_tkn_index] == NULL)
667             break;
668         }
669
670       /* Special handling for CD built-in */
671       if (strncmp (cmd_tokens[cmd_tkn_index], "builtin_cd", 11) == 0)
672         {
673           int result;
674           semicolon_seen = 0;
675           free (cmd_tokens[cmd_tkn_index]);
676           cmd_tkn_index++;
677           if (cmd_tokens[cmd_tkn_index] == NULL)
678             break;
679           DB(DB_JOBS, (_("BUILTIN CD %s\n"), cmd_tokens[cmd_tkn_index]));
680
681           /* TODO: chdir fails with some valid syntaxes */
682           result = chdir (cmd_tokens[cmd_tkn_index]);
683           if (result != 0)
684             {
685               /* TODO: Handle failure better */
686               free (cmd);
687               while (cmd_tokens[cmd_tkn_index] == NULL)
688                 free (cmd_tokens[cmd_tkn_index++]);
689               cmd_dsc->dsc$w_length = -1;
690               cmd_dsc->dsc$a_pointer = NULL;
691               return cmd_dsc;
692             }
693         }
694       else if (strncmp (cmd_tokens[cmd_tkn_index], "exit", 5) == 0)
695         {
696           /* Copy the exit command */
697           semicolon_seen = 0;
698           strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
699           cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
700           free (cmd_tokens[cmd_tkn_index++]);
701           if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
702             {
703               errno = E2BIG;
704               break;
705             }
706
707           /* Optional whitespace */
708           if (isspace (cmd_tokens[cmd_tkn_index][0]))
709             {
710               strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
711               cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
712               free (cmd_tokens[cmd_tkn_index++]);
713               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
714               {
715                 errno = E2BIG;
716                 break;
717               }
718             }
719
720           /* There should be a status, but it is optional */
721           if (cmd_tokens[cmd_tkn_index][0] == ';')
722             continue;
723
724           /* If Unix simulation, add '((' */
725           if (vms_unix_simulation)
726             {
727               strcpy (&cmd[cmd_len], "((");
728               cmd_len += 2;
729               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
730                 {
731                   errno = E2BIG;
732                   break;
733                 }
734             }
735
736           /* Add the parameter */
737           strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
738           cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
739           free (cmd_tokens[cmd_tkn_index++]);
740           if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
741             {
742               errno = E2BIG;
743               break;
744             }
745
746           /* Add " * 8) .and. %x7f8) .or. %x1035a002" */
747           if (vms_unix_simulation)
748             {
749               const char *end_str = " * 8) .and. %x7f8) .or. %x1035a002";
750               strcpy (&cmd[cmd_len], end_str);
751               cmd_len += strlen (end_str);
752               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
753                 {
754                   errno = E2BIG;
755                   break;
756                 }
757             }
758           continue;
759         }
760
761       /* auto pipe needs spaces before semicolon */
762       if (use_pipe_cmd == add_pipe)
763         if (cmd_tokens[cmd_tkn_index][0] == ';')
764           {
765             cmd[cmd_len++] = ' ';
766             semicolon_seen = 1;
767             if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
768               {
769                 errno = E2BIG;
770                 break;
771               }
772           }
773         else
774           {
775             char ch;
776             ch = cmd_tokens[cmd_tkn_index][0];
777             if (!(ch == ' ' || ch == '\t'))
778               semicolon_seen = 0;
779           }
780
781       strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
782       cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
783
784       free (cmd_tokens[cmd_tkn_index++]);
785
786       /* Skip the append tokens if they exist */
787       if (cmd_tkn_index == append_token)
788         {
789           free (cmd_tokens[cmd_tkn_index++]);
790           if (isspace (cmd_tokens[cmd_tkn_index][0]))
791             free (cmd_tokens[cmd_tkn_index++]);
792           free (cmd_tokens[cmd_tkn_index++]);
793         }
794     }
795
796   cmd[cmd_len] = 0;
797   cmd_dsc->dsc$w_length = cmd_len;
798   cmd_dsc->dsc$a_pointer = cmd;
799   cmd_dsc->dsc$b_dtype = DSC$K_DTYPE_T;
800   cmd_dsc->dsc$b_class = DSC$K_CLASS_S;
801
802   return cmd_dsc;
803 }
804
805 int
806 child_execute_job (struct child *child, char *argv)
807 {
808   int i;
809
810   static struct dsc$descriptor_s *cmd_dsc;
811   static struct dsc$descriptor_s pnamedsc;
812   int spflags = CLI$M_NOWAIT;
813   int status;
814   int comnamelen;
815   char procname[100];
816
817   char *p;
818   char *cmd_tokens[(MAX_DCL_TOKENS * 2) + 1]; /* whitespace does not count */
819   char token_str[MAX_DCL_TOKEN_LENGTH + 1];
820   struct token_info token;
821   int cmd_tkn_index;
822   int paren_level = 0;
823   enum auto_pipe use_pipe_cmd = nopipe;
824   int append_token = -1;
825   char *append_file = NULL;
826   int unix_echo_cmd = 0;  /* Special handle Unix echo command */
827   int assignment_hack = 0; /* Handle x=y command as piped command */
828
829   /* Parse IO redirection.  */
830
831   child->comname = NULL;
832
833   DB (DB_JOBS, ("child_execute_job (%s)\n", argv));
834
835   while (isspace ((unsigned char)*argv))
836     argv++;
837
838   if (*argv == 0)
839     {
840       /* Only a built-in or a null command - Still need to run term AST */
841       child->cstatus = VMS_POSIX_EXIT_MASK;
842       child->vms_launch_status = SS$_NORMAL;
843       /* TODO what is this "magic number" */
844       child->pid = 270163; /* Special built-in */
845       child->efn = 0;
846       vmsHandleChildTerm (child);
847       return 1;
848     }
849
850   sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff);
851   pnamedsc.dsc$w_length = strlen (procname);
852   pnamedsc.dsc$a_pointer = procname;
853   pnamedsc.dsc$b_dtype = DSC$K_DTYPE_T;
854   pnamedsc.dsc$b_class = DSC$K_CLASS_S;
855
856   /* Old */
857   /* Handle comments and redirection.
858      For ONESHELL, the redirection must be on the first line. Any other
859      redirection token is handled by DCL, that is, the pipe command with
860      redirection can be used, but it should not be used on the first line
861      for ONESHELL. */
862
863   /* VMS parser notes:
864      1. A token is any of DCL verbs, qualifiers, parameters, or punctuation.
865      2. Only MAX_DCL_TOKENS per line in both one line or command file mode.
866      3. Each token limited to MAC_DCL_TOKEN_LENGTH
867      4. If the line to DCL is greater than MAX_DCL_LINE_LENGTH then a
868         command file must be used.
869      5. Currently a command file must be used symbol substitution is to
870         be performed.
871      6. Currently limiting command files to 2 * MAX_DCL_TOKENS.
872
873      Build both a command file token list and command line token list
874      until it is determined that the command line limits are exceeded.
875   */
876
877   cmd_tkn_index = 0;
878   cmd_tokens[cmd_tkn_index] = NULL;
879   p = argv;
880
881   token.text = token_str;
882   token.length = 0;
883   token.cmd_errno = 0;
884   token.use_cmd_file = 0;
885
886   while (*p != 0)
887     {
888       /* We can not build this command so give up */
889       if (token.cmd_errno != 0)
890         break;
891
892       token.src = p;
893
894       switch (*p)
895         {
896         case '\'':
897           if (vms_unix_simulation || unix_echo_cmd)
898             {
899               p = posix_parse_sq (&token);
900               UPDATE_TOKEN;
901               break;
902             }
903
904           /* VMS mode, the \' means that a symbol substitution is starting
905              so while you might think you can just copy until the next
906              \'.  Unfortunately the substitution can be a lexical function
907              which can contain embedded strings and lexical functions.
908              Messy.
909           */
910           p = vms_parse_quotes (&token);
911           UPDATE_TOKEN;
912           break;
913         case '"':
914           if (vms_unix_simulation)
915             {
916               p = posix_parse_dq (&token);
917               UPDATE_TOKEN;
918               break;
919             }
920
921           /* VMS quoted string, can contain lexical functions with
922              quoted strings and nested lexical functions.
923           */
924           p = vms_parse_quotes (&token);
925           UPDATE_TOKEN;
926           break;
927
928         case '$':
929           if (vms_unix_simulation)
930             {
931               p = posix_parse_dollar (&token);
932               UPDATE_TOKEN;
933               break;
934             }
935
936           /* Otherwise nothing special */
937           p = parse_text (&token, 0);
938           UPDATE_TOKEN;
939           break;
940         case '\\':
941           if (p[1] == '\n')
942             {
943               /* Line continuation, remove it */
944               p += 2;
945               break;
946             }
947
948           /* Ordinary character otherwise */
949           if (assignment_hack != 0)
950             assignment_hack++;
951           if (assignment_hack > 2)
952             {
953               assignment_hack = 0;          /* Reset */
954               if (use_pipe_cmd == nopipe)   /* force pipe use */
955                 use_pipe_cmd = add_pipe;
956               token_str[0] = ';';              /* add ; token */
957               token_str[1] = 0;
958               UPDATE_TOKEN;
959             }
960           p = parse_text (&token, assignment_hack);
961           UPDATE_TOKEN;
962           break;
963         case '!':
964         case '#':
965           /* Unix '#' is VMS '!' which comments out the rest of the line.
966              Historically the rest of the line has been skipped.
967              Not quite the right thing to do, as the f$verify lexical
968              function works in comments.  But this helps keep the line
969              lengths short.
970           */
971           unix_echo_cmd = 0;
972           while (*p != '\n' && *p != 0)
973             p++;
974           break;
975         case '(':
976           /* Subshell, equation, or lexical function argument start */
977           p = parse_char (&token, 1);
978           UPDATE_TOKEN;
979           paren_level++;
980           break;
981         case ')':
982           /* Close out a paren level */
983           p = parse_char (&token, 1);
984           UPDATE_TOKEN;
985           paren_level--;
986           /* TODO: Should we diagnose if paren_level goes negative? */
987           break;
988         case '&':
989           if (isalpha (p[1]) && !vms_unix_simulation)
990             {
991               /* VMS symbol substitution */
992               p = parse_text (&token, 0);
993               token.use_cmd_file = 1;
994               UPDATE_TOKEN;
995               break;
996             }
997           if (use_pipe_cmd == nopipe)
998             use_pipe_cmd = add_pipe;
999           if (p[1] != '&')
1000             p = parse_char (&token, 1);
1001           else
1002             p = parse_char (&token, 2);
1003           UPDATE_TOKEN;
1004           break;
1005         case '|':
1006           if (use_pipe_cmd == nopipe)
1007             use_pipe_cmd = add_pipe;
1008           if (p[1] != '|')
1009             p = parse_char (&token, 1);
1010           else
1011             p = parse_char (&token, 2);
1012           UPDATE_TOKEN;
1013           break;
1014         case ';':
1015           /* Separator - convert to a pipe command. */
1016           unix_echo_cmd = 0;
1017         case '<':
1018           if (use_pipe_cmd == nopipe)
1019             use_pipe_cmd = add_pipe;
1020           p = parse_char (&token, 1);
1021           UPDATE_TOKEN;
1022           break;
1023         case '>':
1024           if (use_pipe_cmd == nopipe)
1025             use_pipe_cmd = add_pipe;
1026           if (p[1] == '>')
1027             {
1028               /* Parsing would have been simple until support for the >>
1029                  append redirect was added.
1030                  Implementation needs:
1031                  * if not exist output file create empty
1032                  * open/append gnv$make_temp??? output_file
1033                  * define/user sys$output gnv$make_temp???
1034                  ** And all this done before the command previously tokenized.
1035                  * command previously tokenized
1036                  * close gnv$make_temp???
1037               */
1038               p = parse_char (&token, 2);
1039               append_token = cmd_tkn_index;
1040               token.use_cmd_file = 1;
1041             }
1042           else
1043             p = parse_char (&token, 1);
1044           UPDATE_TOKEN;
1045           break;
1046         case '/':
1047           /* Unix path or VMS option start, read until non-path symbol */
1048           if (assignment_hack != 0)
1049             assignment_hack++;
1050           if (assignment_hack > 2)
1051             {
1052               assignment_hack = 0;          /* Reset */
1053               if (use_pipe_cmd == nopipe)   /* force pipe use */
1054                 use_pipe_cmd = add_pipe;
1055               token_str[0] = ';';              /* add ; token */
1056               token_str[1] = 0;
1057               UPDATE_TOKEN;
1058             }
1059           p = parse_text (&token, assignment_hack);
1060           UPDATE_TOKEN;
1061           break;
1062         case ':':
1063           if ((p[1] == 0) || isspace (p[1]))
1064             {
1065               /* Unix Null command - treat as comment until next command */
1066               unix_echo_cmd = 0;
1067               p++;
1068               while (*p != 0)
1069                 {
1070                   if (*p == ';')
1071                     {
1072                       /* Remove Null command from pipeline */
1073                       p++;
1074                       break;
1075                     }
1076                   p++;
1077                 }
1078               break;
1079             }
1080
1081           /* String assignment */
1082           /* := :== or : */
1083           if (p[1] != '=')
1084             p = parse_char (&token, 1);
1085           else if (p[2] != '=')
1086             p = parse_char (&token, 2);
1087           else
1088             p = parse_char (&token, 3);
1089           UPDATE_TOKEN;
1090           break;
1091         case '=':
1092           /* = or == */
1093           /* If this is not an echo statement, this could be a shell
1094              assignment.  VMS requires the target to be quoted if it
1095              is not a macro substitution */
1096           if (!unix_echo_cmd && vms_unix_simulation && (assignment_hack == 0))
1097             assignment_hack = 1;
1098           if (p[1] != '=')
1099             p = parse_char (&token, 1);
1100           else
1101             p = parse_char (&token, 2);
1102           UPDATE_TOKEN;
1103           break;
1104         case '+':
1105         case '-':
1106         case '*':
1107           p = parse_char (&token, 1);
1108           UPDATE_TOKEN;
1109           break;
1110         case '.':
1111           /* .xxx. operation, VMS does not require the trailing . */
1112           p = parse_text (&token, 0);
1113           UPDATE_TOKEN;
1114           break;
1115         default:
1116           /* Skip repetitive whitespace */
1117           if (isspace (*p))
1118             {
1119               p = parse_char (&token, 1);
1120
1121               /* Force to a space or a tab */
1122               if ((token_str[0] != ' ') ||
1123                   (token_str[0] != '\t'))
1124                 token_str[0] = ' ';
1125               UPDATE_TOKEN;
1126
1127               while (isspace (*p))
1128                 p++;
1129               if (assignment_hack != 0)
1130                 assignment_hack++;
1131               break;
1132             }
1133
1134           if (assignment_hack != 0)
1135             assignment_hack++;
1136           if (assignment_hack > 2)
1137             {
1138               assignment_hack = 0;          /* Reset */
1139               if (use_pipe_cmd == nopipe)   /* force pipe use */
1140                 use_pipe_cmd = add_pipe;
1141               token_str[0] = ';';              /* add ; token */
1142               token_str[1] = 0;
1143               UPDATE_TOKEN;
1144             }
1145           p = parse_text (&token, assignment_hack);
1146           if (strncasecmp (token.text, "echo", 4) == 0)
1147             unix_echo_cmd = 1;
1148           else if (strncasecmp (token.text, "pipe", 4) == 0)
1149             use_pipe_cmd = dcl_pipe;
1150           UPDATE_TOKEN;
1151           break;
1152         }
1153     }
1154
1155   /* End up here with a list of tokens to build a command line.
1156      Deal with errors detected during parsing.
1157    */
1158   if (token.cmd_errno != 0)
1159     {
1160       while (cmd_tokens[cmd_tkn_index] == NULL)
1161         free (cmd_tokens[cmd_tkn_index++]);
1162       child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3);
1163       child->vms_launch_status = SS$_ABORT;
1164       /* TODO what is this "magic number" */
1165       child->pid = 270163; /* Special built-in */
1166       child->efn = 0;
1167       errno = token.cmd_errno;
1168       return 0;
1169     }
1170
1171   /* Save any redirection to append file */
1172   if (append_token != -1)
1173     {
1174       int file_token;
1175       char * lastdot;
1176       char * lastdir;
1177       char * raw_append_file;
1178       file_token = append_token;
1179       file_token++;
1180       if (isspace (cmd_tokens[file_token][0]))
1181         file_token++;
1182       raw_append_file = vmsify (cmd_tokens[file_token], 0);
1183       /* VMS DCL needs a trailing dot if null file extension */
1184       lastdot = strrchr(raw_append_file, '.');
1185       lastdir = strrchr(raw_append_file, ']');
1186       if (lastdir == NULL)
1187         lastdir = strrchr(raw_append_file, '>');
1188       if (lastdir == NULL)
1189         lastdir = strrchr(raw_append_file, ':');
1190       if ((lastdot == NULL) || (lastdot > lastdir))
1191         {
1192           append_file = xmalloc (strlen (raw_append_file) + 1);
1193           strcpy (append_file, raw_append_file);
1194           strcat (append_file, ".");
1195         }
1196       else
1197         append_file = strdup(raw_append_file);
1198     }
1199
1200   cmd_dsc = build_vms_cmd (cmd_tokens, use_pipe_cmd, append_token);
1201   if (cmd_dsc->dsc$a_pointer == NULL)
1202     {
1203       if (cmd_dsc->dsc$w_length < 0)
1204         {
1205           free (cmd_dsc);
1206           child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3);
1207           child->vms_launch_status = SS$_ABORT;
1208           /* TODO what is this "magic number" */
1209           child->pid = 270163; /* Special built-in */
1210           child->efn = 0;
1211           return 0;
1212         }
1213
1214       /* Only a built-in or a null command - Still need to run term AST */
1215       free (cmd_dsc);
1216       child->cstatus = VMS_POSIX_EXIT_MASK;
1217       child->vms_launch_status = SS$_NORMAL;
1218       /* TODO what is this "magic number" */
1219       child->pid = 270163; /* Special built-in */
1220       child->efn = 0;
1221       vmsHandleChildTerm (child);
1222       return 1;
1223     }
1224
1225   if (cmd_dsc->dsc$w_length > MAX_DCL_LINE_LENGTH)
1226     token.use_cmd_file = 1;
1227
1228   DB(DB_JOBS, (_("DCL: %s\n"), cmd_dsc->dsc$a_pointer));
1229
1230   /* Enforce the creation of a command file if "vms_always_use_cmd_file" is
1231      non-zero.
1232      Further, this way DCL reads the input stream and therefore does
1233      'forced' symbol substitution, which it doesn't do for one-liners when
1234      they are 'lib$spawn'ed.
1235
1236      Otherwise the behavior is:
1237
1238      Create a *.com file if either the command is too long for
1239      lib$spawn, or if a redirect appending to a file is desired, or
1240      symbol substitition.
1241   */
1242
1243   if (vms_always_use_cmd_file || token.use_cmd_file)
1244     {
1245       FILE *outfile;
1246       int cmd_len;
1247
1248       outfile = output_tmpfile (&child->comname,
1249                                 "sys$scratch:gnv$make_cmdXXXXXX.com");
1250       /*                                          012345678901234567890 */
1251       if (outfile == 0)
1252         pfatal_with_name (_("fopen (temporary file)"));
1253       comnamelen = strlen (child->comname);
1254
1255       /* The whole DCL "script" is executed as one action, and it behaves as
1256          any DCL "script", that is errors stop it but warnings do not. Usually
1257          the command on the last line, defines the exit code.  However, with
1258          redirections there is a prolog and possibly an epilog to implement
1259          the redirection.  Both are part of the script which is actually
1260          executed. So if the redirection encounters an error in the prolog,
1261          the user actions will not run; if in the epilog, the user actions
1262          ran, but output is not captured. In both error cases, the error of
1263          redirection is passed back and not the exit code of the actions. The
1264          user should be able to enable DCL "script" verification with "set
1265          verify". However, the prolog and epilog commands are not shown. Also,
1266          if output redirection is used, the verification output is redirected
1267          into that file as well. */
1268       fprintf (outfile, "$ gnv$$make_verify = \"''f$verify(0)'\"\n");
1269       fprintf (outfile, "$ gnv$$make_pid = f$getjpi(\"\",\"pid\")\n");
1270       fprintf (outfile, "$ on error then $ goto gnv$$make_error\n");
1271
1272       /* Handle append redirection */
1273       if (append_file != NULL)
1274         {
1275           /* If file does not exist, create it */
1276           fprintf (outfile,
1277                    "$ gnv$$make_al = \"gnv$$make_append''gnv$$make_pid'\"\n");
1278           fprintf (outfile,
1279                    "$ if f$search(\"%s\") .eqs. \"\" then create %s\n",
1280                    append_file, append_file);
1281
1282           fprintf (outfile,
1283                    "$ open/append 'gnv$$make_al' %s\n", append_file);
1284
1285           /* define sys$output to that file */
1286           fprintf (outfile,
1287                    "$ define/user sys$output 'gnv$$make_al'\n");
1288           DB (DB_JOBS, (_("Append output to %s\n"), append_file));
1289           free(append_file);
1290         }
1291
1292       fprintf (outfile, "$ gnv$$make_verify = f$verify(gnv$$make_verify)\n");
1293
1294       /* TODO:
1295          Only for ONESHELL there will be several commands separated by
1296          '\n'. But there can always be multiple continuation lines.
1297       */
1298
1299       fprintf (outfile, "%s\n", cmd_dsc->dsc$a_pointer);
1300       fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
1301       fprintf (outfile, "$ goto gnv$$make_exit\n");
1302
1303       /* Exit and clean up */
1304       fprintf (outfile, "$ gnv$$make_error: ! 'f$verify(0)\n");
1305       fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
1306
1307       if (append_token != -1)
1308         {
1309           fprintf (outfile, "$ deassign sys$output\n");
1310           fprintf (outfile, "$ close 'gnv$$make_al'\n");
1311
1312           DB (DB_JOBS,
1313               (_("Append %.*s and cleanup\n"), comnamelen-3, child->comname));
1314         }
1315       fprintf (outfile, "$ gnv$$make_exit: ! 'f$verify(0)\n");
1316       fprintf (outfile,
1317              "$ exit 'gnv$$make_status_2' + (0*f$verify(gnv$$make_verify))\n");
1318
1319       fclose (outfile);
1320
1321       free (cmd_dsc->dsc$a_pointer);
1322       cmd_dsc->dsc$a_pointer = xmalloc (256 + 4);
1323       sprintf (cmd_dsc->dsc$a_pointer, "$ @%s", child->comname);
1324       cmd_dsc->dsc$w_length = strlen (cmd_dsc->dsc$a_pointer);
1325
1326       DB (DB_JOBS, (_("Executing %s instead\n"), child->comname));
1327     }
1328
1329   child->efn = 0;
1330   while (child->efn < 32 || child->efn > 63)
1331     {
1332       status = LIB$GET_EF ((unsigned long *)&child->efn);
1333       if (!$VMS_STATUS_SUCCESS (status))
1334         {
1335           if (child->comname)
1336             {
1337               if (!ISDB (DB_JOBS))
1338                 unlink (child->comname);
1339               free (child->comname);
1340             }
1341           return 0;
1342         }
1343     }
1344
1345   SYS$CLREF (child->efn);
1346
1347   vms_jobsefnmask |= (1 << (child->efn - 32));
1348
1349   /* Export the child environment into DCL symbols */
1350   if (child->environment != 0)
1351     {
1352       char **ep = child->environment;
1353       while (*ep != 0)
1354         {
1355           vms_putenv_symbol (*ep);
1356           *ep++;
1357         }
1358     }
1359
1360   /*
1361     LIB$SPAWN  [command-string]
1362     [,input-file]
1363     [,output-file]
1364     [,flags]
1365     [,process-name]
1366     [,process-id] [,completion-status-address] [,byte-integer-event-flag-num]
1367     [,AST-address] [,varying-AST-argument]
1368     [,prompt-string] [,cli] [,table]
1369   */
1370
1371 #ifndef DONTWAITFORCHILD
1372   /*
1373    * Code to make ctrl+c and ctrl+y working.
1374    * The problem starts with the synchronous case where after lib$spawn is
1375    * called any input will go to the child. But with input re-directed,
1376    * both control characters won't make it to any of the programs, neither
1377    * the spawning nor to the spawned one. Hence the caller needs to spawn
1378    * with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr
1379    * has to follow to simulate the wanted synchronous behaviour.
1380    * The next problem is ctrl+y which isn't caught by the crtl and
1381    * therefore isn't converted to SIGQUIT (for a signal handler which is
1382    * already established). The only way to catch ctrl+y, is an AST
1383    * assigned to the input channel. But ctrl+y handling of DCL needs to be
1384    * disabled, otherwise it will handle it. Not to mention the previous
1385    * ctrl+y handling of DCL needs to be re-established before make exits.
1386    * One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will
1387    * make it to the signal handler after the child "normally" terminates.
1388    * This isn't enough. It seems reasonable for simple command lines like
1389    * a 'cc foobar.c' spawned in a subprocess but it is unacceptable for
1390    * spawning make. Therefore we need to abort the process in the AST.
1391    *
1392    * Prior to the spawn it is checked if an AST is already set up for
1393    * ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general
1394    * this will work except if make is run in a batch environment, but there
1395    * nobody can press ctrl+y. During the setup the DCL handling of ctrl+y
1396    * is disabled and an exit handler is established to re-enable it.
1397    * If the user interrupts with ctrl+y, the assigned AST will fire, force
1398    * an abort to the subprocess and signal SIGQUIT, which will be caught by
1399    * the already established handler and will bring us back to common code.
1400    * After the spawn (now /nowait) a sys$waitfr simulates the /wait and
1401    * enables the ctrl+y be delivered to this code. And the ctrl+c too,
1402    * which the crtl converts to SIGINT and which is caught by the common
1403    * signal handler. Because signals were blocked before entering this code
1404    * sys$waitfr will always complete and the SIGQUIT will be processed after
1405    * it (after termination of the current block, somewhere in common code).
1406    * And SIGINT too will be delayed. That is ctrl+c can only abort when the
1407    * current command completes. Anyway it's better than nothing :-)
1408    */
1409
1410   if (!setupYAstTried)
1411     tryToSetupYAst();
1412   child->vms_launch_status = lib$spawn (cmd_dsc,               /* cmd-string */
1413                      NULL, /* input-file */
1414                      NULL, /* output-file */
1415                      &spflags,                                 /* flags */
1416                      &pnamedsc,                                /* proc name */
1417                      &child->pid, &child->cstatus, &child->efn,
1418                      0, 0,
1419                      0, 0, 0);
1420
1421   status = child->vms_launch_status;
1422   if ($VMS_STATUS_SUCCESS (status))
1423     {
1424       status = sys$waitfr (child->efn);
1425       vmsHandleChildTerm (child);
1426     }
1427 #else
1428   child->vms_launch_status = lib$spawn (cmd_dsc,
1429                       NULL,
1430                       NULL,
1431                       &spflags,
1432                       &pnamedsc,
1433                       &child->pid, &child->cstatus, &child->efn,
1434                       vmsHandleChildTerm, child,
1435                       0, 0, 0);
1436    status = child->vms_launch_status;
1437 #endif
1438
1439   /* Free the pointer if not a command file */
1440   if (!vms_always_use_cmd_file && !token.use_cmd_file)
1441     free (cmd_dsc->dsc$a_pointer);
1442   free (cmd_dsc);
1443
1444   if (!$VMS_STATUS_SUCCESS (status))
1445     {
1446       switch (status)
1447         {
1448         case SS$_EXQUOTA:
1449           errno = EPROCLIM;
1450           break;
1451         default:
1452           errno = EFAIL;
1453         }
1454     }
1455
1456   /* Restore the VMS symbols that were changed */
1457   if (child->environment != 0)
1458     {
1459       char **ep = child->environment;
1460       while (*ep != 0)
1461         {
1462           vms_restore_symbol (*ep);
1463           *ep++;
1464         }
1465     }
1466
1467   return (status & 1);
1468 }