Logo Search packages:      
Sourcecode: make version File versions  Download package

vmsjobs.c



/* --------------- Moved here from job.c ---------------
   This file must be #included in job.c, as it accesses static functions.
*/

#include <string.h>
#include <descrip.h>
#include <clidef.h>

extern char *vmsify PARAMS ((char *name, int type));

static int vms_jobsefnmask = 0;

/* Wait for nchildren children to terminate */
static void
vmsWaitForChildren(int *status)
{
  while (1)
    {
      if (!vms_jobsefnmask)
      {
        *status = 0;
        return;
      }

      *status = sys$wflor (32, vms_jobsefnmask);
    }
  return;
}

/* Set up IO redirection.  */

char *
vms_redirect (struct dsc$descriptor_s *desc, char *fname, char *ibuf)
{
  char *fptr;

  ibuf++;
  while (isspace ((unsigned char)*ibuf))
    ibuf++;
  fptr = ibuf;
  while (*ibuf && !isspace ((unsigned char)*ibuf))
    ibuf++;
  *ibuf = 0;
  if (strcmp (fptr, "/dev/null") != 0)
    {
      strcpy (fname, vmsify (fptr, 0));
      if (strchr (fname, '.') == 0)
      strcat (fname, ".");
    }
  desc->dsc$w_length = strlen(fname);
  desc->dsc$a_pointer = fname;
  desc->dsc$b_dtype = DSC$K_DTYPE_T;
  desc->dsc$b_class = DSC$K_CLASS_S;

  if (*fname == 0)
    printf (_("Warning: Empty redirection\n"));
  return ibuf;
}


/* found apostrophe at (p-1)
   inc p until after closing apostrophe.
 */

char *
vms_handle_apos (char *p)
{
  int alast;

#define SEPCHARS ",/()= "

  alast = 0;

  while (*p != 0)
    {
      if (*p == '"')
      {
          if (alast)
            {
              alast = 0;
              p++;
          }
        else
          {
            p++;
            if (strchr (SEPCHARS, *p))
            break;
            alast = 1;
          }
      }
      else
      p++;
    }

  return p;
}

/* This is called as an AST when a child process dies (it won't get
   interrupted by anything except a higher level AST).
*/
int
vmsHandleChildTerm(struct child *child)
{
    int status;
    register struct child *lastc, *c;
    int child_failed;

    vms_jobsefnmask &= ~(1 << (child->efn - 32));

    lib$free_ef(&child->efn);

    (void) sigblock (fatal_signal_mask);

    child_failed = !(child->cstatus & 1 || ((child->cstatus & 7) == 0));

    /* Search for a child matching the deceased one.  */
    lastc = 0;
#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
    for (c = children; c != 0 && c != child; lastc = c, c = c->next)
      ;
#else
    c = child;
#endif

    if (child_failed && !c->noerror && !ignore_errors_flag)
      {
      /* The commands failed.  Write an error message,
         delete non-precious targets, and abort.  */
      child_error (c->file->name, c->cstatus, 0, 0, 0);
      c->file->update_status = 1;
      delete_child_targets (c);
      }
    else
      {
      if (child_failed)
        {
          /* The commands failed, but we don't care.  */
          child_error (c->file->name, c->cstatus, 0, 0, 1);
          child_failed = 0;
        }

#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
      /* If there are more commands to run, try to start them.  */
      start_job (c);

      switch (c->file->command_state)
        {
        case cs_running:
          /* Successfully started.  */
          break;

        case cs_finished:
          if (c->file->update_status != 0) {
            /* We failed to start the commands.  */
            delete_child_targets (c);
          }
          break;

        default:
          error (NILF, _("internal error: `%s' command_state"),
                   c->file->name);
          abort ();
          break;
        }
#endif /* RECURSIVEJOBS */
      }

    /* Set the state flag to say the commands have finished.  */
    c->file->command_state = cs_finished;
    notice_finished_file (c->file);

#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
    /* Remove the child from the chain and free it.  */
    if (lastc == 0)
      children = c->next;
    else
      lastc->next = c->next;
    free_child (c);
#endif /* RECURSIVEJOBS */

    /* There is now another slot open.  */
    if (job_slots_used > 0)
      --job_slots_used;

    /* If the job failed, and the -k flag was not given, die.  */
    if (child_failed && !keep_going_flag)
      die (EXIT_FAILURE);

    (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask));

    return 1;
}

/* VMS:
   Spawn a process executing the command in ARGV and return its pid. */

#define MAXCMDLEN 200

/* local helpers to make ctrl+c and ctrl+y working, see below */
#include <iodef.h>
#include <libclidef.h>
#include <ssdef.h>

