| 1 | #!/usr/bin/env python2
 | 
| 2 | """
 | 
| 3 | oils_for_unix.py - A busybox-like binary for OSH and YSH (formerly Oil).
 | 
| 4 | 
 | 
| 5 | This is the main program that is translated to C++ by mycpp.
 | 
| 6 | 
 | 
| 7 | Based on argv[0], it acts like a few different programs.
 | 
| 8 | - true, false
 | 
| 9 | - readlink
 | 
| 10 | 
 | 
| 11 | We could could also expose some other binaries for a smaller POSIX system:
 | 
| 12 | 
 | 
| 13 | - test / '['
 | 
| 14 | - printf, echo
 | 
| 15 | - cat
 | 
| 16 | - 'time' -- different usage
 | 
| 17 | """
 | 
| 18 | from __future__ import print_function
 | 
| 19 | 
 | 
| 20 | import posix_ as posix
 | 
| 21 | import sys
 | 
| 22 | 
 | 
| 23 | from _devbuild.gen.syntax_asdl import loc, CompoundWord
 | 
| 24 | from core import error
 | 
| 25 | from core import shell
 | 
| 26 | from core import pyos
 | 
| 27 | from core import pyutil
 | 
| 28 | from core import util
 | 
| 29 | from frontend import args
 | 
| 30 | from frontend import py_readline
 | 
| 31 | from mycpp import mylib
 | 
| 32 | from mycpp.mylib import print_stderr, log
 | 
| 33 | from pylib import os_path
 | 
| 34 | 
 | 
| 35 | if mylib.PYTHON:
 | 
| 36 |     from tools import readlink
 | 
| 37 | 
 | 
| 38 | import fanos
 | 
| 39 | 
 | 
| 40 | from typing import List
 | 
| 41 | 
 | 
| 42 | 
 | 
| 43 | def CaperDispatch():
 | 
| 44 |     # type: () -> int
 | 
| 45 |     log('Running Oil in ---caper mode')
 | 
| 46 |     fd_out = []  # type: List[int]
 | 
| 47 |     while True:
 | 
| 48 |         try:
 | 
| 49 |             msg = fanos.recv(0, fd_out)
 | 
| 50 |         except ValueError as e:
 | 
| 51 |             # TODO: recv() needs to detect EOF condition.  Should it return ''
 | 
| 52 |             # like sys.stdin.readline(), or something else?
 | 
| 53 |             # Should that be distinguished from '0:,' ?   with None perhaps?
 | 
| 54 |             log('FANOS error: %s', e)
 | 
| 55 |             fanos.send(1, 'ERROR %s' % e)
 | 
| 56 |             continue
 | 
| 57 | 
 | 
| 58 |         log('msg = %r', msg)
 | 
| 59 | 
 | 
| 60 |         command, arg = mylib.split_once(msg, ' ')
 | 
| 61 |         if command == 'GETPID':
 | 
| 62 |             pass
 | 
| 63 |         elif command == 'CHDIR':
 | 
| 64 |             pass
 | 
| 65 |         elif command == 'SETENV':
 | 
| 66 |             pass
 | 
| 67 |         elif command == 'MAIN':
 | 
| 68 |             #argv = ['TODO']
 | 
| 69 |             # I think we need to factor the oil.{py,ovm} condition out and call it like this:
 | 
| 70 |             # MainDispatch(main_name, argv) or
 | 
| 71 |             # MainDispatch(main_name, arg_r)
 | 
| 72 |             pass
 | 
| 73 | 
 | 
| 74 |         # fanos.send(1, reply)
 | 
| 75 | 
 | 
| 76 |     return 0  # Does this fail?
 | 
| 77 | 
 | 
| 78 | 
 | 
| 79 | # TODO: Hook up valid applets (including these) to completion
 | 
| 80 | # APPLETS = ['osh', 'ysh', 'oil', 'readlink', 'true', 'false']
 | 
| 81 | 
 | 
| 82 | 
 | 
| 83 | def AppBundleMain(argv):
 | 
| 84 |     # type: (List[str]) -> int
 | 
| 85 | 
 | 
| 86 |     # NOTE: This has a side effect of deleting _OVM_* from the environment!
 | 
| 87 |     loader = pyutil.GetResourceLoader()
 | 
| 88 | 
 | 
| 89 |     b = os_path.basename(argv[0])
 | 
| 90 |     main_name, ext = os_path.splitext(b)
 | 
| 91 | 
 | 
| 92 |     missing = None  # type: CompoundWord
 | 
| 93 |     arg_r = args.Reader(argv, locs=[missing] * len(argv))
 | 
| 94 | 
 | 
| 95 |     login_shell = False
 | 
| 96 | 
 | 
| 97 |     # Are we running the C++ bundle or the Python bundle directly, without a
 | 
| 98 |     # symlink?
 | 
| 99 |     if mylib.PYTHON:
 | 
| 100 |         bundle = 'oils_for_unix'  # bin/oils_for_unix.py
 | 
| 101 |     else:
 | 
| 102 |         bundle = 'oils-for-unix'  # _bin/cxx-dbg/oils-for-unix
 | 
