Example #1
0
    def Run(self):
        # type: () -> None
        val = self.mem.GetValue('PROMPT_COMMAND')
        if val.tag_() != value_e.Str:
            return

        # PROMPT_COMMAND almost never changes, so we try to cache its parsing.
        # This avoids memory allocations.
        prompt_cmd = cast(value__Str, val).s
        node = self.parse_cache.get(prompt_cmd)
        if node is None:
            line_reader = reader.StringLineReader(prompt_cmd, self.arena)
            c_parser = self.parse_ctx.MakeOshParser(line_reader)

            # NOTE: This is similar to CommandEvaluator.ParseTrapCode().
            # TODO: Add spid
            with alloc.ctx_Location(self.arena,
                                    source.PromptCommand(runtime.NO_SPID)):
                try:
                    node = main_loop.ParseWholeFile(c_parser)
                except error.Parse as e:
                    ui.PrettyPrintError(e, self.arena)
                    return  # don't execute

            self.parse_cache[prompt_cmd] = node

        # Save this so PROMPT_COMMAND can't set $?
        with state.ctx_Status(self.mem):
            # Catches fatal execution error
            self.cmd_ev.ExecuteAndCatch(node)
Example #2
0
File: shell.py Project: dpercy/oil
def SourceStartupFile(fd_state, rc_path, lang, parse_ctx, cmd_ev):
    # type: (process.FdState, str, str, parse_lib.ParseContext, cmd_eval.CommandEvaluator) -> None

    # Right now this is called when the shell is interactive.  (Maybe it should
    # be called on login_shel too.)
    #
    # Terms:
    # - interactive shell: Roughly speaking, no args or -c, and isatty() is true
    #   for stdin and stdout.
    # - login shell: Started from the top level, e.g. from init or ssh.
    #
    # We're not going to copy everything bash does because it's too complex, but
    # for reference:
    # https://www.gnu.org/software/bash/manual/bash.html#Bash-Startup-Files
    # Bash also has --login.

    try:
        f = fd_state.Open(rc_path)
    except OSError as e:
        # TODO: Could warn about nonexistent explicit --rcfile?
        if e.errno != errno.ENOENT:
            raise  # Goes to top level.  Handle this better?
        return

    arena = parse_ctx.arena
    rc_line_reader = reader.FileLineReader(f, arena)
    rc_c_parser = parse_ctx.MakeOshParser(rc_line_reader)

    with alloc.ctx_Location(arena, source.SourcedFile(rc_path)):
        # TODO: handle status, e.g. 2 for ParseError
        status = main_loop.Batch(cmd_ev, rc_c_parser, arena)

    f.close()
Example #3
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int

        # There are no flags, but we need it to respect --
        _, arg_r = flag_spec.ParseCmdVal('eval', cmd_val)

        if self.exec_opts.simple_eval_builtin():
            code_str, eval_spid = arg_r.ReadRequired2('requires code string')
            if not arg_r.AtEnd():
                e_usage('requires exactly 1 argument')
        else:
            code_str = ' '.join(arg_r.Rest())
            # code_str could be EMPTY, so just use the first one
            eval_spid = cmd_val.arg_spids[0]

        line_reader = reader.StringLineReader(code_str, self.arena)
        c_parser = self.parse_ctx.MakeOshParser(line_reader)

        src = source.EvalArg(eval_spid)
        with dev.ctx_Tracer(self.tracer, 'eval', None):
            with alloc.ctx_Location(self.arena, src):
                return main_loop.Batch(self.cmd_ev,
                                       c_parser,
                                       self.arena,
                                       cmd_flags=cmd_eval.IsEvalSource)
Example #4
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        call_spid = cmd_val.arg_spids[0]
        _, arg_r = flag_spec.ParseCmdVal('source', cmd_val)

        path = arg_r.Peek()
        if path is None:
            e_usage('missing required argument')
        arg_r.Next()

        resolved = self.search_path.Lookup(path, exec_required=False)
        if resolved is None:
            resolved = path
        try:
            f = self.fd_state.Open(resolved)  # Shell can't use descriptors 3-9
        except OSError as e:
            self.errfmt.Print_('source %r failed: %s' %
                               (path, pyutil.strerror(e)),
                               span_id=cmd_val.arg_spids[1])
            return 1

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

            # A sourced module CAN have a new arguments array, but it always shares
            # the same variable scope as the caller.  The caller could be at either a
            # global or a local scope.
            source_argv = arg_r.Rest()
            self.mem.PushSource(path, source_argv)

            src = source.SourcedFile(path, call_spid)
            try:
                with alloc.ctx_Location(self.arena, src):
                    status = main_loop.Batch(self.cmd_ev,
                                             c_parser,
                                             self.arena,
                                             cmd_flags=cmd_eval.IsEvalSource)
            finally:
                self.mem.PopSource(source_argv)

            return status

        except _ControlFlow as e:
            if e.IsReturn():
                return e.StatusCode()
            else:
                raise
        finally:
            f.close()
