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