Пример #1
0
def _GetOpts(spec, argv, optind, errfmt):
    # type: (Dict[str, bool], List[str], int, ErrorFormatter) -> Tuple[int, str, str, int]
    optarg = ''  # not set by default

    try:
        current = argv[optind - 1]  # 1-based indexing
    except IndexError:
        return 1, '?', optarg, optind

    if not current.startswith('-'):  # The next arg doesn't look like a flag.
        return 1, '?', optarg, optind

    # It looks like an argument.  Stop iteration by returning 1.
    if current not in spec:  # Invalid flag
        optind += 1
        return 0, '?', optarg, optind

    optind += 1
    opt_char = current[-1]

    needs_arg = spec[current]
    if needs_arg:
        try:
            optarg = argv[optind - 1]  # 1-based indexing
        except IndexError:
            # TODO: Add location info
            errfmt.Print_('getopts: option %r requires an argument.' % current)
            tmp = [qsn.maybe_shell_encode(a) for a in argv]
            stderr_line('(getopts argv: %s)', ' '.join(tmp))
            # Hm doesn't cause status 1?
            return 0, '?', optarg, optind

        optind += 1

    return 0, opt_char, optarg, optind
Пример #2
0
  def Run(self, cmd_val):
    # type: (cmd_value__Argv) -> int

    argv = cmd_val.argv[1:]
    if len(argv) == 0:
      # umask() has a dumb API: you can't get it without modifying it first!
      # NOTE: dash disables interrupts around the two umask() calls, but that
      # shouldn't be a concern for us.  Signal handlers won't call umask().
      mask = posix.umask(0)
      posix.umask(mask)  #
      print('0%03o' % mask)  # octal format
      return 0

    if len(argv) == 1:
      a = argv[0]
      try:
        new_mask = int(a, 8)
      except ValueError:
        # NOTE: This happens if we have '8' or '9' in the input too.
        stderr_line("osh warning: umask with symbolic input isn't implemented")
        return 1
      else:
        posix.umask(new_mask)
        return 0

    raise error.Usage('umask: unexpected arguments')
Пример #3
0
def main(argv):
    # type: (List[str]) -> int
    try:
        return AppBundleMain(argv)
    except error.Usage as e:
        #builtin.Help(['oil-usage'], util.GetResourceLoader())
        log('oil: %s', e.msg)
        return 2
    except RuntimeError as e:
        if 0:
            import traceback
            traceback.print_exc()
        # NOTE: The Python interpreter can cause this, e.g. on stack overflow.
        log('FATAL: %r', e)
        return 1
    except KeyboardInterrupt:
        print()
        return 130  # 128 + 2
    except (IOError, OSError) as e:
        if 0:
            import traceback
            traceback.print_exc()

        # test this with prlimit --nproc=1 --pid=$$
        stderr_line('osh I/O error: %s', posix.strerror(e.errno))
        return 2  # dash gives status 2
    finally:
        _tlog('Exiting main()')
        if _trace_path:
            _tracer.Stop(_trace_path)
Пример #4
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        attrs, arg_r = flag_spec.ParseCmdVal('hash', cmd_val)
        arg = arg_types.hash(attrs.attrs)

        rest = arg_r.Rest()
        if arg.r:
            if len(rest):
                e_usage('got extra arguments after -r')
            self.search_path.ClearCache()
            return 0

        status = 0
        if len(rest):
            for cmd in rest:  # enter in cache
                full_path = self.search_path.CachedLookup(cmd)
                if full_path is None:
                    stderr_line('hash: %r not found', cmd)
                    status = 1
        else:  # print cache
            commands = self.search_path.CachedCommands()
            commands.sort()
            for cmd in commands:
                print(cmd)

        return status
Пример #5
0
 def __call__(self, unused_word, state):
     # type: (str, int) -> Optional[str]
     """Return a single match."""
     try:
         return self._GetNextCompletion(state)
     except util.UserExit as e:
         # TODO: Could use errfmt to show this
         stderr_line("osh: Ignoring 'exit' in completion plugin")
     except error.FatalRuntime as e:
         # From -W.  TODO: -F is swallowed now.
         # We should have a nicer UI for displaying errors.  Maybe they shouldn't
         # print it to stderr.  That messes up the completion display.  We could
         # print what WOULD have been COMPREPLY here.
         stderr_line('osh: Runtime error while completing: %s', e)
         self.debug_f.log('Runtime error while completing: %s', e)
     except (IOError, OSError) as e:
         # test this with prlimit --nproc=1 --pid=$$
         stderr_line('osh: I/O error in completion: %s',
                     posix.strerror(e.errno))
     except KeyboardInterrupt:
         # It appears GNU readline handles Ctrl-C to cancel a long completion.
         # So this may never happen?
         stderr_line('Ctrl-C in completion')
     except Exception as e:  # ESSENTIAL because readline swallows exceptions.
         if 1:
             import traceback
             traceback.print_exc()
         stderr_line('osh: Unhandled exception while completing: %s', e)
         self.debug_f.log('Unhandled exception while completing: %s', e)
     except SystemExit as e:
         # Because readline ignores SystemExit!
         posix._exit(e.code)
Пример #6
0
def PrintAst(node, flag):
    # type: (command_t, arg_types.main) -> None

    if flag.ast_format == 'none':
        stderr_line('AST not printed.')
        if 0:
            from _devbuild.gen.id_kind_asdl import Id_str
            from frontend.lexer import ID_HIST
            for id_, count in ID_HIST.most_common(10):
                print('%8d %s' % (count, Id_str(id_)))
            print()
            total = sum(ID_HIST.values())
            print('%8d total tokens returned' % total)

    else:  # text output
        f = mylib.Stdout()

        afmt = flag.ast_format  # note: mycpp rewrite to avoid 'in'
        if afmt in ('text', 'abbrev-text'):
            ast_f = fmt.DetectConsoleOutput(f)
        elif afmt in ('html', 'abbrev-html'):
            ast_f = fmt.HtmlOutput(f)
        else:
            raise AssertionError()

        if 'abbrev-' in afmt:
            tree = node.AbbreviatedTree()
        else:
            tree = node.PrettyTree()

        ast_f.FileHeader()
        fmt.PrintTree(tree, ast_f)
        ast_f.FileFooter()
        ast_f.write('\n')
Пример #7
0
    def Run(self):
        # type: () -> None

        # NOTE: may NOT return due to exec().
        if not self.inherit_errexit:
            self.cmd_ev.mutable_opts.DisableErrExit()
        try:
            # optimize to eliminate redundant subshells like ( echo hi ) | wc -l etc.
            self.cmd_ev.ExecuteAndCatch(self.node, cmd_flags=cmd_eval.Optimize)
            status = self.cmd_ev.LastStatus()
            # NOTE: We ignore the is_fatal return value.  The user should set -o
            # errexit so failures in subprocesses cause failures in the parent.
        except util.UserExit as e:
            status = e.status

        # Handle errors in a subshell.  These two cases are repeated from main()
        # and the core/completion.py hook.
        except KeyboardInterrupt:
            print('')
            status = 130  # 128 + 2
        except (IOError, OSError) as e:
            stderr_line('osh I/O error: %s', pyutil.strerror(e))
            status = 2

        # Raises SystemExit, so we still have time to write a crash dump.
        exit(status)
