OILS / bin / oils_for_unix.py View on Github | oils.pub

202 lines, 110 significant
1#!/usr/bin/env python2
2"""
3oils_for_unix.py - A busybox-like binary for OSH and YSH (formerly Oil).
4
5This is the main program that is translated to C++ by mycpp.
6
7Based on argv[0], it acts like a few different programs.
8- true, false
9- readlink
10
11We 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"""
18from __future__ import print_function
19
20import posix_ as posix
21import sys
22
23from _devbuild.gen.syntax_asdl import loc, CompoundWord
24from core import error
25from core import shell
26from core import pyos
27from core import pyutil
28from core import util
29from frontend import args
30from frontend import py_readline
31from mycpp import mylib
32from mycpp.mylib import print_stderr, log
33from pylib import os_path
34
35if mylib.PYTHON:
36 from tools import readlink
37
38import fanos
39
40from typing import List
41
42
43def 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
83def 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.startswith('ysh') or applet == 'oil':
137 return shell.Main('ysh', arg_r, environ, login_shell, loader, readline)
138
139 elif applet.startswith('osh') or applet.endswith(
140 'sh'): # sh, osh, bash imply OSH
141 return shell.Main('osh', arg_r, environ, login_shell, loader, readline)
142
143 # For testing latency
144 elif applet == 'true':
145 return 0
146 elif applet == 'false':
147 return 1
148 elif applet == 'readlink':
149 if mylib.PYTHON:
150 # TODO: Move this to 'internal readlink' (issue #1013)
151 main_argv = arg_r.Rest()
152 return readlink.main(main_argv)
153 else:
154 print_stderr('readlink not translated')
155 return 2
156
157 else:
158 raise error.Usage("Invalid applet %r" % applet, loc.Missing)
159
160
161def main(argv):
162 # type: (List[str]) -> int
163
164 if mylib.PYTHON:
165 if not pyutil.IsAppBundle():
166 # For unmodified Python interpreters to simulate the OVM_MAIN patch
167 import libc
168 libc.cpython_reset_locale()
169
170 try:
171 return AppBundleMain(argv)
172
173 except error.Usage as e:
174 #builtin.Help(['oil-usage'], util.GetResourceLoader())
175 log('oils: %s', e.msg)
176 return 2
177
178 except KeyboardInterrupt:
179 # The interactive shell and the batch shell both handle
180 # KeyboardInterrupt themselves.
181 # This is a catch-all for --tool and so forth.
182 print('')
183 return 130 # 128 + 2
184
185 except (IOError, OSError) as e:
186 if 0:
187 import traceback
188 traceback.print_exc()
189
190 # test this with prlimit --nproc=1 --pid=$$
191 print_stderr('oils I/O error (main): %s' % posix.strerror(e.errno))
192
193 # dash gives status 2. Consider changing: it conflicts a bit with
194 # usage errors.
195 return 2
196
197 # We don't catch RuntimeError (including AssertionError/NotImplementedError),
198 # because those are simply bugs, and we want to see a Python stack trace.
199
200
201if __name__ == '__main__':
202 sys.exit(main(sys.argv))