Example #5
0
    def _UnsetVar(self, arg, spid, proc_fallback):
        # type: (str, int, bool) -> bool
        """
    Returns:
      bool: whether the 'unset' builtin should succeed with code 0.
    """
        arena = self.parse_ctx.arena
        a_parser = self.parse_ctx.MakeArithParser(arg)

        with alloc.ctx_Location(arena, source.ArgvWord(spid)):
            try:
                anode = a_parser.Parse()
            except error.Parse as e:
                ui.PrettyPrintError(e, arena)  # show parse error
                e_usage('Invalid unset expression', span_id=spid)

        lval = self.arith_ev.EvalArithLhs(anode, spid)

        # Prevent attacks like these by default:
        #
        # unset -v 'A["$(echo K; rm *)"]'
        if not self.exec_opts.eval_unsafe_arith(
        ) and lval.tag_() != lvalue_e.Named:
            e_usage(
                'expected a variable name.  shopt -s eval_unsafe_arith allows expressions',
                span_id=spid)

        #log('lval %s', lval)
        found = False
        try:
            # Note: This has 'setvar' semantics.  It could be 'setref' too?
            # So it composes?
            found = self.mem.Unset(lval, False)
        except error.Runtime as e:
            # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't
            # fail because it's a builtin.  So I guess the same is true of 'unset'.
            e.span_id = spid
            ui.PrettyPrintError(e, arena)
            return False

        if proc_fallback and not found:
            if arg in self.funcs:
                del self.funcs[arg]

        return True
Example #6
0
  def _ParseTrapCode(self, code_str):
    # type: (str) -> command_t
    """
    Returns:
      A node, or None if the code is invalid.
    """
    line_reader = reader.StringLineReader(code_str, self.arena)
    c_parser = self.parse_ctx.MakeOshParser(line_reader)

    # TODO: the SPID should be passed through argv.  Use ArgvWord?
    with alloc.ctx_Location(self.arena, source.Trap(runtime.NO_SPID)):
      try:
        node = main_loop.ParseWholeFile(c_parser)
      except error.Parse as e:
        ui.PrettyPrintError(e, self.arena)
        return None

    return node
Example #7
0
    def _Line(self, arg, var_name):
        # type: (arg_types.read, str) -> int
        line = _ReadLine()
        if len(line) == 0:  # EOF
            return 1

        if not arg.with_eol:
            if line.endswith('\r\n'):
                line = line[:-2]
            elif line.endswith('\n'):
                line = line[:-1]

        # Lines that don't start with a single quote aren't QSN.  They may contain
        # a single quote internally, like:
        #
        # Fool's Gold
        if arg.q and line.startswith("'"):
            arena = self.parse_ctx.arena
            line_reader = reader.StringLineReader(line, arena)
            lexer = self.parse_ctx._MakeLexer(line_reader)

            # The parser only yields valid tokens:
            #     Char_Literals, Char_OneChar, Char_Hex, Char_UBraced
            # So we can use word_compile.EvalCStringToken, which is also used for
            # $''.
            # Important: we don't generate Id.Unknown_Backslash because that is valid
            # in echo -e.  We just make it Id.Unknown_Tok?
            try:
                # TODO: read should know about stdin, and redirects, and pipelines?
                with alloc.ctx_Location(arena, source.Stdin('')):
                    tokens = qsn_native.Parse(lexer)
            except error.Parse as e:
                ui.PrettyPrintError(e, arena)
                return 1
            tmp = [word_compile.EvalCStringToken(t) for t in tokens]
            line = ''.join(tmp)

        lhs = lvalue.Named(var_name)
        self.mem.SetValue(lhs, value.Str(line), scope_e.LocalOnly)
        return 0