Пример #8
0
def EvalCStringToken(tok):
    # type: (Token) -> Optional[str]
    """
  This function is shared between echo -e and $''.

  $'' could use it at compile time, much like brace expansion in braces.py.
  """
    id_ = tok.id
    value = tok.val

    if id_ == Id.Char_Literals:
        return value

    elif id_ == Id.Char_BadBackslash:
        if 1:
            # Either \A or trailing \ (A is not a valid backslash escape)
            # TODO: add location info with tok.span_id (errfmt), and make it an rror
            # when strict_backslash is on.  I USED this to fix a refactored regex!
            # Extract from [[ ]] and fix backslashes.
            stderr_line(
                'warning: Invalid backslash escape in C-style string: %r' %
                value)
            #from core.pyerror import e_die
            #e_die('Invalid backslash escape %r', value, span_id=tok.span_id)
        return value

    elif id_ == Id.Char_OneChar:
        c = value[1]
        return consts.LookupCharC(c)

    elif id_ == Id.Char_Stop:  # \c returns a special sentinel
        return None

    elif id_ in (Id.Char_Octal3, Id.Char_Octal4):
        if id_ == Id.Char_Octal3:  # $'\377'
            s = value[1:]
        else:  # echo -e '\0377'
            s = value[2:]

        i = int(s, 8)
        if i >= 256:
            i = i % 256
            # NOTE: This is for strict mode
            #raise AssertionError('Out of range')
        return chr(i)

    elif id_ == Id.Char_Hex:
        s = value[2:]
        i = int(s, 16)
        return chr(i)

    elif id_ in (Id.Char_Unicode4, Id.Char_Unicode8):
        s = value[2:]
        i = int(s, 16)
        #util.log('i = %d', i)
        return string_ops.Utf8Encode(i)

    else:
        raise AssertionError()
Пример #9
0
def main(argv):
  arg, i = SPEC.ParseArgv(argv)
  if not arg.f:
    stderr_line("readlink: -f must be passed")
    return 1
  for path in argv[i:]:
    res = libc.realpath(path)
    if res is None:
      return 1
    print(res)
  return 0
Пример #10
0
def EvalCStringToken(id_, value):
    # type: (Id_t, str) -> Optional[str]
    """
  This function is shared between echo -e and $''.

  $'' could use it at compile time, much like brace expansion in braces.py.
  """
    if id_ == Id.Char_Literals:
        return value

    elif id_ == Id.Char_BadBackslash:
        if 1:
            # TODO:
            # - make this an error in strict mode
            # - improve the error message.  We don't have a span_id!
            # Either \A or trailing \ (A is not a valid backslash escape)
            stderr_line('warning: Invalid backslash escape in C-style string')
        return value

    elif id_ == Id.Char_OneChar:
        c = value[1]
        return _ONE_CHAR[c]

    elif id_ == Id.Char_Stop:  # \c returns a special sentinel
        return None

    elif id_ in (Id.Char_Octal3, Id.Char_Octal4):
        if id_ == Id.Char_Octal3:  # $'\377'
            s = value[1:]
        else:  # echo -e '\0377'
            s = value[2:]

        i = int(s, 8)
        if i >= 256:
            i = i % 256
            # NOTE: This is for strict mode
            #raise AssertionError('Out of range')
        return chr(i)

    elif id_ == Id.Char_Hex:
        s = value[2:]
        i = int(s, 16)
        return chr(i)

    elif id_ in (Id.Char_Unicode4, Id.Char_Unicode8):
        s = value[2:]
        i = int(s, 16)
        #util.log('i = %d', i)
        return string_ops.Utf8Encode(i)

    else:
        raise AssertionError()
Пример #11
0
    def Expand(self, arg, out):
        # type: (str, List[str]) -> int
        """Given a string that could be a glob, append a list of strings to 'out'.

    Returns:
      Number of items appended.
    """
        if not LooksLikeGlob(arg):
            # e.g. don't glob 'echo' because it doesn't look like a glob
            out.append(GlobUnescape(arg))
            return 1
        if self.exec_opts.noglob():
            # we didn't glob escape it in osh/word_eval.py
            out.append(arg)
            return 1

        try:
            results = libc.glob(arg)
        except RuntimeError as e:
            # These errors should be rare: I/O error, out of memory, or unknown
            # There are no syntax errors.  (But see comment about globerr() in
            # native/libc.c.)
            # note: MyPy doesn't know RuntimeError has e.message (and e.args)
            msg = e.message  # type: str
            stderr_line("Error expanding glob %r: %s", arg, msg)
            raise
        #log('glob %r -> %r', arg, g)

        n = len(results)
        if n:  # Something matched
            for name in results:
                # Omit files starting with - to solve the --.
                # dash_glob turned OFF with shopt -s oil:basic.
                if name.startswith('-') and not self.exec_opts.dashglob():
                    n -= 1
                    continue
                out.append(name)
            return n

        # Nothing matched
        #if self.exec_opts.failglob():
        # note: to match bash, the whole command has to return 1.  But this also
        # happens in for loop and array literal contexts.  It might not be worth
        # it?
        #  raise NotImplementedError()

        if self.exec_opts.nullglob():
            return 0

        # Return the original string
        out.append(GlobUnescape(arg))
        return 1
Пример #12
0
def main(argv):
  arg_r = args.Reader(argv)
  arg_r.Next()  # skip argv[0]
  arg = args.Parse(SPEC, arg_r)

  if not arg.f:
    stderr_line("readlink: -f must be passed")
    return 1
  for path in arg_r.Rest():
    res = libc.realpath(path)
    if res is None:
      return 1
    print(res)
  return 0
Пример #13
0
def _GetOpts(spec, argv, my_state, errfmt):
    # type: (Dict[str, bool], List[str], GetOptsState, ErrorFormatter) -> Tuple[int, str]
    current = my_state.GetArg(argv)
    #log('current %s', current)

    if current is None:  # out of range, etc.
        my_state.Fail()
        return 1, '?'

    if not current.startswith('-') or current == '-':
        my_state.Fail()
        return 1, '?'

    flag_char = current[my_state.flag_pos]

    if my_state.flag_pos < len(current) - 1:
        my_state.flag_pos += 1  # don't move past this arg yet
        more_chars = True
    else:
        my_state.IncIndex()
        my_state.flag_pos = 1
        more_chars = False

    if flag_char not in spec:  # Invalid flag
        return 0, '?'

    if spec[flag_char]:  # does it need an argument?
        if more_chars:
            optarg = current[my_state.flag_pos:]
        else:
            optarg = my_state.GetArg(argv)
            if optarg is None:
                my_state.Fail()
                # TODO: Add location info
                errfmt.Print_('getopts: option %r requires an argument.' %
                              current)
                tmp = [qsn.maybe_shell_encode(a) for a in argv]
                stderr_line('(getopts argv: %s)', ' '.join(tmp))

                # Hm doesn't cause status 1?
                return 0, '?'
        my_state.IncIndex()
        my_state.SetArg(optarg)
    else:
        my_state.SetArg('')

    return 0, flag_char
Пример #14
0
def main(argv):
  # type: (List[str]) -> int
  loader = pyutil.GetResourceLoader()
  login_shell = False

  environ = {}  # type: Dict[str, str]
  environ['PWD'] = posix.getcwd()
  environ['PATH'] = '/bin'  # stub
  # No getenv()!
  #environ['PATH'] = posix.getenv('PATH')
  #environ = posix.environ 

  arg_r = args.Reader(argv, spids=[runtime.NO_SPID] * len(argv))

  try:
    status = pure.Main('osh', arg_r, environ, login_shell, loader, None)
    return status
  except error.Usage as e:
    #builtin.Help(['oil-usage'], util.GetResourceLoader())
    log('oil: %s', e.msg)
    return 2
  except RuntimeError as e:
    if 0:
      import traceback
      traceback.print_exc()
    # NOTE: The Python interpreter can cause this, e.g. on stack overflow.
    # f() { f; }; f will cause this
    msg = e.message  # type: str
    stderr_line('osh fatal error: %s', msg)
    return 1

  # Note: This doesn't happen in C++.
  except KeyboardInterrupt:
    print('')
    return 130  # 128 + 2

  except (IOError, OSError) as e:
    if 0:
      import traceback
      traceback.print_exc()

    # test this with prlimit --nproc=1 --pid=$$
    stderr_line('osh I/O error: %s', pyutil.strerror(e))
    return 2  # dash gives status 2
Пример #15
0
  def OnChange(self, opt_array, opt_name, b):
    # type: (List[bool], str, bool) -> bool
    """This method is called whenever an option is changed.

    Returns success or failure.
    """
    if opt_name == 'vi' or opt_name == 'emacs':
      # TODO: Replace with a hook?  Just like setting LANG= can have a hook.
      if self.line_input:
        self.line_input.parse_and_bind("set editing-mode " + opt_name);
      else:
        stderr_line(
            "Warning: Can't set option %r because Oil wasn't built with line editing (e.g. GNU readline)", opt_name)
        return False

      # Invert: they are mutually exclusive!
      if opt_name == 'vi':
        opt_array[option_i.emacs] = not b
      elif opt_name == 'emacs':
        opt_array[option_i.vi] = not b

    return True
