Beispiel #1
0
  def RunCommandSub(self, node):
    p = self._MakeProcess(node)

    r, w = os.pipe()
    p.AddStateChange(process.StdoutToPipe(r, w))
    pid = p.Start()
    #log('Command sub started %d', pid)
    self.waiter.Register(pid, p.WhenDone)

    chunks = []
    os.close(w)  # not going to write
    while True:
      byte_str = os.read(r, 4096)
      if not byte_str:
        break
      chunks.append(byte_str)
    os.close(r)

    status = p.WaitUntilDone(self.waiter)

    # TODO: Add context
    if self.exec_opts.ErrExit() and status != 0:
      e_die('Command sub exited with status %d (%r)', status,
            node.__class__.__name__)

    return ''.join(chunks).rstrip('\n')
Beispiel #2
0
    def RunCommandSub(self, node):
        # type: (command_t) -> str

        # Hack for weird $(<file) construct
        if node.tag_() == command_e.Simple:
            simple = cast(command__Simple, node)
            # Detect '< file'
            if (len(simple.words) == 0 and len(simple.redirects) == 1
                    and simple.redirects[0].op.id == Id.Redir_Less):
                # change it to __cat < file
                # note: cmd_eval.py _Dispatch works around lack of spid
                tok = Token(Id.Lit_Chars, runtime.NO_SPID, '__cat')
                cat_word = compound_word([tok])
                # MUTATE the command.Simple node.  This will only be done the first
                # time in the parent process.
                simple.words.append(cat_word)

        p = self._MakeProcess(node,
                              inherit_errexit=self.exec_opts.inherit_errexit())

        r, w = posix.pipe()
        p.AddStateChange(process.StdoutToPipe(r, w))
        _ = p.Start()
        #log('Command sub started %d', pid)

        chunks = []  # type: List[str]
        posix.close(w)  # not going to write
        while True:
            byte_str = posix.read(r, 4096)
            if len(byte_str) == 0:
                break
            chunks.append(byte_str)
        posix.close(r)

        status = p.Wait(self.waiter)

        # OSH has the concept of aborting in the middle of a WORD.  We're not
        # waiting until the command is over!
        if self.exec_opts.more_errexit():
            if self.exec_opts.errexit() and status != 0:
                raise error.ErrExit('Command sub exited with status %d (%r)',
                                    status, NewStr(command_str(node.tag_())))
        else:
            # Set a flag so we check errexit at the same time as bash.  Example:
            #
            # a=$(false)
            # echo foo  # no matter what comes here, the flag is reset
            #
            # Set ONLY until this command node has finished executing.

            # HACK: move this
            self.cmd_ev.check_command_sub_status = True
            self.mem.SetLastStatus(status)

        # Runtime errors test case: # $("echo foo > $@")
        # Why rstrip()?
        # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char
        return ''.join(chunks).rstrip('\n')
Beispiel #3
0
  def RunCommandSub(self, node):
    p = self._MakeProcess(node,
                          disable_errexit=not self.exec_opts.strict_errexit)

    r, w = posix.pipe()
    p.AddStateChange(process.StdoutToPipe(r, w))
    pid = p.Start()
    #log('Command sub started %d', pid)
    self.waiter.Register(pid, p.WhenDone)

    chunks = []
    posix.close(w)  # not going to write
    while True:
      byte_str = posix.read(r, 4096)
      if not byte_str:
        break
      chunks.append(byte_str)
    posix.close(r)

    status = p.WaitUntilDone(self.waiter)

    # OSH has the concept of aborting in the middle of a WORD.  We're not
    # waiting until the command is over!
    if self.exec_opts.strict_errexit:
      if self.exec_opts.ErrExit() and status != 0:
        raise util.ErrExitFailure(
            'Command sub exited with status %d (%r)', status,
            node.__class__.__name__)
    else:
      # Set a flag so we check errexit at the same time as bash.  Example:
      #
      # a=$(false)
      # echo foo  # no matter what comes here, the flag is reset
      #
      # Set ONLY until this command node has finished executing.
      self.check_command_sub_status = True
      self.mem.last_status = status

    # Runtime errors test case: # $("echo foo > $@")
    # Why rstrip()?
    # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char
    return ''.join(chunks).rstrip('\n')