static int ctrlMask= LIB$M_CLI_CTRLY;
static int oldCtrlMask;
static int setupYAstTried= 0;
static int pidToAbort= 0;
static int chan= 0;

static void
reEnableAst(void)
{
      lib$enable_ctrl (&oldCtrlMask,0);
}

static void
astHandler (void)
{
      if (pidToAbort) {
            sys$forcex (&pidToAbort, 0, SS$_ABORT);
            pidToAbort= 0;
      }
      kill (getpid(),SIGQUIT);
}

static void
tryToSetupYAst(void)
{
      $DESCRIPTOR(inputDsc,"SYS$COMMAND");
      int   status;
      struct {
            short int   status, count;
            int   dvi;
      } iosb;

      setupYAstTried++;

      if (!chan) {
            status= sys$assign(&inputDsc,&chan,0,0);
            if (!(status&SS$_NORMAL)) {
                  lib$signal(status);
                  return;
            }
      }
      status= sys$qiow (0, chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0,
            astHandler,0,0,0,0,0);
      if (status==SS$_NORMAL)
            status= iosb.status;
        if (status==SS$_ILLIOFUNC || status==SS$_NOPRIV) {
            sys$dassgn(chan);
#ifdef      CTRLY_ENABLED_ANYWAY
            fprintf (stderr,
                         _("-warning, CTRL-Y will leave sub-process(es) around.\n"));
#else
            return;
#endif
      }
      else if (!(status&SS$_NORMAL)) {
            sys$dassgn(chan);
            lib$signal(status);
            return;
      }

      /* called from AST handler ? */
      if (setupYAstTried>1)
            return;
      if (atexit(reEnableAst))
            fprintf (stderr,
                         _("-warning, you may have to re-enable CTRL-Y handling from DCL.\n"));
      status= lib$disable_ctrl (&ctrlMask, &oldCtrlMask);
      if (!(status&SS$_NORMAL)) {
            lib$signal(status);
            return;
      }
}