| 103 | 
 | 
| 104 |     # for legacy oil.ovm
 | 
| 105 |     if main_name == bundle or (main_name == 'oil' and len(ext)):
 | 
| 106 |         arg_r.Next()
 | 
| 107 |         first_arg = arg_r.Peek()
 | 
| 108 |         if first_arg is None:
 | 
| 109 |             raise error.Usage('Missing required applet name.', loc.Missing)
 | 
| 110 | 
 | 
| 111 |         # Special flags to the top level binary: bin/oil.py --help, ---caper, etc.
 | 
| 112 |         if first_arg in ('-h', '--help'):
 | 
| 113 |             util.HelpFlag(loader, 'oils-usage', mylib.Stdout())
 | 
| 114 |             return 0
 | 
| 115 | 
 | 
| 116 |         if first_arg in ('-V', '--version'):
 | 
| 117 |             util.VersionFlag(loader, mylib.Stdout())
 | 
| 118 |             return 0
 | 
| 119 | 
 | 
| 120 |         # This has THREE dashes since it isn't a normal flag
 | 
| 121 |         if first_arg == '---caper':
 | 
| 122 |             return CaperDispatch()
 | 
| 123 | 
 | 
| 124 |         applet = first_arg
 | 
| 125 |     else:
 | 
| 126 |         applet = main_name
 | 
| 127 | 
 | 
| 128 |         if applet.startswith('-'):
 | 
| 129 |             login_shell = True
 | 
| 130 |             applet = applet[1:]
 | 
| 131 | 
 | 
| 132 |     readline = py_readline.MaybeGetReadline()
 | 
| 133 | 
 | 
| 134 |     environ = pyos.Environ()
 | 
| 135 | 
 | 
| 136 |     if applet in ('ysh', 'oil'):
 | 
| 137 |         return shell.Main('ysh', arg_r, environ, login_shell, loader, readline)
 | 
| 138 | 
 | 
| 139 |     elif applet.endswith('sh'):  # sh, osh, bash imply OSH
 | 
| 140 |         return shell.Main('osh', arg_r, environ, login_shell, loader, readline)
 | 
| 141 | 
 | 
| 142 |     # For testing latency
 | 
| 143 |     elif applet == 'true':
 | 
| 144 |         return 0
 | 
| 145 |     elif applet == 'false':
 | 
| 146 |         return 1
 | 
| 147 |     elif applet == 'readlink':
 | 
| 148 |         if mylib.PYTHON:
 | 
| 149 |             # TODO: Move this to 'internal readlink' (issue #1013)
 | 
| 150 |             main_argv = arg_r.Rest()
 | 
| 151 |             return readlink.main(main_argv)
 | 
| 152 |         else:
 | 
| 153 |             print_stderr('readlink not translated')
 | 
| 154 |             return 2
 | 
| 155 | 
 | 
| 156 |     else:
 | 
| 157 |         raise error.Usage("Invalid applet %r" % applet, loc.Missing)
 | 
| 158 | 
 | 
| 159 | 
 | 
| 160 | def main(argv):
 | 
| 161 |     # type: (List[str]) -> int
 | 
| 162 | 
 | 
| 163 |     if mylib.PYTHON:
 | 
| 164 |         if not pyutil.IsAppBundle():
 | 
| 165 |             # For unmodified Python interpreters to simulate the OVM_MAIN patch
 | 
| 166 |             import libc
 | 
| 167 |             libc.cpython_reset_locale()
 | 
| 168 | 
 | 
| 169 |     try:
 | 
| 170 |         return AppBundleMain(argv)
 | 
| 171 | 
 | 
| 172 |     except error.Usage as e:
 | 
| 173 |         #builtin.Help(['oil-usage'], util.GetResourceLoader())
 | 
| 174 |         log('oils: %s', e.msg)
 | 
| 175 |         return 2
 | 
| 176 | 
 | 
| 177 |     except KeyboardInterrupt:
 | 
| 178 |         print('')
 | 
| 179 |         return 130  # 128 + 2
 | 
| 180 | 
 | 
| 181 |     except (IOError, OSError) as e:
 | 
| 182 |         if 0:
 | 
| 183 |             import traceback
 | 
| 184 |             traceback.print_exc()
 | 
| 185 | 
 | 
| 186 |         # test this with prlimit --nproc=1 --pid=$$
 | 
| 187 |         print_stderr('oils I/O error (main): %s' % posix.strerror(e.errno))
 | 
| 188 | 
 | 
| 189 |         # dash gives status 2.  Consider changing: it conflicts a bit with
 | 
| 190 |         # usage errors.
 | 
| 191 |         return 2
 | 
| 192 | 
 | 
| 193 |     # We don't catch RuntimeError (including AssertionError/NotImplementedError),
 | 
| 194 |     # because those are simply bugs, and we want to see a Python stack trace.
 | 
| 195 | 
 | 
| 196 | 
 | 
| 197 | if __name__ == '__main__':
 | 
| 198 |     sys.exit(main(sys.argv))
 |