Beispiel #4
0
  def RunProcessSub(self, node, op_id):
    """Process sub creates a forks a process connected to a pipe.

    The pipe is typically passed to another process via a /dev/fd/$FD path.

    TODO:

    sane-proc-sub:
    - wait for all the words

    Otherwise, set $!  (mem.last_job_id)

    strict-proc-sub:
    - Don't allow it anywhere except SimpleCommand, any redirect, or
    Assignment?  And maybe not even assignment?

    Should you put return codes in @PROCESS_SUB_STATUS?  You need two of them.
    """
    p = self._MakeProcess(node)

    r, w = posix.pipe()

    if op_id == Id.Left_ProcSubIn:
      # Example: cat < <(head foo.txt)
      #
      # The head process should write its stdout to a pipe.
      redir = process.StdoutToPipe(r, w)

    elif op_id == Id.Left_ProcSubOut:
      # Example: head foo.txt > >(tac)
      #
      # The tac process should read its stdin from a pipe.
      #
      # NOTE: This appears to hang in bash?  At least when done interactively.
      # It doesn't work at all in osh interactively?
      redir = process.StdinFromPipe(r, w)

    else:
      raise AssertionError

    p.AddStateChange(redir)

    # Fork, letting the child inherit the pipe file descriptors.
    pid = p.Start()

    # After forking, close the end of the pipe we're not using.
    if op_id == Id.Left_ProcSubIn:
      posix.close(w)
    elif op_id == Id.Left_ProcSubOut:
      posix.close(r)
    else:
      raise AssertionError

    #log('I am %d', posix.getpid())
    #log('Process sub started %d', pid)
    self.waiter.Register(pid, p.WhenDone)

    # NOTE: Like bash, we never actually wait on it!
    # TODO: At least set $! ?

    # Is /dev Linux-specific?
    if op_id == Id.Left_ProcSubIn:
      return '/dev/fd/%d' % r

    elif op_id == Id.Left_ProcSubOut:
      return '/dev/fd/%d' % w

    else:
      raise AssertionError
Beispiel #5
0
  def RunProcessSub(self, cs_part):
    # type: (command_sub) -> str
    """Process sub creates a forks a process connected to a pipe.

    The pipe is typically passed to another process via a /dev/fd/$FD path.

    Life cycle of a process substitution:

    1. Start with this code

      diff <(seq 3) <(seq 4)

    2. To evaluate the command line, we evaluate every word.  The
    NormalWordEvaluator this method, RunProcessSub(), which does 3 things:

      a. Create a pipe(), getting r and w
      b. Starts the seq process, which inherits r and w
         It has a StdoutToPipe() redirect, which means that it dup2(w, 1)
         and close(r)
      c. Close the w FD, because neither the shell or 'diff' will write to it.
         However we must retain 'r', because 'diff' hasn't opened /dev/fd yet!
      d. We evaluate <(seq 3) to /dev/fd/$r, so "diff" can read from it

    3. Now we're done evaluating every word, so we know the command line of
       diff, which looks like

      diff /dev/fd/64 /dev/fd/65

    Those are the FDs for the read ends of the pipes we created.

    4. diff inherits a copy of the read end of bot pipes.  But it actually
    calls open() both files passed as argv.  (I think this is fine.)

    5. wait() for the diff process.

    6. The shell closes both the read ends of both pipes.  Neither us or
    'diffd' will read again.

    7. The shell waits for both 'seq' processes.

    Related:
      shopt -s process_sub_fail
      _process_sub_status
    """
    p = self._MakeProcess(cs_part.child)

    r, w = posix.pipe()
    #log('pipe = %d, %d', r, w)

    op_id = cs_part.left_token.id
    if op_id == Id.Left_ProcSubIn:
      # Example: cat < <(head foo.txt)
      #
      # The head process should write its stdout to a pipe.
      redir = process.StdoutToPipe(r, w)  # type: process.ChildStateChange

    elif op_id == Id.Left_ProcSubOut:
      # Example: head foo.txt > >(tac)
      #
      # The tac process should read its stdin from a pipe.

      # Note: this example sometimes requires you to hit "enter" in bash and
      # zsh.  WHy?
      redir = process.StdinFromPipe(r, w)

    else:
      raise AssertionError()

    p.AddStateChange(redir)

    # Fork, letting the child inherit the pipe file descriptors.
    pid = p.Start()

    ps_frame = self.process_sub_stack[-1]

    # Note: bash never waits() on the process, but zsh does.  The calling
    # program needs to read() before we can wait, e.g.
    #   diff <(sort left.txt) <(sort right.txt)
    ps_frame.to_wait.append(p)
    ps_frame.span_ids.append(cs_part.left_token.span_id)

    # After forking, close the end of the pipe we're not using.
    if op_id == Id.Left_ProcSubIn:
      posix.close(w)  # cat < <(head foo.txt)
      ps_frame.to_close.append(r)  # close later
    elif op_id == Id.Left_ProcSubOut:
      posix.close(r)
      #log('Left_ProcSubOut closed %d', r)
      ps_frame.to_close.append(w)  # close later
    else:
      raise AssertionError()

    # Is /dev Linux-specific?
    if op_id == Id.Left_ProcSubIn:
      return '/dev/fd/%d' % r

    elif op_id == Id.Left_ProcSubOut:
      return '/dev/fd/%d' % w

    else:
      raise AssertionError()