int
child_execute_job (char *argv, struct child *child)
{
  int i;
  static struct dsc$descriptor_s cmddsc;
  static struct dsc$descriptor_s pnamedsc;
  static struct dsc$descriptor_s ifiledsc;
  static struct dsc$descriptor_s ofiledsc;
  static struct dsc$descriptor_s efiledsc;
  int have_redirection = 0;
  int have_newline = 0;

  int spflags = CLI$M_NOWAIT;
  int status;
  char *cmd = alloca (strlen (argv) + 512), *p, *q;
  char ifile[256], ofile[256], efile[256];
  char *comname = 0;
  char procname[100];
  int in_string;

  /* Parse IO redirection.  */

  ifile[0] = 0;
  ofile[0] = 0;
  efile[0] = 0;

  DB (DB_JOBS, ("child_execute_job (%s)\n", argv));

  while (isspace ((unsigned char)*argv))
    argv++;

  if (*argv == 0)
    return 0;

  sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff);
  pnamedsc.dsc$w_length = strlen(procname);
  pnamedsc.dsc$a_pointer = procname;
  pnamedsc.dsc$b_dtype = DSC$K_DTYPE_T;
  pnamedsc.dsc$b_class = DSC$K_CLASS_S;

  in_string = 0;
  /* Handle comments and redirection. */
  for (p = argv, q = cmd; *p; p++, q++)
    {
      if (*p == '"')
        in_string = !in_string;
      if (in_string)
        {
          *q = *p;
          continue;
        }
      switch (*p)
      {
        case '#':
          *p-- = 0;
          *q-- = 0;
          break;
        case '\\':
          p++;
          if (*p == '\n')
            p++;
          if (isspace ((unsigned char)*p))
            {
            do { p++; } while (isspace ((unsigned char)*p));
            p--;
            }
          *q = *p;
          break;
        case '<':
          p = vms_redirect (&ifiledsc, ifile, p);
          *q = ' ';
          have_redirection = 1;
          break;
        case '>':
          have_redirection = 1;
          if (*(p-1) == '2')
            {
            q--;
            if (strncmp (p, ">&1", 3) == 0)
              {
                p += 3;
                strcpy (efile, "sys$output");
                efiledsc.dsc$w_length = strlen(efile);
                efiledsc.dsc$a_pointer = efile;
                efiledsc.dsc$b_dtype = DSC$K_DTYPE_T;
                efiledsc.dsc$b_class = DSC$K_CLASS_S;
              }
            else
              {
                p = vms_redirect (&efiledsc, efile, p);
              }
            }
          else
            {
            p = vms_redirect (&ofiledsc, ofile, p);
            }
          *q = ' ';
          break;
        case '\n':
          have_newline = 1;
        default:
          *q = *p;
          break;
      }
    }
  *q = *p;
  while (isspace ((unsigned char)*--q))
    *q = '\0';

  if (strncmp (cmd, "builtin_", 8) == 0)
    {
      child->pid = 270163;
      child->efn = 0;
      child->cstatus = 1;

      DB (DB_JOBS, (_("BUILTIN [%s][%s]\n"), cmd, cmd+8));

      p = cmd + 8;

      if ((*(p) == 'c')
        && (*(p+1) == 'd')
        && ((*(p+2) == ' ') || (*(p+2) == '\t')))
      {
        p += 3;
        while ((*p == ' ') || (*p == '\t'))
          p++;
        DB (DB_JOBS, (_("BUILTIN CD %s\n"), p));
        if (chdir (p))
          return 0;
        else
          return 1;
      }
      else if ((*(p) == 'r')
        && (*(p+1) == 'm')
        && ((*(p+2) == ' ') || (*(p+2) == '\t')))
      {
        int in_arg;

        /* rm  */
        p += 3;
        while ((*p == ' ') || (*p == '\t'))
          p++;
        in_arg = 1;

        DB (DB_JOBS, (_("BUILTIN RM %s\n"), p));
        while (*p)
          {
            switch (*p)
            {
              case ' ':
              case '\t':
                if (in_arg)
                  {
                  *p++ = ';';
                  in_arg = 0;
                  }
                break;
              default:
                break;
            }
            p++;
          }
      }
      else
      {
        printf(_("Unknown builtin command '%s'\n"), cmd);
        fflush(stdout);
        return 0;
      }
    }

  /* Create a *.com file if either the command is too long for
     lib$spawn, or the command contains a newline, or if redirection
     is desired. Forcing commands with newlines into DCLs allows to
     store search lists on user mode logicals.  */

  if (strlen (cmd) > MAXCMDLEN
      || (have_redirection != 0)
      || (have_newline != 0))
    {
      FILE *outfile;
      char c;
      char *sep;
      int alevel = 0;   /* apostrophe level */

      if (strlen (cmd) == 0)
      {
        printf (_("Error, empty command\n"));
        fflush (stdout);
        return 0;
      }

      outfile = open_tmpfile (&comname, "sys$scratch:CMDXXXXXX.COM");
      if (outfile == 0)
      pfatal_with_name (_("fopen (temporary file)"));

      if (ifile[0])
      {
        fprintf (outfile, "$ assign/user %s sys$input\n", ifile);
          DB (DB_JOBS, (_("Redirected input from %s\n"), ifile));
        ifiledsc.dsc$w_length = 0;
      }

      if (efile[0])
      {
        fprintf (outfile, "$ define sys$error %s\n", efile);
          DB (DB_JOBS, (_("Redirected error to %s\n"), efile));
        efiledsc.dsc$w_length = 0;
      }

      if (ofile[0])
      {
        fprintf (outfile, "$ define sys$output %s\n", ofile);
        DB (DB_JOBS, (_("Redirected output to %s\n"), ofile));
        ofiledsc.dsc$w_length = 0;
      }

      p = sep = q = cmd;
      for (c = '\n'; c; c = *q++)
      {
        switch (c)
          {
            case '\n':
              /* At a newline, skip any whitespace around a leading $
                 from the command and issue exactly one $ into the DCL. */
              while (isspace ((unsigned char)*p))
                p++;
              if (*p == '$')
                p++;
              while (isspace ((unsigned char)*p))
                p++;
              fwrite (p, 1, q - p, outfile);
              fputc ('$', outfile);
              fputc (' ', outfile);
              /* Reset variables. */
              p = sep = q;
              break;

            /* Nice places for line breaks are after strings, after
             comma or space and before slash. */
            case '"':
              q = vms_handle_apos (q);
              sep = q;
              break;
            case ',':
            case ' ':
              sep = q;
              break;
            case '/':
            case '\0':
              sep = q - 1;
              break;
            default:
              break;
          }
        if (sep - p > 78)
          {
            /* Enough stuff for a line. */
            fwrite (p, 1, sep - p, outfile);
            p = sep;
            if (*sep)
            {
              /* The command continues.  */
              fputc ('-', outfile);
            }
            fputc ('\n', outfile);
          }
      }

      fwrite (p, 1, q - p, outfile);
      fputc ('\n', outfile);

      fclose (outfile);

      sprintf (cmd, "$ @%s", comname);

      DB (DB_JOBS, (_("Executing %s instead\n"), cmd));
    }

  cmddsc.dsc$w_length = strlen(cmd);
  cmddsc.dsc$a_pointer = cmd;
  cmddsc.dsc$b_dtype = DSC$K_DTYPE_T;
  cmddsc.dsc$b_class = DSC$K_CLASS_S;

  child->efn = 0;
  while (child->efn < 32 || child->efn > 63)
    {
      status = lib$get_ef ((unsigned long *)&child->efn);
      if (!(status & 1))
      return 0;
    }

  sys$clref (child->efn);

  vms_jobsefnmask |= (1 << (child->efn - 32));

