OILS / bin / oils_for_unix.py View on Github | oilshell.org

198 lines, 109 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 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
160def 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
197if __name__ == '__main__':
198 sys.exit(main(sys.argv))