Пример #16
0
def TeaMain(argv):
    # type: (str, List[str]) -> int
    arena = alloc.Arena()
    try:
        script_name = argv[1]
        arena.PushSource(source.MainFile(script_name))
    except IndexError:
        arena.PushSource(source.Stdin())
        f = sys.stdin
    else:
        try:
            f = open(script_name)
        except IOError as e:
            stderr_line("tea: Couldn't open %r: %s", script_name,
                        posix.strerror(e.errno))
            return 2

    aliases = {}  # Dummy value; not respecting aliases!

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    # Not used in tea, but OK...
    opt0_array = state.InitOpts()
    parse_opts = optview.Parse(opt0_array)

    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    line_reader = reader.FileLineReader(f, arena)

    try:
        parse_ctx.ParseTeaModule(line_reader)
        status = 0
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        status = 2

    return status
Пример #17
0
    def Matches(self, comp):
        # type: (Api) -> List[str]
        # Have to clear the response every time.  TODO: Reuse the object?
        state.SetGlobalArray(self.cmd_ev.mem, 'COMPREPLY', [])

        # New completions should use COMP_ARGV, a construct specific to OSH>
        state.SetGlobalArray(self.cmd_ev.mem, 'COMP_ARGV', comp.partial_argv)

        # Old completions may use COMP_WORDS.  It is split by : and = to emulate
        # bash's behavior.
        # More commonly, they will call _init_completion and use the 'words' output
        # of that, ignoring COMP_WORDS.
        comp_words = []
        for a in comp.partial_argv:
            AdjustArg(a, [':', '='], comp_words)
        if comp.index == -1:  # cmopgen
            comp_cword = comp.index
        else:
            comp_cword = len(comp_words) - 1  # weird invariant

        state.SetGlobalArray(self.cmd_ev.mem, 'COMP_WORDS', comp_words)
        state.SetGlobalString(self.cmd_ev.mem, 'COMP_CWORD', str(comp_cword))
        state.SetGlobalString(self.cmd_ev.mem, 'COMP_LINE', comp.line)
        state.SetGlobalString(self.cmd_ev.mem, 'COMP_POINT', str(comp.end))

        argv = [comp.first, comp.to_complete, comp.prev]
        self.log('Running completion function %r with arguments %s',
                 self.func.name, argv)

        self.comp_lookup.ClearCommandsChanged()
        status = self.cmd_ev.RunFuncForCompletion(self.func, argv)
        commands_changed = self.comp_lookup.GetCommandsChanged()

        self.log('comp.first %s, commands_changed: %s', comp.first,
                 commands_changed)

        if status == 124:
            cmd = os_path.basename(comp.first)
            if cmd in commands_changed:
                self.log('Got status 124 from %r and %s commands changed',
                         self.func.name, commands_changed)
                raise _RetryCompletion()
            else:
                # This happens with my own completion scripts.  bash doesn't show an
                # error.
                self.log(
                    "Function %r returned 124, but the completion spec for %r wasn't "
                    "changed", self.func.name, cmd)
                return []

        # Read the response.
        # NOTE: 'COMP_REPLY' would follow the naming convention!
        val = state.GetGlobal(self.cmd_ev.mem, 'COMPREPLY')
        if val.tag_() == value_e.Undef:
            # We set it above, so this error would only happen if the user unset it.
            # Not changing it means there were no completions.
            # TODO: This writes over the command line; it would be better to use an
            # error object.
            stderr_line('osh: Ran function %r but COMPREPLY was unset',
                        self.func.name)
            return []

        if val.tag_() != value_e.MaybeStrArray:
            log('ERROR: COMPREPLY should be an array, got %s', val)
            return []
        self.log('COMPREPLY %s', val)

        # Return this all at once so we don't have a generator.  COMPREPLY happens
        # all at once anyway.
        return val.strs
Пример #18
0
    def WaitForOne(self, eintr_retry):
        # type: (bool) -> int
        """Wait until the next process returns (or maybe Ctrl-C).

    Args:
      Should be True to prevent zombies

    Returns:
      -1      Nothing to wait for
      0       OK, job mutated
      >= 128  Interrupted with signal

      In the interactive shell, we return 0 if we get a Ctrl-C, so the caller
      will try again.

    Callers:
      wait -n  -- WaitForOne() just once
      wait     -- WaitForOne() in a loop
      wait $!  -- job.JobWait()

    Comparisons:
      bash: jobs.c waitchld() Has a special case macro(!) CHECK_WAIT_INTR for
      the wait builtin

      dash: jobs.c waitproc() uses sigfillset(), sigprocmask(), etc.  Runs in a
      loop while (gotsigchld), but that might be a hack for System V!

    You could imagine a clean API like posix::wait_for_one() 

    wait_result =
      ECHILD                     -- nothing to wait for
    | Done(int pid, int status)  -- process done
    | EINTR(bool sigint)         -- may or may not retry

    But I think we want to keep KeyboardInterrupt as an exception for now.

    """
        # This is a list of async jobs
        try:
            # Notes:
            # - The arg -1 makes it like wait(), which waits for any process.
            # - WUNTRACED is necessary to get stopped jobs.  What about WCONTINUED?
            # - Unlike other syscalls, we do NOT try on EINTR, because the 'wait'
            #   builtin should be interruptable.  This doesn't appear to cause any
            #   problems for other WaitForOne callers?
            pid, status = posix.waitpid(-1, WUNTRACED)
        except OSError as e:
            #log('wait() error: %s', e)
            if e.errno == errno_.ECHILD:
                return -1  # nothing to wait for caller should stop
            elif e.errno == errno_.EINTR:  # Bug #858 fix
                # Examples:
                # - 128 + SIGUSR1 = 128 + 10 = 138
                # - 128 + SIGUSR2 = 128 + 12 = 140
                return 0 if eintr_retry else (128 +
                                              self.sig_state.last_sig_num)
            else:
                # The signature of waitpid() means this shouldn't happen
                raise AssertionError()
        except KeyboardInterrupt:
            # NOTE: Another way to handle this is to disable SIGINT when a process is
            # running.  Not sure if there's any real difference.  bash and dash
            # handle SIGINT pretty differently.
            if self.exec_opts.interactive():
                # Caller should keep waiting.  If we run 'sleep 3' and hit Ctrl-C, both
                # processes will get SIGINT, but the shell has to wait again to get the
                # exit code.
                return 0
            else:
                raise  # abort a batch script

        # All child processes are supposed to be in this dict.  But this may
        # legitimately happen if a grandchild outlives the child (its parent).
        # Then it is reparented under this process, so we might receive
        # notification of its exit, even though we didn't start it.  We can't have
        # any knowledge of such processes, so print a warning.
        if pid not in self.job_state.child_procs:
            stderr_line("osh: PID %d stopped, but osh didn't start it", pid)
            return True  # caller should keep waiting

        proc = self.job_state.child_procs[pid]

        if WIFSIGNALED(status):
            term_sig = WTERMSIG(status)
            status = 128 + term_sig

            # Print newline after Ctrl-C.
            if term_sig == signal_.SIGINT:
                print('')

            proc.WhenDone(pid, status)

        elif WIFEXITED(status):
            status = WEXITSTATUS(status)
            #log('exit status: %s', status)
            proc.WhenDone(pid, status)

        elif WIFSTOPPED(status):
            #sig = posix.WSTOPSIG(status)

            # TODO: Do something nicer here.  Implement 'fg' command.
            # Show in jobs list.
            log('')
            log('[PID %d] Stopped', pid)
            self.job_state.NotifyStopped(
                pid)  # show in 'jobs' list, enable 'fg'
            proc.WhenStopped()

        else:
            raise AssertionError(status)

        self.last_status = status  # for wait -n
        self.tracer.OnProcessEnd(pid, status)
        return 0  # caller should keep waiting