Example #8
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        """
    printf: printf [-v var] format [argument ...]
    """
        attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val)
        arg = arg_types.printf(attrs.attrs)

        fmt, fmt_spid = arg_r.ReadRequired2('requires a format string')
        varargs, spids = arg_r.Rest2()

        #log('fmt %s', fmt)
        #log('vals %s', vals)

        arena = self.parse_ctx.arena
        if fmt in self.parse_cache:
            parts = self.parse_cache[fmt]
        else:
            line_reader = reader.StringLineReader(fmt, arena)
            # TODO: Make public
            lexer = self.parse_ctx._MakeLexer(line_reader)
            parser = _FormatStringParser(lexer)

            with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)):
                try:
                    parts = parser.Parse()
                except error.Parse as e:
                    self.errfmt.PrettyPrintError(e)
                    return 2  # parse error

            self.parse_cache[fmt] = parts

        if 0:
            print()
            for part in parts:
                part.PrettyPrint()
                print()

        out = []  # type: List[str]
        arg_index = 0
        num_args = len(varargs)
        backslash_c = False

        while True:
            for part in parts:
                UP_part = part
                if part.tag_() == printf_part_e.Literal:
                    part = cast(printf_part__Literal, UP_part)
                    token = part.token
                    if token.id == Id.Format_EscapedPercent:
                        s = '%'
                    else:
                        s = word_compile.EvalCStringToken(token)
                    out.append(s)

                elif part.tag_() == printf_part_e.Percent:
                    part = cast(printf_part__Percent, UP_part)
                    flags = []  # type: List[str]
                    if len(part.flags) > 0:
                        for flag_token in part.flags:
                            flags.append(flag_token.val)

                    width = -1  # nonexistent
                    if part.width:
                        if part.width.id in (Id.Format_Num, Id.Format_Zero):
                            width_str = part.width.val
                            width_spid = part.width.span_id
                        elif part.width.id == Id.Format_Star:
                            if arg_index < num_args:
                                width_str = varargs[arg_index]
                                width_spid = spids[arg_index]
                                arg_index += 1
                            else:
                                width_str = ''  # invalid
                                width_spid = runtime.NO_SPID
                        else:
                            raise AssertionError()

                        try:
                            width = int(width_str)
                        except ValueError:
                            if width_spid == runtime.NO_SPID:
                                width_spid = part.width.span_id
                            self.errfmt.Print_("printf got invalid width %r" %
                                               width_str,
                                               span_id=width_spid)
                            return 1

                    precision = -1  # nonexistent
                    if part.precision:
                        if part.precision.id == Id.Format_Dot:
                            precision_str = '0'
                            precision_spid = part.precision.span_id
                        elif part.precision.id in (Id.Format_Num,
                                                   Id.Format_Zero):
                            precision_str = part.precision.val
                            precision_spid = part.precision.span_id
                        elif part.precision.id == Id.Format_Star:
                            if arg_index < num_args:
                                precision_str = varargs[arg_index]
                                precision_spid = spids[arg_index]
                                arg_index += 1
                            else:
                                precision_str = ''
                                precision_spid = runtime.NO_SPID
                        else:
                            raise AssertionError()

                        try:
                            precision = int(precision_str)
                        except ValueError:
                            if precision_spid == runtime.NO_SPID:
                                precision_spid = part.precision.span_id
                            self.errfmt.Print_(
                                'printf got invalid precision %r' %
                                precision_str,
                                span_id=precision_spid)
                            return 1

                    #log('index=%d n=%d', arg_index, num_args)
                    if arg_index < num_args:
                        s = varargs[arg_index]
                        word_spid = spids[arg_index]
                        arg_index += 1
                    else:
                        s = ''
                        word_spid = runtime.NO_SPID

                    typ = part.type.val
                    if typ == 's':
                        if precision >= 0:
                            s = s[:precision]  # truncate

                    elif typ == 'q':
                        s = qsn.maybe_shell_encode(s)

                    elif typ == 'b':
                        # Process just like echo -e, except \c handling is simpler.

                        c_parts = []  # type: List[str]
                        lex = match.EchoLexer(s)
                        while True:
                            id_, tok_val = lex.Next()
                            if id_ == Id.Eol_Tok:  # Note: This is really a NUL terminator
                                break

                            # TODO: add span_id from argv
                            tok = Token(id_, runtime.NO_SPID, tok_val)
                            p = word_compile.EvalCStringToken(tok)

                            # Unusual behavior: '\c' aborts processing!
                            if p is None:
                                backslash_c = True
                                break

                            c_parts.append(p)
                        s = ''.join(c_parts)

                    elif typ in 'diouxX' or part.type.id == Id.Format_Time:
                        try:
                            d = int(s)
                        except ValueError:
                            if len(s) >= 1 and s[0] in '\'"':
                                # TODO: utf-8 decode s[1:] to be more correct.  Probably
                                # depends on issue #366, a utf-8 library.
                                # Note: len(s) == 1 means there is a NUL (0) after the quote..
                                d = ord(s[1]) if len(s) >= 2 else 0
                            elif part.type.id == Id.Format_Time and len(
                                    s) == 0 and word_spid == runtime.NO_SPID:
                                # Note: No argument means -1 for %(...)T as in Bash Reference
                                #   Manual 4.2 "If no argument is specified, conversion behaves
                                #   as if -1 had been given."
                                d = -1
                            else:
                                if word_spid == runtime.NO_SPID:
                                    # Blame the format string
                                    blame_spid = part.type.span_id
                                else:
                                    blame_spid = word_spid
                                self.errfmt.Print_(
                                    'printf expected an integer, got %r' % s,
                                    span_id=blame_spid)
                                return 1

                        if typ in 'di':
                            s = str(d)
                        elif typ in 'ouxX':
                            if d < 0:
                                e_die(
                                    "Can't format negative number %d with %%%s",
                                    d,
                                    typ,
                                    span_id=part.type.span_id)
                            if typ == 'u':
                                s = str(d)
                            elif typ == 'o':
                                s = mylib.octal(d)
                            elif typ == 'x':
                                s = mylib.hex_lower(d)
                            elif typ == 'X':
                                s = mylib.hex_upper(d)

                        elif part.type.id == Id.Format_Time:
                            # %(...)T

                            # Initialize timezone:
                            #   `localtime' uses the current timezone information initialized
                            #   by `tzset'.  The function `tzset' refers to the environment
                            #   variable `TZ'.  When the exported variable `TZ' is present,
                            #   its value should be reflected in the real environment
                            #   variable `TZ' before call of `tzset'.
                            #
                            # Note: unlike LANG, TZ doesn't seem to change behavior if it's
                            # not exported.
                            #
                            # TODO: In Oil, provide an API that doesn't rely on libc's
                            # global state.

                            tzcell = self.mem.GetCell('TZ')
                            if tzcell and tzcell.exported and tzcell.val.tag_(
                            ) == value_e.Str:
                                tzval = cast(value__Str, tzcell.val)
                                posix.putenv('TZ', tzval.s)

                            time_.tzset()

                            # Handle special values:
                            #   User can specify two special values -1 and -2 as in Bash
                            #   Reference Manual 4.2: "Two special argument values may be
                            #   used: -1 represents the current time, and -2 represents the
                            #   time the shell was invoked." from
                            #   https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf
                            if d == -1:  # the current time
                                ts = time_.time()
                            elif d == -2:  # the shell start time
                                ts = self.shell_start_time
                            else:
                                ts = d

                            s = time_.strftime(typ[1:-2], time_.localtime(ts))
                            if precision >= 0:
                                s = s[:precision]  # truncate

                        else:
                            raise AssertionError()

                    else:
                        raise AssertionError()

                    if width >= 0:
                        if len(flags):
                            if '-' in flags:
                                s = s.ljust(width, ' ')
                            elif '0' in flags:
                                s = s.rjust(width, '0')
                            else:
                                pass
                        else:
                            s = s.rjust(width, ' ')

                    out.append(s)

                else:
                    raise AssertionError()

                if backslash_c:  # 'printf %b a\cb xx' - \c terminates processing!
                    break

            if arg_index >= num_args:
                break
            # Otherwise there are more args.  So cycle through the loop once more to
            # implement the 'arg recycling' behavior.

        result = ''.join(out)
        if arg.v is not None:
            # TODO: get the span_id for arg.v!
            v_spid = runtime.NO_SPID

            arena = self.parse_ctx.arena
            a_parser = self.parse_ctx.MakeArithParser(arg.v)

            with alloc.ctx_Location(arena, source.ArgvWord(v_spid)):
                try:
                    anode = a_parser.Parse()
                except error.Parse as e:
                    ui.PrettyPrintError(e, arena)  # show parse error
                    e_usage('Invalid -v expression', span_id=v_spid)

            lval = self.arith_ev.EvalArithLhs(anode, v_spid)

            if not self.exec_opts.eval_unsafe_arith(
            ) and lval.tag_() != lvalue_e.Named:
                e_usage(
                    '-v expected a variable name.  shopt -s eval_unsafe_arith allows expressions',
                    span_id=v_spid)

            state.SetRef(self.mem, lval, value.Str(result))
        else:
            mylib.Stdout().write(result)
        return 0
