71492a8de575c7a139ee25c989a839912715e5f4
[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, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
5 2007, 2008, 2009 Free Software Foundation, Inc.
6 This file is part of GNU Make.
7
8 GNU Make is free software; you can redistribute it and/or modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation; either version 3 of the License, or (at your option) any later
11 version.
12
13 GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License along with
18 this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 #include <string.h>
21 #include <descrip.h>
22 #include <clidef.h>
23
24 char *vmsify (char *name, int type);
25
26 static int vms_jobsefnmask = 0;
27
28 /* Wait for nchildren children to terminate */
29 static void
30 vmsWaitForChildren(int *status)
31 {
32   while (1)
33     {
34       if (!vms_jobsefnmask)
35         {
36           *status = 0;
37           return;
38         }
39
40       *status = sys$wflor (32, vms_jobsefnmask);
41     }
42   return;
43 }
44
45 /* Set up IO redirection.  */
46
47 char *
48 vms_redirect (struct dsc$descriptor_s *desc, char *fname, char *ibuf)
49 {
50   char *fptr;
51
52   ibuf++;
53   while (isspace ((unsigned char)*ibuf))
54     ibuf++;
55   fptr = ibuf;
56   while (*ibuf && !isspace ((unsigned char)*ibuf))
57     ibuf++;
58   *ibuf = 0;
59   if (strcmp (fptr, "/dev/null") != 0)
60     {
61       strcpy (fname, vmsify (fptr, 0));
62       if (strchr (fname, '.') == 0)
63         strcat (fname, ".");
64     }
65   desc->dsc$w_length = strlen(fname);
66   desc->dsc$a_pointer = fname;
67   desc->dsc$b_dtype = DSC$K_DTYPE_T;
68   desc->dsc$b_class = DSC$K_CLASS_S;
69
70   if (*fname == 0)
71     printf (_("Warning: Empty redirection\n"));
72   return ibuf;
73 }
74
75
76 /* found apostrophe at (p-1)
77    inc p until after closing apostrophe.
78  */
79
80 char *
81 vms_handle_apos (char *p)
82 {
83   int alast;
84
85 #define SEPCHARS ",/()= "
86
87   alast = 0;
88
89   while (*p != 0)
90     {
91       if (*p == '"')
92         {
93           if (alast)
94             {
95               alast = 0;
96               p++;
97             }
98           else
99             {
100               p++;
101               if (strchr (SEPCHARS, *p))
102                 break;
103               alast = 1;
104             }
105         }
106       else
107         p++;
108     }
109
110   return p;
111 }
112
113 /* This is called as an AST when a child process dies (it won't get
114    interrupted by anything except a higher level AST).
115 */
116 int
117 vmsHandleChildTerm(struct child *child)
118 {
119     int status;
120     register struct child *lastc, *c;
121     int child_failed;
122
123     vms_jobsefnmask &= ~(1 << (child->efn - 32));
124
125     lib$free_ef(&child->efn);
126
127     (void) sigblock (fatal_signal_mask);
128
129     child_failed = !(child->cstatus & 1 || ((child->cstatus & 7) == 0));
130
131     /* Search for a child matching the deceased one.  */
132     lastc = 0;
133 #if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
134     for (c = children; c != 0 && c != child; lastc = c, c = c->next)
135       ;
136 #else
137     c = child;
138 #endif
139
140     if (child_failed && !c->noerror && !ignore_errors_flag)
141       {
142         /* The commands failed.  Write an error message,
143            delete non-precious targets, and abort.  */
144         child_error (c->file->name, c->cstatus, 0, 0, 0);
145         c->file->update_status = 1;
146         delete_child_targets (c);
147       }
148     else
149       {
150         if (child_failed)
151           {
152             /* The commands failed, but we don't care.  */
153             child_error (c->file->name, c->cstatus, 0, 0, 1);
154             child_failed = 0;
155           }
156
157 #if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
158         /* If there are more commands to run, try to start them.  */
159         start_job (c);
160
161         switch (c->file->command_state)
162           {
163           case cs_running:
164             /* Successfully started.  */
165             break;
166
167           case cs_finished:
168             if (c->file->update_status != 0) {
169                 /* We failed to start the commands.  */
170                 delete_child_targets (c);
171             }
172             break;
173
174           default:
175             error (NILF, _("internal error: `%s' command_state"),
176                    c->file->name);
177             abort ();
178             break;
179           }
180 #endif /* RECURSIVEJOBS */
181       }
182
183     /* Set the state flag to say the commands have finished.  */
184     c->file->command_state = cs_finished;
185     notice_finished_file (c->file);
186
187 #if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
188     /* Remove the child from the chain and free it.  */
189     if (lastc == 0)
190       children = c->next;
191     else
192       lastc->next = c->next;
193     free_child (c);
194 #endif /* RECURSIVEJOBS */
195
196     /* There is now another slot open.  */
197     if (job_slots_used > 0)
198       --job_slots_used;
199
200     /* If the job failed, and the -k flag was not given, die.  */
201     if (child_failed && !keep_going_flag)
202       die (EXIT_FAILURE);
203
204     (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask));
205
206     return 1;
207 }
208
209 /* VMS:
210    Spawn a process executing the command in ARGV and return its pid. */
211
212 #define MAXCMDLEN 200
213
214 /* local helpers to make ctrl+c and ctrl+y working, see below */
215 #include <iodef.h>
216 #include <libclidef.h>
217 #include <ssdef.h>
218
219 static int ctrlMask= LIB$M_CLI_CTRLY;
220 static int oldCtrlMask;
221 static int setupYAstTried= 0;
222 static int pidToAbort= 0;
223 static int chan= 0;
224
225 static void
226 reEnableAst(void)
227 {
228         lib$enable_ctrl (&oldCtrlMask,0);
229 }
230
231 static void
232 astHandler (void)
233 {
234         if (pidToAbort) {
235                 sys$forcex (&pidToAbort, 0, SS$_ABORT);
236                 pidToAbort= 0;
237         }
238         kill (getpid(),SIGQUIT);
239 }
240
241 static void
242 tryToSetupYAst(void)
243 {
244         $DESCRIPTOR(inputDsc,"SYS$COMMAND");
245         int     status;
246         struct {
247                 short int       status, count;
248                 int     dvi;
249         } iosb;
250
251         setupYAstTried++;
252
253         if (!chan) {
254                 status= sys$assign(&inputDsc,&chan,0,0);
255                 if (!(status&SS$_NORMAL)) {
256                         lib$signal(status);
257                         return;
258                 }
259         }
260         status= sys$qiow (0, chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0,
261                 astHandler,0,0,0,0,0);
262         if (status==SS$_NORMAL)
263                 status= iosb.status;
264         if (status==SS$_ILLIOFUNC || status==SS$_NOPRIV) {
265                 sys$dassgn(chan);
266 #ifdef  CTRLY_ENABLED_ANYWAY
267                 fprintf (stderr,
268                          _("-warning, CTRL-Y will leave sub-process(es) around.\n"));
269 #else
270                 return;
271 #endif
272         }
273         else if (!(status&SS$_NORMAL)) {
274                 sys$dassgn(chan);
275                 lib$signal(status);
276                 return;
277         }
278
279         /* called from AST handler ? */
280         if (setupYAstTried>1)
281                 return;
282         if (atexit(reEnableAst))
283                 fprintf (stderr,
284                          _("-warning, you may have to re-enable CTRL-Y handling from DCL.\n"));
285         status= lib$disable_ctrl (&ctrlMask, &oldCtrlMask);
286         if (!(status&SS$_NORMAL)) {
287                 lib$signal(status);
288                 return;
289         }
290 }
291
292 int
293 child_execute_job (char *argv, struct child *child)
294 {
295   int i;
296   static struct dsc$descriptor_s cmddsc;
297   static struct dsc$descriptor_s pnamedsc;
298   static struct dsc$descriptor_s ifiledsc;
299   static struct dsc$descriptor_s ofiledsc;
300   static struct dsc$descriptor_s efiledsc;
301   int have_redirection = 0;
302   int have_newline = 0;
303
304   int spflags = CLI$M_NOWAIT;
305   int status;
306   char *cmd = alloca (strlen (argv) + 512), *p, *q;
307   char ifile[256], ofile[256], efile[256];
308   char *comname = 0;
309   char procname[100];
310   int in_string;
311
312   /* Parse IO redirection.  */
313
314   ifile[0] = 0;
315   ofile[0] = 0;
316   efile[0] = 0;
317
318   DB (DB_JOBS, ("child_execute_job (%s)\n", argv));
319
320   while (isspace ((unsigned char)*argv))
321     argv++;
322
323   if (*argv == 0)
324     return 0;
325
326   sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff);
327   pnamedsc.dsc$w_length = strlen(procname);
328   pnamedsc.dsc$a_pointer = procname;
329   pnamedsc.dsc$b_dtype = DSC$K_DTYPE_T;
330   pnamedsc.dsc$b_class = DSC$K_CLASS_S;
331
332   in_string = 0;
333   /* Handle comments and redirection. */
334   for (p = argv, q = cmd; *p; p++, q++)
335     {
336       if (*p == '"')
337         in_string = !in_string;
338       if (in_string)
339         {
340           *q = *p;
341           continue;
342         }
343       switch (*p)
344         {
345           case '#':
346             *p-- = 0;
347             *q-- = 0;
348             break;
349           case '\\':
350             p++;
351             if (*p == '\n')
352               p++;
353             if (isspace ((unsigned char)*p))
354               {
355                 do { p++; } while (isspace ((unsigned char)*p));
356                 p--;
357               }
358             *q = *p;
359             break;
360           case '<':
361             p = vms_redirect (&ifiledsc, ifile, p);
362             *q = ' ';
363             have_redirection = 1;
364             break;
365           case '>':
366             have_redirection = 1;
367             if (*(p-1) == '2')
368               {
369                 q--;
370                 if (strncmp (p, ">&1", 3) == 0)
371                   {
372                     p += 3;
373                     strcpy (efile, "sys$output");
374                     efiledsc.dsc$w_length = strlen(efile);
375                     efiledsc.dsc$a_pointer = efile;
376                     efiledsc.dsc$b_dtype = DSC$K_DTYPE_T;
377                     efiledsc.dsc$b_class = DSC$K_CLASS_S;
378                   }
379                 else
380                   {
381                     p = vms_redirect (&efiledsc, efile, p);
382                   }
383               }
384             else
385               {
386                 p = vms_redirect (&ofiledsc, ofile, p);
387               }
388             *q = ' ';
389             break;
390           case '\n':
391             have_newline = 1;
392           default:
393             *q = *p;
394             break;
395         }
396     }
397   *q = *p;
398   while (isspace ((unsigned char)*--q))
399     *q = '\0';
400
401   if (strncmp (cmd, "builtin_", 8) == 0)
402     {
403       child->pid = 270163;
404       child->efn = 0;
405       child->cstatus = 1;
406
407       DB (DB_JOBS, (_("BUILTIN [%s][%s]\n"), cmd, cmd+8));
408
409       p = cmd + 8;
410
411       if ((*(p) == 'c')
412           && (*(p+1) == 'd')
413           && ((*(p+2) == ' ') || (*(p+2) == '\t')))
414         {
415           p += 3;
416           while ((*p == ' ') || (*p == '\t'))
417             p++;
418           DB (DB_JOBS, (_("BUILTIN CD %s\n"), p));
419           if (chdir (p))
420             return 0;
421           else
422             return 1;
423         }
424       else if ((*(p) == 'r')
425           && (*(p+1) == 'm')
426           && ((*(p+2) == ' ') || (*(p+2) == '\t')))
427         {
428           int in_arg;
429
430           /* rm  */
431           p += 3;
432           while ((*p == ' ') || (*p == '\t'))
433             p++;
434           in_arg = 1;
435
436           DB (DB_JOBS, (_("BUILTIN RM %s\n"), p));
437           while (*p)
438             {
439               switch (*p)
440                 {
441                   case ' ':
442                   case '\t':
443                     if (in_arg)
444                       {
445                         *p++ = ';';
446                         in_arg = 0;
447                       }
448                     break;
449                   default:
450                     break;
451                 }
452               p++;
453             }
454         }
455       else
456         {
457           printf(_("Unknown builtin command '%s'\n"), cmd);
458           fflush(stdout);
459           return 0;
460         }
461     }
462
463   /* Create a *.com file if either the command is too long for
464      lib$spawn, or the command contains a newline, or if redirection
465      is desired. Forcing commands with newlines into DCLs allows to
466      store search lists on user mode logicals.  */
467
468   if (strlen (cmd) > MAXCMDLEN
469       || (have_redirection != 0)
470       || (have_newline != 0))
471     {
472       FILE *outfile;
473       char c;
474       char *sep;
475       int alevel = 0;   /* apostrophe level */
476
477       if (strlen (cmd) == 0)
478         {
479           printf (_("Error, empty command\n"));
480           fflush (stdout);
481           return 0;
482         }
483
484       outfile = open_tmpfile (&comname, "sys$scratch:CMDXXXXXX.COM");
485       if (outfile == 0)
486         pfatal_with_name (_("fopen (temporary file)"));
487
488       if (ifile[0])
489         {
490           fprintf (outfile, "$ assign/user %s sys$input\n", ifile);
491           DB (DB_JOBS, (_("Redirected input from %s\n"), ifile));
492           ifiledsc.dsc$w_length = 0;
493         }
494
495       if (efile[0])
496         {
497           fprintf (outfile, "$ define sys$error %s\n", efile);
498           DB (DB_JOBS, (_("Redirected error to %s\n"), efile));
499           efiledsc.dsc$w_length = 0;
500         }
501
502       if (ofile[0])
503         {
504           fprintf (outfile, "$ define sys$output %s\n", ofile);
505           DB (DB_JOBS, (_("Redirected output to %s\n"), ofile));
506           ofiledsc.dsc$w_length = 0;
507         }
508
509       p = sep = q = cmd;
510       for (c = '\n'; c; c = *q++)
511         {
512           switch (c)
513             {
514             case '\n':
515               /* At a newline, skip any whitespace around a leading $
516                  from the command and issue exactly one $ into the DCL. */
517               while (isspace ((unsigned char)*p))
518                 p++;
519               if (*p == '$')
520                 p++;
521               while (isspace ((unsigned char)*p))
522                 p++;
523               fwrite (p, 1, q - p, outfile);
524               fputc ('$', outfile);
525               fputc (' ', outfile);
526               /* Reset variables. */
527               p = sep = q;
528               break;
529
530               /* Nice places for line breaks are after strings, after
531                  comma or space and before slash. */
532             case '"':
533               q = vms_handle_apos (q);
534               sep = q;
535               break;
536             case ',':
537             case ' ':
538               sep = q;
539               break;
540             case '/':
541             case '\0':
542               sep = q - 1;
543               break;
544             default:
545               break;
546             }
547           if (sep - p > 78)
548             {
549               /* Enough stuff for a line. */
550               fwrite (p, 1, sep - p, outfile);
551               p = sep;
552               if (*sep)
553                 {
554                   /* The command continues.  */
555                   fputc ('-', outfile);
556                 }
557               fputc ('\n', outfile);
558             }
559         }
560
561       fwrite (p, 1, q - p, outfile);
562       fputc ('\n', outfile);
563
564       fclose (outfile);
565
566       sprintf (cmd, "$ @%s", comname);
567
568       DB (DB_JOBS, (_("Executing %s instead\n"), cmd));
569     }
570
571   cmddsc.dsc$w_length = strlen(cmd);
572   cmddsc.dsc$a_pointer = cmd;
573   cmddsc.dsc$b_dtype = DSC$K_DTYPE_T;
574   cmddsc.dsc$b_class = DSC$K_CLASS_S;
575
576   child->efn = 0;
577   while (child->efn < 32 || child->efn > 63)
578     {
579       status = lib$get_ef ((unsigned long *)&child->efn);
580       if (!(status & 1))
581         return 0;
582     }
583
584   sys$clref (child->efn);
585
586   vms_jobsefnmask |= (1 << (child->efn - 32));
587
588 /*
589              LIB$SPAWN  [command-string]
590                         [,input-file]
591                         [,output-file]
592                         [,flags]
593                         [,process-name]
594                         [,process-id] [,completion-status-address] [,byte-integer-event-flag-num]
595                         [,AST-address] [,varying-AST-argument]
596                         [,prompt-string] [,cli] [,table]
597 */
598
599 #ifndef DONTWAITFORCHILD
600 /*
601  *      Code to make ctrl+c and ctrl+y working.
602  *      The problem starts with the synchronous case where after lib$spawn is
603  *      called any input will go to the child. But with input re-directed,
604  *      both control characters won't make it to any of the programs, neither
605  *      the spawning nor to the spawned one. Hence the caller needs to spawn
606  *      with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr
607  *      has to follow to simulate the wanted synchronous behaviour.
608  *      The next problem is ctrl+y which isn't caught by the crtl and
609  *      therefore isn't converted to SIGQUIT (for a signal handler which is
610  *      already established). The only way to catch ctrl+y, is an AST
611  *      assigned to the input channel. But ctrl+y handling of DCL needs to be
612  *      disabled, otherwise it will handle it. Not to mention the previous
613  *      ctrl+y handling of DCL needs to be re-established before make exits.
614  *      One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will
615  *      make it to the signal handler after the child "normally" terminates.
616  *      This isn't enough. It seems reasonable for simple command lines like
617  *      a 'cc foobar.c' spawned in a subprocess but it is unacceptable for
618  *      spawning make. Therefore we need to abort the process in the AST.
619  *
620  *      Prior to the spawn it is checked if an AST is already set up for
621  *      ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general
622  *      this will work except if make is run in a batch environment, but there
623  *      nobody can press ctrl+y. During the setup the DCL handling of ctrl+y
624  *      is disabled and an exit handler is established to re-enable it.
625  *      If the user interrupts with ctrl+y, the assigned AST will fire, force
626  *      an abort to the subprocess and signal SIGQUIT, which will be caught by
627  *      the already established handler and will bring us back to common code.
628  *      After the spawn (now /nowait) a sys$waitfr simulates the /wait and
629  *      enables the ctrl+y be delivered to this code. And the ctrl+c too,
630  *      which the crtl converts to SIGINT and which is caught by the common
631  *      signal handler. Because signals were blocked before entering this code
632  *      sys$waitfr will always complete and the SIGQUIT will be processed after
633  *      it (after termination of the current block, somewhere in common code).
634  *      And SIGINT too will be delayed. That is ctrl+c can only abort when the
635  *      current command completes. Anyway it's better than nothing :-)
636  */
637
638   if (!setupYAstTried)
639     tryToSetupYAst();
640   status = lib$spawn (&cmddsc,                                  /* cmd-string  */
641                       (ifiledsc.dsc$w_length == 0)?0:&ifiledsc, /* input-file  */
642                       (ofiledsc.dsc$w_length == 0)?0:&ofiledsc, /* output-file */
643                       &spflags,                                 /* flags  */
644                       &pnamedsc,                                /* proc name  */
645                       &child->pid, &child->cstatus, &child->efn,
646                       0, 0,
647                       0, 0, 0);
648   if (status & 1)
649     {
650       pidToAbort= child->pid;
651       status= sys$waitfr (child->efn);
652       pidToAbort= 0;
653       vmsHandleChildTerm(child);
654     }
655 #else
656   status = lib$spawn (&cmddsc,
657                       (ifiledsc.dsc$w_length == 0)?0:&ifiledsc,
658                       (ofiledsc.dsc$w_length == 0)?0:&ofiledsc,
659                       &spflags,
660                       &pnamedsc,
661                       &child->pid, &child->cstatus, &child->efn,
662                       vmsHandleChildTerm, child,
663                       0, 0, 0);
664 #endif
665
666   if (!(status & 1))
667     {
668       printf (_("Error spawning, %d\n") ,status);
669       fflush (stdout);
670       switch (status)
671         {
672         case 0x1c:
673           errno = EPROCLIM;
674           break;
675         default:
676           errno = EFAIL;
677         }
678     }
679
680   if (comname && !ISDB (DB_JOBS))
681     unlink (comname);
682
683   return (status & 1);
684 }