Пример #19
0
def AppBundleMain(argv):
    # type: (List[str]) -> int

    # NOTE: This has a side effect of deleting _OVM_* from the environment!
    loader = pyutil.GetResourceLoader()

    b = os_path.basename(argv[0])
    main_name, ext = os_path.splitext(b)

    arg_r = args.Reader(argv)
    if main_name == 'oil' and ext:  # oil.py or oil.ovm
        arg_r.Next()
        first_arg = arg_r.Peek()
        if first_arg is None:
            raise error.Usage('Missing required applet name.')

        if first_arg in ('-h', '--help'):
            errfmt = None  # not needed here
            help_builtin = builtin_misc.Help(loader, errfmt)
            help_builtin.Run(pure.MakeBuiltinArgv(['bundle-usage']))
            sys.exit(0)

        if first_arg in ('-V', '--version'):
            pyutil.ShowAppVersion('Oil', loader)
            sys.exit(0)

        main_name = first_arg

    login_shell = False
    if main_name.startswith('-'):
        login_shell = True
        main_name = main_name[1:]

    if main_name in ('osh', 'sh'):
        # TODO:
        # - Initialize a different shell if line_input isn't present
        status = shell.Main('osh', arg_r, posix.environ, login_shell, loader,
                            line_input)

        _tlog('done osh main')
        return status

    elif main_name == 'osh-pure':
        # TODO: pure.Main()
        pass

    elif main_name == 'oshc':
        arg_r.Next()
        main_argv = arg_r.Rest()
        try:
            return OshCommandMain(main_argv)
        except error.Usage as e:
            stderr_line('oshc usage error: %s', e.msg)
            return 2

    elif main_name == 'oil':
        return shell.Main('oil', arg_r, posix.environ, login_shell, loader,
                          line_input)

    elif main_name == 'tea':
        main_argv = arg_r.Rest()
        return TeaMain(main_argv)

    # For testing latency
    elif main_name == 'true':
        return 0
    elif main_name == 'false':
        return 1
    elif main_name == 'readlink':
        main_argv = arg_r.Rest()
        return readlink.main(main_argv)
    else:
        raise error.Usage('Invalid applet name %r.' % main_name)
Пример #20
0
def Main(lang, arg_r, environ, login_shell, loader, line_input):
    # type: (str, args.Reader, Dict[str, str], bool, pyutil._ResourceLoader, Any) -> int
    """The full shell lifecycle.  Used by bin/osh and bin/oil.

  Args:
    lang: 'osh' or 'oil'
    argv0, arg_r: command line arguments
    environ: environment
    login_shell: Was - on the front?
    loader: to get help, version, grammar, etc.
    line_input: optional GNU readline
  """
    # Differences between osh and oil:
    # - --help?  I guess Oil has a SUPERSET of OSH options.
    # - oshrc vs oilrc
    # - shopt -s oil:all
    # - Change the prompt in the interactive shell?

    # osh-pure:
    # - no oil grammar
    # - no expression evaluator
    # - no interactive shell, or line_input
    # - no process.*
    #   process.{ExternalProgram,Waiter,FdState,JobState,SignalState} -- we want
    #   to evaluate config files without any of these
    # Modules not translated yet: completion, comp_ui, builtin_comp, process
    # - word evaluator
    #   - shouldn't glob?  set -o noglob?  or hard failure?
    #   - ~ shouldn't read from the file system
    #     - I guess it can just be the HOME=HOME?
    # Builtin:
    #   shellvm -c 'echo hi'
    #   shellvm <<< 'echo hi'

    argv0 = arg_r.Peek()
    assert argv0 is not None
    arg_r.Next()

    assert lang in ('osh', 'oil'), lang

    try:
        attrs = flag_spec.ParseMore('main', arg_r)
    except error.Usage as e:
        stderr_line('osh usage error: %s', e.msg)
        return 2
    flag = arg_types.main(attrs.attrs)

    arena = alloc.Arena()
    errfmt = ui.ErrorFormatter(arena)

    help_builtin = builtin_misc.Help(loader, errfmt)
    if flag.help:
        help_builtin.Run(MakeBuiltinArgv(['%s-usage' % lang]))
        return 0
    if flag.version:
        # OSH version is the only binary in Oil right now, so it's all one version.
        pyutil.ShowAppVersion('Oil', loader)
        return 0

    no_str = None  # type: str

    debug_stack = []  # type: List[state.DebugFrame]
    if arg_r.AtEnd():
        dollar0 = argv0
    else:
        dollar0 = arg_r.Peek()  # the script name, or the arg after -c

        # Copy quirky bash behavior.
        frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0,
                                  0)
        debug_stack.append(frame0)

    # Copy quirky bash behavior.
    frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0)
    debug_stack.append(frame1)

    script_name = arg_r.Peek()  # type: Optional[str]
    arg_r.Next()
    mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack)

    version_str = pyutil.GetVersion(loader)
    state.InitMem(mem, environ, version_str)

    procs = {}  # type: Dict[str, command__ShFunction]

    job_state = process.JobState()
    fd_state = process.FdState(errfmt, job_state, mem)

    opt_hook = state.OptHook()
    parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
    # TODO: only MutableOpts needs mem, so it's not a true circular dep.
    mem.exec_opts = exec_opts  # circular dep

    if attrs.show_options:  # special case: sh -o
        mutable_opts.ShowOptions([])
        return 0

    # Set these BEFORE processing flags, so they can be overridden.
    if lang == 'oil':
        mutable_opts.SetShoptOption('oil:all', True)

    builtin_pure.SetShellOpts(mutable_opts, attrs.opt_changes,
                              attrs.shopt_changes)
    # feedback between runtime and parser
    aliases = {}  # type: Dict[str, str]

    oil_grammar = None  # type: grammar.Grammar
    #oil_grammar = pyutil.LoadOilGrammar(loader)

    if flag.one_pass_parse and not exec_opts.noexec():
        e_usage('--one-pass-parse requires noexec (-n)')
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)
    parse_ctx.Init_OnePassParse(flag.one_pass_parse)

    # Three ParseContext instances SHARE aliases.
    comp_arena = alloc.Arena()
    comp_arena.PushSource(source.Unused('completion'))
    trail1 = parse_lib.Trail()
    # one_pass_parse needs to be turned on to complete inside backticks.  TODO:
    # fix the issue where ` gets erased because it's not part of
    # set_completer_delims().
    comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases,
                                      oil_grammar)
    comp_ctx.Init_Trail(trail1)
    comp_ctx.Init_OnePassParse(True)

    hist_arena = alloc.Arena()
    hist_arena.PushSource(source.Unused('history'))
    trail2 = parse_lib.Trail()
    hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
                                      oil_grammar)
    hist_ctx.Init_Trail(trail2)

    # Deps helps manages dependencies.  These dependencies are circular:
    # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub
    # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
    # - cmd_ev and builtins (which execute code, like eval)
    # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
    cmd_deps = cmd_eval.Deps()
    cmd_deps.mutable_opts = mutable_opts

    # TODO: In general, cmd_deps are shared between the mutually recursive
    # evaluators.  Some of the four below are only shared between a builtin and
    # the CommandEvaluator, so we could put them somewhere else.
    cmd_deps.traps = {}
    cmd_deps.trap_nodes = []  # TODO: Clear on fork() to avoid duplicates

    waiter = process.Waiter(job_state, exec_opts)

    my_pid = posix.getpid()

    debug_path = ''
    debug_dir = environ.get('OSH_DEBUG_DIR')
    if flag.debug_file is not None:
        # --debug-file takes precedence over OSH_DEBUG_DIR
        debug_path = flag.debug_file
    elif debug_dir is not None:
        debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)

    if len(debug_path):
        raise NotImplementedError()
    else:
        debug_f = util.NullDebugFile()  # type: util._DebugFile

    cmd_deps.debug_f = debug_f

    # Not using datetime for dependency reasons.  TODO: maybe show the date at
    # the beginning of the log, and then only show time afterward?  To save
    # space, and make space for microseconds.  (datetime supports microseconds
    # but time.strftime doesn't).
    if mylib.PYTHON:
        iso_stamp = time_.strftime("%Y-%m-%d %H:%M:%S")
        debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid,
                    arg_r.argv)
    if len(debug_path):
        debug_f.log('Writing logs to %r', debug_path)

    interp = environ.get('OSH_HIJACK_SHEBANG', '')
    search_path = state.SearchPath(mem)
    ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f)

    splitter = split.SplitContext(mem)

    # This could just be OSH_DEBUG_STREAMS='debug crash' ?  That might be
    # stuffing too much into one, since a .json crash dump isn't a stream.
    crash_dump_dir = environ.get('OSH_CRASH_DUMP_DIR', '')
    cmd_deps.dumper = dev.CrashDumper(crash_dump_dir)

    if flag.xtrace_to_debug_file:
        trace_f = debug_f
    else:
        trace_f = util.DebugFile(mylib.Stderr())

    #comp_lookup = completion.Lookup()

    # Various Global State objects to work around readline interfaces
    #compopt_state = completion.OptionState()
    #comp_ui_state = comp_ui.State()
    #prompt_state = comp_ui.PromptState()

    dir_stack = state.DirStack()

    #
    # Initialize builtins that don't depend on evaluators
    #

    builtins = {}  # type: Dict[int, vm._Builtin]

    AddPure(builtins, mem, procs, mutable_opts, aliases, search_path, errfmt)
    AddIO(builtins, mem, dir_stack, exec_opts, splitter, parse_ctx, errfmt)

    builtins[builtin_i.help] = help_builtin

    #
    # Initialize Evaluators
    #

    arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt)
    bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt)
    expr_ev = None  # type: expr_eval.OilEvaluator
    word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)

    assign_b = InitAssignmentBuiltins(mem, procs, errfmt)
    cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b,
                                       arena, cmd_deps)

    shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
                                      builtins, search_path, ext_prog, waiter,
                                      job_state, fd_state, errfmt)
    #shell_ex = NullExecutor(exec_opts, mutable_opts, procs, builtins)

    # PromptEvaluator rendering is needed in non-interactive shells for @P.
    prompt_ev = prompt.Evaluator(lang, parse_ctx, mem)
    tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev,
                        trace_f)

    # Wire up circular dependencies.
    vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
                        prompt_ev, tracer)

    #
    # Initialize builtins that depend on evaluators
    #

    # note: 'printf -v a[i]' and 'unset a[i]' require same deps
    builtins[builtin_i.printf] = builtin_printf.Printf(mem, exec_opts,
                                                       parse_ctx, arith_ev,
                                                       errfmt)

    builtins[builtin_i.unset] = builtin_assign.Unset(mem, exec_opts, procs,
                                                     parse_ctx, arith_ev,
                                                     errfmt)
    builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev)

    #source_builtin = builtin_meta.Source(parse_ctx, search_path, cmd_ev,
    #fd_state, errfmt)
    #builtins[builtin_i.source] = source_builtin
    #builtins[builtin_i.dot] = source_builtin

    AddMeta(builtins, shell_ex, mutable_opts, mem, procs, aliases, search_path,
            errfmt)
    AddBlock(builtins, mem, mutable_opts, dir_stack, cmd_ev, errfmt)

    #sig_state = process.SignalState()
    #sig_state.InitShell()

    #builtins[builtin_i.trap] = builtin_process.Trap(sig_state, cmd_deps.traps,
    #                                                cmd_deps.trap_nodes,
    #                                                parse_ctx, errfmt)

    if flag.c is not None:
        arena.PushSource(source.CFlag())
        line_reader = reader.StringLineReader(flag.c,
                                              arena)  # type: reader._Reader
        if flag.i:  # -c and -i can be combined
            mutable_opts.set_interactive()

    elif flag.i:  # force interactive
        raise NotImplementedError()

    else:
        if script_name is None:
            stdin = mylib.Stdin()
            arena.PushSource(source.Stdin(''))
            line_reader = reader.FileLineReader(stdin, arena)
        else:
            arena.PushSource(source.MainFile(script_name))
            try:
                f = fd_state.Open(script_name)
                #f = mylib.open(script_name)
            except OSError as e:
                stderr_line("osh: Couldn't open %r: %s", script_name,
                            pyutil.strerror(e))
                return 1
            line_reader = reader.FileLineReader(f, arena)

    # TODO: assert arena.NumSourcePaths() == 1
    # TODO: .rc file needs its own arena.
    c_parser = parse_ctx.MakeOshParser(line_reader)

    if exec_opts.interactive():
        raise NotImplementedError()

    if exec_opts.noexec():
        status = 0
        try:
            node = main_loop.ParseWholeFile(c_parser)
        except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            status = 2

        if status == 0:
            if flag.parser_mem_dump is not None:  # only valid in -n mode
                input_path = '/proc/%d/status' % posix.getpid()
                pyutil.CopyFile(input_path, flag.parser_mem_dump)

            ui.PrintAst(node, flag)
    else:
        if flag.parser_mem_dump is not None:
            e_usage('--parser-mem-dump can only be used with -n')

        try:
            status = main_loop.Batch(cmd_ev,
                                     c_parser,
                                     arena,
                                     cmd_flags=cmd_eval.IsMainProgram)
        except util.UserExit as e:
            status = e.status
        box = [status]
        cmd_ev.MaybeRunExitTrap(box)
        status = box[0]

    # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub
    # don't because they call sys.exit().
    if flag.runtime_mem_dump is not None:
        input_path = '/proc/%d/status' % posix.getpid()
        pyutil.CopyFile(input_path, flag.runtime_mem_dump)

    # NOTE: We haven't closed the file opened with fd_state.Open
    return status