/*
             LIB$SPAWN  [command-string]
                  [,input-file]
                  [,output-file]
                  [,flags]
                  [,process-name]
                  [,process-id] [,completion-status-address] [,byte-integer-event-flag-num]
                  [,AST-address] [,varying-AST-argument]
                  [,prompt-string] [,cli] [,table]
*/

#ifndef DONTWAITFORCHILD
/*
 *    Code to make ctrl+c and ctrl+y working.
 *    The problem starts with the synchronous case where after lib$spawn is
 *    called any input will go to the child. But with input re-directed,
 *    both control characters won't make it to any of the programs, neither
 *    the spawning nor to the spawned one. Hence the caller needs to spawn
 *    with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr
 *    has to follow to simulate the wanted synchronous behaviour.
 *    The next problem is ctrl+y which isn't caught by the crtl and
 *    therefore isn't converted to SIGQUIT (for a signal handler which is
 *    already established). The only way to catch ctrl+y, is an AST
 *    assigned to the input channel. But ctrl+y handling of DCL needs to be
 *    disabled, otherwise it will handle it. Not to mention the previous
 *    ctrl+y handling of DCL needs to be re-established before make exits.
 *    One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will
 *    make it to the signal handler after the child "normally" terminates.
 *    This isn't enough. It seems reasonable for simple command lines like
 *    a 'cc foobar.c' spawned in a subprocess but it is unacceptable for
 *    spawning make. Therefore we need to abort the process in the AST.
 *
 *    Prior to the spawn it is checked if an AST is already set up for
 *    ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general
 *    this will work except if make is run in a batch environment, but there
 *    nobody can press ctrl+y. During the setup the DCL handling of ctrl+y
 *    is disabled and an exit handler is established to re-enable it.
 *    If the user interrupts with ctrl+y, the assigned AST will fire, force
 *    an abort to the subprocess and signal SIGQUIT, which will be caught by
 *    the already established handler and will bring us back to common code.
 *    After the spawn (now /nowait) a sys$waitfr simulates the /wait and
 *    enables the ctrl+y be delivered to this code. And the ctrl+c too,
 *    which the crtl converts to SIGINT and which is caught by the common
 *    signal handler. Because signals were blocked before entering this code
 *    sys$waitfr will always complete and the SIGQUIT will be processed after
 *    it (after termination of the current block, somewhere in common code).
 *    And SIGINT too will be delayed. That is ctrl+c can only abort when the
 *    current command completes. Anyway it's better than nothing :-)
 */

  if (!setupYAstTried)
    tryToSetupYAst();
  status = lib$spawn (&cmddsc,                              /* cmd-string  */
                  (ifiledsc.dsc$w_length == 0)?0:&ifiledsc, /* input-file  */
                  (ofiledsc.dsc$w_length == 0)?0:&ofiledsc, /* output-file */
                  &spflags,                           /* flags  */
                  &pnamedsc,                    /* proc name  */
                  &child->pid, &child->cstatus, &child->efn,
                  0, 0,
                  0, 0, 0);
  if (status & 1)
    {
      pidToAbort= child->pid;
      status= sys$waitfr (child->efn);
      pidToAbort= 0;
      vmsHandleChildTerm(child);
    }
#else
  status = lib$spawn (&cmddsc,
                  (ifiledsc.dsc$w_length == 0)?0:&ifiledsc,
                  (ofiledsc.dsc$w_length == 0)?0:&ofiledsc,
                  &spflags,
                  &pnamedsc,
                  &child->pid, &child->cstatus, &child->efn,
                  vmsHandleChildTerm, child,
                  0, 0, 0);
#endif

  if (!(status & 1))
    {
      printf (_("Error spawning, %d\n") ,status);
      fflush (stdout);
      switch (status)
        {
        case 0x1c:
          errno = EPROCLIM;
          break;
        default:
          errno = EFAIL;
        }
    }

  if (comname && !ISDB (DB_JOBS))
    unlink (comname);

  return (status & 1);
}

Generated by  Doxygen 1.6.0   Back to index