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

212 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 oils-ref.ovm
105 if main_name == bundle or (main_name == 'oils-ref' 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'):
137 return shell.Main('ysh', arg_r, environ, login_shell, loader, readline)
138
139 # sh, osh, bash imply OSH
140 elif applet.startswith('osh') or applet.endswith('sh'):
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
149 # TODO:
150 # - private builtins:
151 # - sleep, rm, cat
152 # - Others
153 # - printf, test
154 # - time keyword can change to something that outputs JSON / QTT8
155 # - readlink should be a builtin
156 #
157 # So then arg_locs - loc_t.Argument?
158 elif applet == 'readlink':
159 if mylib.PYTHON:
160 # TODO: Make this 'builtin readlink'
161 main_argv = arg_r.Rest()
162 return readlink.main(main_argv)
163 else:
164 print_stderr('readlink not translated')
165 return 2
166
167 else:
168 raise error.Usage("Invalid applet %r" % applet, loc.Missing)
169
170
171def main(argv):
172 # type: (List[str]) -> int
173
174 if mylib.PYTHON:
175 if not pyutil.IsAppBundle():
176 # For unmodified Python interpreters to simulate the OVM_MAIN patch
177 import libc
178 libc.cpython_reset_locale()
179
180 try:
181 return AppBundleMain(argv)
182
183 except error.Usage as e:
184 #builtin.Help(['oil-usage'], util.GetResourceLoader())
185 log('oils: %s', e.msg)
186 return 2
187
188 except KeyboardInterrupt:
189 # The interactive shell and the batch shell both handle
190 # KeyboardInterrupt themselves.
191 # This is a catch-all for --tool and so forth.
192 print('')
193 return 130 # 128 + 2
194
195 except (IOError, OSError) as e:
196 if 0:
197 import traceback
198 traceback.print_exc()
199
200 # test this with prlimit --nproc=1 --pid=$$
201 print_stderr('oils I/O error (main): %s' % posix.strerror(e.errno))
202
203 # dash gives status 2. Consider changing: it conflicts a bit with
204 # usage errors.
205 return 2
206
207 # We don't catch RuntimeError (including AssertionError/NotImplementedError),
208 # because those are simply bugs, and we want to see a Python stack trace.
209
210
211if __name__ == '__main__':
212 sys.exit(main(sys.argv))