Пример #21
0
def OshCommandMain(argv):
    """Run an 'oshc' tool.

  'osh' is short for "osh compiler" or "osh command".

  TODO:
  - oshc --help

  oshc deps
    --path: the $PATH to use to find executables.  What about libraries?

    NOTE: we're leaving out su -c, find, xargs, etc.?  Those should generally
    run functions using the $0 pattern.
    --chained-command sudo
  """
    try:
        action = argv[0]
    except IndexError:
        raise error.Usage('Missing required subcommand.')

    if action not in SUBCOMMANDS:
        raise error.Usage('Invalid subcommand %r.' % action)

    if action == 'parse-glob':
        # Pretty-print the AST produced by osh/glob_.py
        print('TODO:parse-glob')
        return 0

    if action == 'parse-printf':
        # Pretty-print the AST produced by osh/builtin_printf.py
        print('TODO:parse-printf')
        return 0

    arena = alloc.Arena()
    try:
        script_name = argv[1]
        arena.PushSource(source.MainFile(script_name))
    except IndexError:
        arena.PushSource(source.Stdin())
        f = sys.stdin
    else:
        try:
            f = open(script_name)
        except IOError as e:
            stderr_line("oshc: Couldn't open %r: %s", script_name,
                        posix.strerror(e.errno))
            return 2

    aliases = {}  # Dummy value; not respecting aliases!

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    opt0_array = state.InitOpts()
    no_stack = None  # type: List[bool]  # for mycpp
    opt_stacks = [no_stack] * option_i.ARRAY_SIZE  # type: List[List[bool]]

    parse_opts = optview.Parse(opt0_array, opt_stacks)
    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)
    parse_ctx.Init_OnePassParse(True)

    line_reader = reader.FileLineReader(f, arena)
    c_parser = parse_ctx.MakeOshParser(line_reader)

    try:
        node = main_loop.ParseWholeFile(c_parser)
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        return 2
    assert node is not None

    f.close()

    # Columns for list-*
    # path line name
    # where name is the binary path, variable name, or library path.

    # bin-deps and lib-deps can be used to make an app bundle.
    # Maybe I should list them together?  'deps' can show 4 columns?
    #
    # path, line, type, name
    #
    # --pretty can show the LST location.

    # stderr: show how we're following imports?

    if action == 'translate':
        osh2oil.PrintAsOil(arena, node)

    elif action == 'arena':  # for debugging
        osh2oil.PrintArena(arena)

    elif action == 'spans':  # for debugging
        osh2oil.PrintSpans(arena)

    elif action == 'format':
        # TODO: autoformat code
        raise NotImplementedError(action)

    elif action == 'deps':
        deps.Deps(node)

    elif action == 'undefined-vars':  # could be environment variables
        raise NotImplementedError()

    else:
        raise AssertionError  # Checked above

    return 0
