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