Beispiel #6
0
    def RunCommandSub(self, cs_part):
        # type: (command_sub) -> str

        if not self.exec_opts.allow_command_sub():
            # TODO:
            # - Add spid of $(
            # - Better hints.  Use 'run' for 'if myfunc', and 2 lines like local x;
            #   x=$(false) fo assignment builtins.
            # - Maybe we should have an error message ID that links somewhere?

            e_die(
                "Command subs not allowed here because status wouldn't be checked (strict_errexit)."
            )

        node = cs_part.child

        # Hack for weird $(<file) construct
        if node.tag_() == command_e.Simple:
            simple = cast(command__Simple, node)
            # Detect '< file'
            if (len(simple.words) == 0 and len(simple.redirects) == 1
                    and simple.redirects[0].op.id == Id.Redir_Less):
                # change it to __cat < file
                # note: cmd_eval.py _Dispatch works around lack of spid
                tok = Token(Id.Lit_Chars, runtime.NO_SPID, '__cat')
                cat_word = compound_word([tok])
                # MUTATE the command.Simple node.  This will only be done the first
                # time in the parent process.
                simple.words.append(cat_word)

        p = self._MakeProcess(node,
                              inherit_errexit=self.exec_opts.inherit_errexit())

        r, w = posix.pipe()
        p.AddStateChange(process.StdoutToPipe(r, w))
        _ = p.Start()
        #log('Command sub started %d', pid)

        chunks = []  # type: List[str]
        posix.close(w)  # not going to write
        while True:
            byte_str = posix.read(r, 4096)
            if len(byte_str) == 0:
                break
            chunks.append(byte_str)
        posix.close(r)

        status = p.Wait(self.waiter)

        # OSH has the concept of aborting in the middle of a WORD.  We're not
        # waiting until the command is over!
        if self.exec_opts.command_sub_errexit():
            if status != 0:
                raise error.ErrExit('Command sub exited with status %d (%s)' %
                                    (status, ui.CommandType(node)),
                                    span_id=cs_part.left_token.span_id,
                                    status=status)

        else:
            # Set a flag so we check errexit at the same time as bash.  Example:
            #
            # a=$(false)
            # echo foo  # no matter what comes here, the flag is reset
            #
            # Set ONLY until this command node has finished executing.

            # HACK: move this
            self.cmd_ev.check_command_sub_status = True
            self.mem.SetLastStatus(status)

        # Runtime errors test case: # $("echo foo > $@")
        # Why rstrip()?
        # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char
        return ''.join(chunks).rstrip('\n')