Пример #22
0
def Main(lang, arg_r, environ, login_shell, loader, line_input):
    # type: (str, args.Reader, Dict[str, str], bool, pyutil._ResourceLoader, Any) -> int
    """The full shell lifecycle.  Used by bin/osh and bin/oil.

  Args:
    lang: 'osh' or 'oil'
    argv0, arg_r: command line arguments
    environ: environment
    login_shell: Was - on the front?
    loader: to get help, version, grammar, etc.
    line_input: optional GNU readline
  """
    # Differences between osh and oil:
    # - --help?  I guess Oil has a SUPERSET of OSH options.
    # - oshrc vs oilrc
    # - shopt -s oil:all
    # - Change the prompt in the interactive shell?

    # osh-pure:
    # - no oil grammar
    # - no expression evaluator
    # - no interactive shell, or line_input
    # - no process.*
    #   process.{ExternalProgram,Waiter,FdState,JobState,SignalState} -- we want
    #   to evaluate config files without any of these
    # Modules not translated yet: completion, comp_ui, builtin_comp, process
    # - word evaluator
    #   - shouldn't glob?  set -o noglob?  or hard failure?
    #   - ~ shouldn't read from the file system
    #     - I guess it can just be the HOME=HOME?
    # Builtin:
    #   shellvm -c 'echo hi'
    #   shellvm <<< 'echo hi'

    argv0 = arg_r.Peek()
    assert argv0 is not None
    arg_r.Next()

    assert lang in ('osh', 'oil'), lang

    try:
        attrs = flag_spec.ParseMore('main', arg_r)
    except error.Usage as e:
        stderr_line('osh usage error: %s', e.msg)
        return 2
    flag = arg_types.main(attrs.attrs)

    arena = alloc.Arena()
    errfmt = ui.ErrorFormatter(arena)

    help_builtin = builtin_misc.Help(loader, errfmt)
    if flag.help:
        help_builtin.Run(pure.MakeBuiltinArgv(['%s-usage' % lang]))
        return 0
    if flag.version:
        # OSH version is the only binary in Oil right now, so it's all one version.
        pyutil.ShowAppVersion('Oil', loader)
        return 0

    no_str = None  # type: str

    debug_stack = []  # type: List[state.DebugFrame]
    if arg_r.AtEnd():
        dollar0 = argv0
    else:
        dollar0 = arg_r.Peek()  # the script name, or the arg after -c

        # Copy quirky bash behavior.
        frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0,
                                  0)
        debug_stack.append(frame0)

    # Copy quirky bash behavior.
    frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0)
    debug_stack.append(frame1)

    script_name = arg_r.Peek()  # type: Optional[str]
    arg_r.Next()
    mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack)

    version_str = pyutil.GetVersion(loader)
    state.InitMem(mem, environ, version_str)

    builtin_funcs.Init(mem)

    procs = {}  # type: Dict[str, command__ShFunction]

    job_state = process.JobState()
    fd_state = process.FdState(errfmt, job_state, mem)

    opt_hook = ShellOptHook(line_input)
    parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
    # TODO: only MutableOpts needs mem, so it's not a true circular dep.
    mem.exec_opts = exec_opts  # circular dep

    if attrs.show_options:  # special case: sh -o
        mutable_opts.ShowOptions([])
        return 0

    # Set these BEFORE processing flags, so they can be overridden.
    if lang == 'oil':
        mutable_opts.SetShoptOption('oil:all', True)

    builtin_pure.SetShellOpts(mutable_opts, attrs.opt_changes,
                              attrs.shopt_changes)
    # feedback between runtime and parser
    aliases = {}  # type: Dict[str, str]

    oil_grammar = pyutil.LoadOilGrammar(loader)

    if flag.one_pass_parse and not exec_opts.noexec():
        raise error.Usage('--one-pass-parse requires noexec (-n)')
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)
    parse_ctx.Init_OnePassParse(flag.one_pass_parse)

    # Three ParseContext instances SHARE aliases.
    comp_arena = alloc.Arena()
    comp_arena.PushSource(source.Unused('completion'))
    trail1 = parse_lib.Trail()
    # one_pass_parse needs to be turned on to complete inside backticks.  TODO:
    # fix the issue where ` gets erased because it's not part of
    # set_completer_delims().
    comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases,
                                      oil_grammar)
    comp_ctx.Init_Trail(trail1)
    comp_ctx.Init_OnePassParse(True)

    hist_arena = alloc.Arena()
    hist_arena.PushSource(source.Unused('history'))
    trail2 = parse_lib.Trail()
    hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
                                      oil_grammar)
    hist_ctx.Init_Trail(trail2)

    # Deps helps manages dependencies.  These dependencies are circular:
    # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub
    # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
    # - cmd_ev and builtins (which execute code, like eval)
    # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
    cmd_deps = cmd_eval.Deps()
    cmd_deps.mutable_opts = mutable_opts

    # TODO: In general, cmd_deps are shared between the mutually recursive
    # evaluators.  Some of the four below are only shared between a builtin and
    # the CommandEvaluator, so we could put them somewhere else.
    cmd_deps.traps = {}
    cmd_deps.trap_nodes = []  # TODO: Clear on fork() to avoid duplicates

    waiter = process.Waiter(job_state, exec_opts)

    my_pid = posix.getpid()

    debug_path = ''
    debug_dir = environ.get('OSH_DEBUG_DIR')
    if flag.debug_file is not None:
        # --debug-file takes precedence over OSH_DEBUG_DIR
        debug_path = flag.debug_file
    elif debug_dir is not None:
        debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)

    if len(debug_path):
        # This will be created as an empty file if it doesn't exist, or it could be
        # a pipe.
        try:
            debug_f = util.DebugFile(
                fd_state.OpenForWrite(debug_path))  # type: util._DebugFile
        except OSError as e:
            stderr_line("osh: Couldn't open %r: %s", debug_path,
                        posix.strerror(e.errno))
            return 2
    else:
        debug_f = util.NullDebugFile()

    cmd_deps.debug_f = debug_f

    # Not using datetime for dependency reasons.  TODO: maybe show the date at
    # the beginning of the log, and then only show time afterward?  To save
    # space, and make space for microseconds.  (datetime supports microseconds
    # but time.strftime doesn't).
    if mylib.PYTHON:
        iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S")
        debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid,
                    arg_r.argv)
    if len(debug_path):
        debug_f.log('Writing logs to %r', debug_path)

    interp = environ.get('OSH_HIJACK_SHEBANG', '')
    search_path = state.SearchPath(mem)
    ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f)

    splitter = split.SplitContext(mem)

    # split() builtin
    # TODO: Accept IFS as a named arg?  split('a b', IFS=' ')
    builtin_funcs.SetGlobalFunc(
        mem,
        'split',
        lambda s, ifs=None: splitter.SplitForWordEval(s, ifs=ifs))

    # glob() builtin
    # TODO: This is instantiation is duplicated in osh/word_eval.py
    globber = glob_.Globber(exec_opts)
    builtin_funcs.SetGlobalFunc(mem, 'glob', lambda s: globber.OilFuncCall(s))

    # This could just be OSH_DEBUG_STREAMS='debug crash' ?  That might be
    # stuffing too much into one, since a .json crash dump isn't a stream.
    crash_dump_dir = environ.get('OSH_CRASH_DUMP_DIR', '')
    cmd_deps.dumper = dev.CrashDumper(crash_dump_dir)

    if flag.xtrace_to_debug_file:
        trace_f = debug_f
    else:
        trace_f = util.DebugFile(mylib.Stderr())

    comp_lookup = completion.Lookup()

    # Various Global State objects to work around readline interfaces
    compopt_state = completion.OptionState()
    comp_ui_state = comp_ui.State()
    prompt_state = comp_ui.PromptState()

    dir_stack = state.DirStack()

    #
    # Initialize builtins that don't depend on evaluators
    #

    builtins = {}  # type: Dict[int, vm._Builtin]

    pure.AddPure(builtins, mem, procs, mutable_opts, aliases, search_path,
                 errfmt)
    pure.AddIO(builtins, mem, dir_stack, exec_opts, splitter, errfmt)
    AddProcess(builtins, mem, ext_prog, fd_state, job_state, waiter,
               search_path, errfmt)
    AddOil(builtins, mem, errfmt)

    builtins[builtin_i.help] = help_builtin

    # Interactive, depend on line_input
    builtins[builtin_i.bind] = builtin_lib.Bind(line_input, errfmt)
    builtins[builtin_i.history] = builtin_lib.History(line_input,
                                                      mylib.Stdout())

    #
    # Assignment builtins
    #

    assign_b = {}  # type: Dict[int, vm._AssignBuiltin]

    new_var = builtin_assign.NewVar(mem, procs, errfmt)
    assign_b[builtin_i.declare] = new_var
    assign_b[builtin_i.typeset] = new_var
    assign_b[builtin_i.local] = new_var

    assign_b[builtin_i.export_] = builtin_assign.Export(mem, errfmt)
    assign_b[builtin_i.readonly] = builtin_assign.Readonly(mem, errfmt)

    #
    # Initialize Evaluators
    #

    arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt)
    bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt)
    expr_ev = expr_eval.OilEvaluator(mem, procs, splitter, errfmt)
    word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)
    cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b,
                                       arena, cmd_deps)

    shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
                                      builtins, search_path, ext_prog, waiter,
                                      job_state, fd_state, errfmt)

    # PromptEvaluator rendering is needed in non-interactive shells for @P.
    prompt_ev = prompt.Evaluator(lang, parse_ctx, mem)
    tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev,
                        trace_f)

    # Wire up circular dependencies.
    vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
                        prompt_ev, tracer)

    #
    # Initialize builtins that depend on evaluators
    #

    # note: 'printf -v a[i]' and 'unset a[i]' require same deps
    builtins[builtin_i.printf] = builtin_printf.Printf(mem, exec_opts,
                                                       parse_ctx, arith_ev,
                                                       errfmt)
    builtins[builtin_i.unset] = builtin_assign.Unset(mem, exec_opts, procs,
                                                     parse_ctx, arith_ev,
                                                     errfmt)
    builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev)

    source_builtin = builtin_meta.Source(parse_ctx, search_path, cmd_ev,
                                         fd_state, errfmt)
    builtins[builtin_i.source] = source_builtin
    builtins[builtin_i.dot] = source_builtin

    builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt)
    builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs,
                                                       aliases, search_path)

    spec_builder = builtin_comp.SpecBuilder(cmd_ev, parse_ctx, word_ev,
                                            splitter, comp_lookup)
    complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup)
    builtins[builtin_i.complete] = complete_builtin
    builtins[builtin_i.compgen] = builtin_comp.CompGen(spec_builder)
    builtins[builtin_i.compopt] = builtin_comp.CompOpt(compopt_state, errfmt)
    builtins[builtin_i.compadjust] = builtin_comp.CompAdjust(mem)

    # These builtins take blocks, and thus need cmd_ev.
    builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt)
    builtins[builtin_i.json] = builtin_oil.Json(mem, cmd_ev, errfmt)

    sig_state = pyos.SignalState()
    sig_state.InitShell()

    builtins[builtin_i.trap] = builtin_process.Trap(sig_state, cmd_deps.traps,
                                                    cmd_deps.trap_nodes,
                                                    parse_ctx, errfmt)

    # History evaluation is a no-op if line_input is None.
    hist_ev = history.Evaluator(line_input, hist_ctx, debug_f)

    if flag.c is not None:
        arena.PushSource(source.CFlag())
        line_reader = reader.StringLineReader(flag.c,
                                              arena)  # type: reader._Reader
        if flag.i:  # -c and -i can be combined
            mutable_opts.set_interactive()

    elif flag.i:  # force interactive
        arena.PushSource(source.Stdin(' -i'))
        line_reader = py_reader.InteractiveLineReader(arena, prompt_ev,
                                                      hist_ev, line_input,
                                                      prompt_state)
        mutable_opts.set_interactive()

    else:
        if script_name is None:
            stdin = mylib.Stdin()
            if stdin.isatty():
                arena.PushSource(source.Interactive())
                line_reader = py_reader.InteractiveLineReader(
                    arena, prompt_ev, hist_ev, line_input, prompt_state)
                mutable_opts.set_interactive()
            else:
                arena.PushSource(source.Stdin(''))
                line_reader = reader.FileLineReader(stdin, arena)
        else:
            arena.PushSource(source.MainFile(script_name))
            try:
                f = fd_state.Open(script_name)
            except OSError as e:
                stderr_line("osh: Couldn't open %r: %s", script_name,
                            posix.strerror(e.errno))
                return 1
            line_reader = reader.FileLineReader(f, arena)

    # TODO: assert arena.NumSourcePaths() == 1
    # TODO: .rc file needs its own arena.
    c_parser = parse_ctx.MakeOshParser(line_reader)

    if exec_opts.interactive():
        # bash: 'set -o emacs' is the default only in the interactive shell
        mutable_opts.set_emacs()

        # Calculate ~/.config/oil/oshrc or oilrc
        # Use ~/.config/oil to avoid cluttering the user's home directory.  Some
        # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc.

        # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
        home_dir = pyos.GetMyHomeDir()
        assert home_dir is not None
        history_filename = os_path.join(home_dir,
                                        '.config/oil/history_%s' % lang)

        if line_input:
            # NOTE: We're using a different WordEvaluator here.
            ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter,
                                                   errfmt)

            ev.arith_ev = arith_ev
            ev.expr_ev = expr_ev
            ev.prompt_ev = prompt_ev
            ev.CheckCircularDeps()

            root_comp = completion.RootCompleter(ev, mem, comp_lookup,
                                                 compopt_state, comp_ui_state,
                                                 comp_ctx, debug_f)

            term_width = 0
            if flag.completion_display == 'nice':
                try:
                    term_width = libc.get_terminal_width()
                except IOError:  # stdin not a terminal
                    pass

            if term_width != 0:
                display = comp_ui.NiceDisplay(
                    term_width, comp_ui_state, prompt_state, debug_f,
                    line_input)  # type: comp_ui._IDisplay
            else:
                display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
                                                 debug_f)

            comp_ui.InitReadline(line_input, history_filename, root_comp,
                                 display, debug_f)
            _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)

        else:  # Without readline module
            display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
                                             debug_f)

        sig_state.InitInteractiveShell(display)

        rc_path = flag.rcfile or os_path.join(home_dir,
                                              '.config/oil/%src' % lang)
        try:
            # NOTE: Should be called AFTER _InitDefaultCompletions.
            SourceStartupFile(fd_state, rc_path, lang, parse_ctx, cmd_ev)
        except util.UserExit as e:
            return e.status

        line_reader.Reset()  # After sourcing startup file, render $PS1

        prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev)
        try:
            status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
                                           prompt_plugin, errfmt)
        except util.UserExit as e:
            status = e.status
        box = [status]
        cmd_ev.MaybeRunExitTrap(box)
        status = box[0]

        return status

    if flag.rcfile:  # bash doesn't have this warning, but it's useful
        stderr_line('osh warning: --rcfile ignored in non-interactive shell')

    if exec_opts.noexec():
        status = 0
        try:
            node = main_loop.ParseWholeFile(c_parser)
        except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            status = 2

        if status == 0:
            if flag.parser_mem_dump is not None:  # only valid in -n mode
                input_path = '/proc/%d/status' % posix.getpid()
                pyutil.CopyFile(input_path, flag.parser_mem_dump)

            ui.PrintAst(node, flag)
    else:
        if flag.parser_mem_dump is not None:
            raise error.Usage('--parser-mem-dump can only be used with -n')

        try:
            status = main_loop.Batch(cmd_ev,
                                     c_parser,
                                     arena,
                                     cmd_flags=cmd_eval.IsMainProgram)
        except util.UserExit as e:
            status = e.status
        box = [status]
        cmd_ev.MaybeRunExitTrap(box)
        status = box[0]

    # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub
    # don't because they call sys.exit().
    if flag.runtime_mem_dump is not None:
        input_path = '/proc/%d/status' % posix.getpid()
        pyutil.CopyFile(input_path, flag.runtime_mem_dump)

    # NOTE: We haven't closed the file opened with fd_state.Open
    return status