Example #9
0
  def _StringToInteger(self, s, span_id=runtime.NO_SPID):
    # type: (str, int) -> int
    """Use bash-like rules to coerce a string to an integer.

    Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13

    0xAB -- hex constant
    042  -- octal constant
    42   -- decimal constant
    64#z -- arbitary base constant

    bare word: variable
    quoted word: string (not done?)
    """
    if s.startswith('0x'):
      try:
        integer = int(s, 16)
      except ValueError:
        e_strict('Invalid hex constant %r', s, span_id=span_id)
      return integer

    if s.startswith('0'):
      try:
        integer = int(s, 8)
      except ValueError:
        e_strict('Invalid octal constant %r', s, span_id=span_id)
      return integer

    if '#' in s:
      b, digits = mylib.split_once(s, '#')
      try:
        base = int(b)
      except ValueError:
        e_strict('Invalid base for numeric constant %r',  b, span_id=span_id)

      integer = 0
      for ch in digits:
        if IsLower(ch):
          digit = ord(ch) - ord('a') + 10
        elif IsUpper(ch):
          digit = ord(ch) - ord('A') + 36
        elif ch == '@':  # horrible syntax
          digit = 62
        elif ch == '_':
          digit = 63
        elif ch.isdigit():
          digit = int(ch)
        else:
          e_strict('Invalid digits for numeric constant %r', digits, span_id=span_id)

        if digit >= base:
          e_strict('Digits %r out of range for base %d', digits, base, span_id=span_id)

        integer = integer * base + digit
      return integer

    try:
      # Normal base 10 integer.  This includes negative numbers like '-42'.
      integer = int(s)
    except ValueError:
      # doesn't look like an integer

      # note: 'test' and '[' never evaluate recursively
      if self.exec_opts.eval_unsafe_arith() and self.parse_ctx:
        # Special case so we don't get EOF error
        if len(s.strip()) == 0:
          return 0

        # For compatibility: Try to parse it as an expression and evaluate it.

        arena = self.parse_ctx.arena

        a_parser = self.parse_ctx.MakeArithParser(s)
        with alloc.ctx_Location(arena, source.Variable(span_id)):
          try:
            node2 = a_parser.Parse()  # may raise error.Parse
          except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            e_die('Parse error in recursive arithmetic', span_id=e.span_id)

        # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
        # to itself, and you don't want to reparse it as a word.
        if node2.tag_() == arith_expr_e.Word:
          e_die("Invalid integer constant %r", s, span_id=span_id)
        else:
          integer = self.EvalToInt(node2)
      else:
        if len(s.strip()) == 0 or match.IsValidVarName(s):
          # x42 could evaluate to 0
          e_strict("Invalid integer constant %r", s, span_id=span_id)
        else:
          # 42x is always fatal!
          e_die("Invalid integer constant %r", s, span_id=span_id)

    return integer
