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-2022 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 <https://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 embedded \" 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 ((unsigned char) 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 only 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 ((unsigned char) *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 ((unsigned char) 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 ((unsigned char) 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 ((unsigned char) 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 ((unsigned char) 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 ((unsigned char) *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 ((unsigned char) *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 ((unsigned char) 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
1246 comnamelen = strlen (child->comname);
1247
1248 /* The whole DCL "script" is executed as one action, and it behaves as
1249 any DCL "script", that is errors stop it but warnings do not. Usually
1250 the command on the last line, defines the exit code. However, with
1251 redirections there is a prolog and possibly an epilog to implement
1252 the redirection. Both are part of the script which is actually
1253 executed. So if the redirection encounters an error in the prolog,
1254 the user actions will not run; if in the epilog, the user actions
1255 ran, but output is not captured. In both error cases, the error of
1256 redirection is passed back and not the exit code of the actions. The
1257 user should be able to enable DCL "script" verification with "set
1258 verify". However, the prolog and epilog commands are not shown. Also,
1259 if output redirection is used, the verification output is redirected
1260 into that file as well. */
1261 fprintf (outfile, "$ gnv$$make_verify = \"''f$verify(0)'\"\n");
1262 fprintf (outfile, "$ gnv$$make_pid = f$getjpi(\"\",\"pid\")\n");
1263 fprintf (outfile, "$ on error then $ goto gnv$$make_error\n");
1264
1265 /* Handle append redirection */
1266 if (append_file != NULL)
1267 {
1268 /* If file does not exist, create it */
1269 fprintf (outfile,
1270 "$ gnv$$make_al = \"gnv$$make_append''gnv$$make_pid'\"\n");
1271 fprintf (outfile,
1272 "$ if f$search(\"%s\") .eqs. \"\" then create %s\n",
1273 append_file, append_file);
1274
1275 fprintf (outfile,
1276 "$ open/append 'gnv$$make_al' %s\n", append_file);
1277
1278 /* define sys$output to that file */
1279 fprintf (outfile,
1280 "$ define/user sys$output 'gnv$$make_al'\n");
1281 DB (DB_JOBS, (_("Append output to %s\n"), append_file));
1282 free(append_file);
1283 }
1284
1285 fprintf (outfile, "$ gnv$$make_verify = f$verify(gnv$$make_verify)\n");
1286
1287 /* TODO:
1288 Only for ONESHELL there will be several commands separated by
1289 '\n'. But there can always be multiple continuation lines.
1290 */
1291
1292 fprintf (outfile, "%s\n", cmd_dsc->dsc$a_pointer);
1293 fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
1294 fprintf (outfile, "$ goto gnv$$make_exit\n");
1295
1296 /* Exit and clean up */
1297 fprintf (outfile, "$ gnv$$make_error: ! 'f$verify(0)\n");
1298 fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
1299
1300 if (append_token != -1)
1301 {
1302 fprintf (outfile, "$ deassign sys$output\n");
1303 fprintf (outfile, "$ close 'gnv$$make_al'\n");
1304
1305 DB (DB_JOBS,
1306 (_("Append %.*s and cleanup\n"), comnamelen-3, child->comname));
1307 }
1308 fprintf (outfile, "$ gnv$$make_exit: ! 'f$verify(0)\n");
1309 fprintf (outfile,
1310 "$ exit 'gnv$$make_status_2' + (0*f$verify(gnv$$make_verify))\n");
1311
1312 fclose (outfile);
1313
1314 free (cmd_dsc->dsc$a_pointer);
1315 cmd_dsc->dsc$a_pointer = xmalloc (256 + 4);
1316 sprintf (cmd_dsc->dsc$a_pointer, "$ @%s", child->comname);
1317 cmd_dsc->dsc$w_length = strlen (cmd_dsc->dsc$a_pointer);
1318
1319 DB (DB_JOBS, (_("Executing %s instead\n"), child->comname));
1320 }
1321
1322 child->efn = 0;
1323 while (child->efn < 32 || child->efn > 63)
1324 {
1325 status = LIB$GET_EF ((unsigned long *)&child->efn);
1326 if (!$VMS_STATUS_SUCCESS (status))
1327 {
1328 if (child->comname)
1329 {
1330 if (!ISDB (DB_JOBS))
1331 unlink (child->comname);
1332 free (child->comname);
1333 }
1334 return -1;
1335 }
1336 }
1337
1338 SYS$CLREF (child->efn);
1339
1340 vms_jobsefnmask |= (1 << (child->efn - 32));
1341
1342 /* Export the child environment into DCL symbols */
1343 if (child->environment != 0)
1344 {
1345 char **ep = child->environment;
1346 while (*ep != 0)
1347 {
1348 vms_putenv_symbol (*ep);
1349 *ep++;
1350 }
1351 }
1352
1353 /*
1354 LIB$SPAWN [command-string]
1355 [,input-file]
1356 [,output-file]
1357 [,flags]
1358 [,process-name]
1359 [,process-id] [,completion-status-address] [,byte-integer-event-flag-num]
1360 [,AST-address] [,varying-AST-argument]
1361 [,prompt-string] [,cli] [,table]
1362 */
1363
1364 #ifndef DONTWAITFORCHILD
1365 /*
1366 * Code to make ctrl+c and ctrl+y working.
1367 * The problem starts with the synchronous case where after lib$spawn is
1368 * called any input will go to the child. But with input re-directed,
1369 * both control characters won't make it to any of the programs, neither
1370 * the spawning nor to the spawned one. Hence the caller needs to spawn
1371 * with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr
1372 * has to follow to simulate the wanted synchronous behaviour.
1373 * The next problem is ctrl+y which isn't caught by the crtl and
1374 * therefore isn't converted to SIGQUIT (for a signal handler which is
1375 * already established). The only way to catch ctrl+y, is an AST
1376 * assigned to the input channel. But ctrl+y handling of DCL needs to be
1377 * disabled, otherwise it will handle it. Not to mention the previous
1378 * ctrl+y handling of DCL needs to be re-established before make exits.
1379 * One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will
1380 * make it to the signal handler after the child "normally" terminates.
1381 * This isn't enough. It seems reasonable for simple command lines like
1382 * a 'cc foobar.c' spawned in a subprocess but it is unacceptable for
1383 * spawning make. Therefore we need to abort the process in the AST.
1384 *
1385 * Prior to the spawn it is checked if an AST is already set up for
1386 * ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general
1387 * this will work except if make is run in a batch environment, but there
1388 * nobody can press ctrl+y. During the setup the DCL handling of ctrl+y
1389 * is disabled and an exit handler is established to re-enable it.
1390 * If the user interrupts with ctrl+y, the assigned AST will fire, force
1391 * an abort to the subprocess and signal SIGQUIT, which will be caught by
1392 * the already established handler and will bring us back to common code.
1393 * After the spawn (now /nowait) a sys$waitfr simulates the /wait and
1394 * enables the ctrl+y be delivered to this code. And the ctrl+c too,
1395 * which the crtl converts to SIGINT and which is caught by the common
1396 * signal handler. Because signals were blocked before entering this code
1397 * sys$waitfr will always complete and the SIGQUIT will be processed after
1398 * it (after termination of the current block, somewhere in common code).
1399 * And SIGINT too will be delayed. That is ctrl+c can only abort when the
1400 * current command completes. Anyway it's better than nothing :-)
1401 */
1402
1403 if (!setupYAstTried)
1404 tryToSetupYAst();
1405 child->vms_launch_status = lib$spawn (cmd_dsc, /* cmd-string */
1406 NULL, /* input-file */
1407 NULL, /* output-file */
1408 &spflags, /* flags */
1409 &pnamedsc, /* proc name */
1410 &child->pid, &child->cstatus, &child->efn,
1411 0, 0,
1412 0, 0, 0);
1413
1414 status = child->vms_launch_status;
1415 if ($VMS_STATUS_SUCCESS (status))
1416 {
1417 status = sys$waitfr (child->efn);
1418 vmsHandleChildTerm (child);
1419 }
1420 #else
1421 child->vms_launch_status = lib$spawn (cmd_dsc,
1422 NULL,
1423 NULL,
1424 &spflags,
1425 &pnamedsc,
1426 &child->pid, &child->cstatus, &child->efn,
1427 vmsHandleChildTerm, child,
1428 0, 0, 0);
1429 status = child->vms_launch_status;
1430 #endif
1431
1432 /* Free the pointer if not a command file */
1433 if (!vms_always_use_cmd_file && !token.use_cmd_file)
1434 free (cmd_dsc->dsc$a_pointer);
1435 free (cmd_dsc);
1436
1437 if (!$VMS_STATUS_SUCCESS (status))
1438 {
1439 switch (status)
1440 {
1441 case SS$_EXQUOTA:
1442 errno = EPROCLIM;
1443 break;
1444 default:
1445 errno = EFAIL;
1446 }
1447 }
1448
1449 /* Restore the VMS symbols that were changed */
1450 if (child->environment != 0)
1451 {
1452 char **ep = child->environment;
1453 while (*ep != 0)
1454 {
1455 vms_restore_symbol (*ep);
1456 *ep++;
1457 }
1458 }
1459
1460 /* TODO what is this "magic number" */
1461 return (status & 1) ? 270163 : -1 ;
1462 }