Пример #23
0
  def Run(self, cmd_val):
    # type: (cmd_value__Argv) -> int
    attrs, arg_r = flag_spec.ParseCmdVal('trap', cmd_val)
    arg = arg_types.trap(attrs.attrs)

    if arg.p:  # Print registered handlers
      for name, value in self.traps.iteritems():
        # The unit tests rely on this being one line.
        # bash prints a line that can be re-parsed.
        print('%s %s' % (name, value.__class__.__name__))

      return 0

    if arg.l:  # List valid signals and hooks
      ordered = _SIGNAL_NAMES.items()
      ordered.sort(key=lambda x: x[1])

      for name in _HOOK_NAMES:
        print('   %s' % name)
      for name, int_val in ordered:
        print('%2d %s' % (int_val, name))

      return 0

    code_str = arg_r.ReadRequired('requires a code string')
    sig_spec, sig_spid = arg_r.ReadRequired2('requires a signal or hook name')

    # sig_key is NORMALIZED sig_spec: a signal number string or string hook
    # name.
    sig_key = None  # type: Optional[str]
    sig_num = None
    if sig_spec in _HOOK_NAMES:
      sig_key = sig_spec
    elif sig_spec == '0':  # Special case
      sig_key = 'EXIT'
    else:
      sig_num = _GetSignalNumber(sig_spec)
      if sig_num is not None:
        sig_key = str(sig_num)

    if sig_key is None:
      self.errfmt.Print("Invalid signal or hook %r", sig_spec,
                        span_id=cmd_val.arg_spids[2])
      return 1

    # NOTE: sig_spec isn't validated when removing handlers.
    if code_str == '-':
      if sig_key in _HOOK_NAMES:
        try:
          del self.traps[sig_key]
        except KeyError:
          pass
        return 0

      if sig_num is not None:
        try:
          del self.traps[sig_key]
        except KeyError:
          pass

        self.sig_state.RemoveUserTrap(sig_num)
        return 0

      raise AssertionError('Signal or trap')

    # Try parsing the code first.
    node = self._ParseTrapCode(code_str)
    if node is None:
      return 1  # ParseTrapCode() prints an error for us.

    # Register a hook.
    if sig_key in _HOOK_NAMES:
      if sig_key in ('ERR', 'RETURN', 'DEBUG'):
        stderr_line("osh warning: The %r hook isn't implemented", sig_spec)
      self.traps[sig_key] = _TrapHandler(node, self.nodes_to_run)
      return 0

    # Register a signal.
    if sig_num is not None:
      handler = _TrapHandler(node, self.nodes_to_run)
      # For signal handlers, the traps dictionary is used only for debugging.
      self.traps[sig_key] = handler
      if sig_num in (signal.SIGKILL, signal.SIGSTOP):
        self.errfmt.Print("Signal %r can't be handled", sig_spec,
                          span_id=sig_spid)
        # Other shells return 0, but this seems like an obvious error
        return 1
      self.sig_state.AddUserTrap(sig_num, handler)
      return 0

    raise AssertionError('Signal or trap')