Example #10
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        """
    printf: printf [-v var] format [argument ...]
    """
        attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val)
        arg = arg_types.printf(attrs.attrs)

        fmt, fmt_spid = arg_r.ReadRequired2('requires a format string')
        varargs, spids = arg_r.Rest2()

        #log('fmt %s', fmt)
        #log('vals %s', vals)

        arena = self.parse_ctx.arena
        if fmt in self.parse_cache:
            parts = self.parse_cache[fmt]
        else:
            line_reader = reader.StringLineReader(fmt, arena)
            # TODO: Make public
            lexer = self.parse_ctx._MakeLexer(line_reader)
            parser = _FormatStringParser(lexer)

            with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)):
                try:
                    parts = parser.Parse()
                except error.Parse as e:
                    self.errfmt.PrettyPrintError(e)
                    return 2  # parse error

            self.parse_cache[fmt] = parts

        if 0:
            print()
            for part in parts:
                part.PrettyPrint()
                print()

        out = []  # type: List[str]
        status = self._Format(parts, varargs, spids, out)
        if status != 0:
            return status  # failure

        result = ''.join(out)
        if arg.v is not None:
            # TODO: get the span_id for arg.v!
            v_spid = runtime.NO_SPID

            arena = self.parse_ctx.arena
            a_parser = self.parse_ctx.MakeArithParser(arg.v)

            with alloc.ctx_Location(arena, source.ArgvWord(v_spid)):
                try:
                    anode = a_parser.Parse()
                except error.Parse as e:
                    ui.PrettyPrintError(e, arena)  # show parse error
                    e_usage('Invalid -v expression', span_id=v_spid)

            lval = self.arith_ev.EvalArithLhs(anode, v_spid)

            if not self.exec_opts.eval_unsafe_arith(
            ) and lval.tag_() != lvalue_e.Named:
                e_usage(
                    '-v expected a variable name.  shopt -s eval_unsafe_arith allows expressions',
                    span_id=v_spid)

            state.SetRef(self.mem, lval, value.Str(result))
        else:
            mylib.Stdout().write(result)
        return 0