Пример #24
0
    def WaitForOne(self):
        # type: () -> bool
        """Wait until the next process returns (or maybe Ctrl-C).

    Returns:
      True if we got a notification, or False if there was nothing to wait for.

      In the interactive shell, we return True if we get a Ctrl-C, so the
      caller will try again.
    """
        # This is a list of async jobs
        try:
            # -1 makes it like wait(), which waits for any process.
            # NOTE: WUNTRACED is necessary to get stopped jobs.  What about
            # WCONTINUED?
            pid, status = posix.waitpid(-1, WUNTRACED)
        except OSError as e:
            #log('wait() error: %s', e)
            if e.errno == errno_.ECHILD:
                return False  # nothing to wait for caller should stop
            else:
                # We should never get here.  EINTR was handled by the 'posix'
                # module.  The only other error is EINVAL, which doesn't apply to
                # this call.
                raise
        except KeyboardInterrupt:
            # NOTE: Another way to handle this is to disable SIGINT when a process is
            # running.  Not sure if there's any real difference.  bash and dash
            # handle SIGINT pretty differently.
            if self.exec_opts.interactive():
                # Caller should keep waiting.  If we run 'sleep 3' and hit Ctrl-C, both
                # processes will get SIGINT, but the shell has to wait again to get the
                # exit code.
                return True
            else:
                raise  # abort a batch script

        #log('WAIT got %s %s', pid, status)

        # All child processes are supposed to be in this dict.  But this may
        # legitimately happen if a grandchild outlives the child (its parent).
        # Then it is reparented under this process, so we might receive
        # notification of its exit, even though we didn't start it.  We can't have
        # any knowledge of such processes, so print a warning.
        if pid not in self.job_state.child_procs:
            stderr_line("osh: PID %d stopped, but osh didn't start it", pid)
            return True  # caller should keep waiting

        proc = self.job_state.child_procs[pid]

        if WIFSIGNALED(status):
            term_sig = WTERMSIG(status)
            status = 128 + term_sig

            # Print newline after Ctrl-C.
            if term_sig == signal_.SIGINT:
                print('')

            proc.WhenDone(pid, status)

        elif WIFEXITED(status):
            status = WEXITSTATUS(status)
            #log('exit status: %s', status)
            proc.WhenDone(pid, status)

        elif WIFSTOPPED(status):
            #sig = posix.WSTOPSIG(status)

            # TODO: Do something nicer here.  Implement 'fg' command.
            # Show in jobs list.
            log('')
            log('[PID %d] Stopped', pid)
            self.job_state.NotifyStopped(
                pid)  # show in 'jobs' list, enable 'fg'
            proc.WhenStopped()

        self.last_status = status  # for wait -n

        return True  # caller should keep waiting
Пример #25
0
def Main(arg_r):
    # type: (args.Reader) -> int
    """
  Usage:
    tea myprog.tea                      # Run the script
    tea -c 'func main() { echo "hi" }'  # Run this snippet.  Not common since
                                        # there's no top level statementes!
                                        # Use bin/oil for that.
    tea -n -c 'var x = 1'               # Parse it

    # Compile to C++.  Produce myprog.cc and myprog.h.
    tea --cpp-out myprog myprog.tea lib.tea
  """
    try:
        attrs = flag_spec.ParseOil('tea_main', arg_r)
    except error.Usage as e:
        stderr_line('tea usage error: %s', e.msg)
        return 2
    arg = arg_types.tea_main(attrs.attrs)

    arena = alloc.Arena()

    if arg.c is not None:
        arena.PushSource(source.CFlag())
        line_reader = reader.StringLineReader(arg.c,
                                              arena)  # type: reader._Reader
    else:
        script_name = arg_r.Peek()
        if script_name is None:
            arena.PushSource(source.Stdin())
            f = mylib.Stdin()  # type: mylib.LineReader
        else:
            arena.PushSource(source.MainFile(script_name))
            try:
                # Hack for type checking.  core/shell.py uses fd_state.Open().
                f = cast('mylib.LineReader', open(script_name))
            except IOError as e:
                stderr_line("tea: Couldn't open %r: %s", script_name,
                            posix.strerror(e.errno))
                return 2
        line_reader = reader.FileLineReader(f, arena)

    # Dummy value; not respecting aliases!
    aliases = {}  # type: Dict[str, str]

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    # Not used in tea, but OK...
    opt0_array = state.InitOpts()
    no_stack = None  # type: List[bool]  # for mycpp
    opt_stacks = [no_stack] * option_i.ARRAY_SIZE  # type: List[List[bool]]
    parse_opts = optview.Parse(opt0_array, opt_stacks)

    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    if arg.n:
        try:
            parse_ctx.ParseTeaModule(line_reader)
            status = 0
        except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            status = 2
    else:
        e_usage("Tea doesn't run anything yet.  Pass -n to parse.")

    return status
Пример #26
0
 def StderrLine(self, msg):
     # type: (str) -> None
     stderr_line(msg)