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

263 lines, 137 significant
1#!/usr/bin/env python2
2"""oils_for_unix.py - A busybox-like binary for OSH and YSH
3
4This is the main program that is translated to C++ by mycpp.
5
6Based on argv[0], it acts like a few different programs.
7- true, false
8- readlink
9
10We could could also expose some other binaries for a smaller POSIX system:
11
12- test / '['
13- printf, echo
14- 'time' -- different usage
15- private builtin cat, rm
16"""
17from __future__ import print_function
18
19# We only use the low-level _locale.{setlocale,nl_langinfo} functions
20# Lib/locale.py has string wrappers that we don't need, and it imports
21# 'encodings', which is not in the oils-ref tarball
22from pylib import pylocale
23import sys
24
25from _devbuild.gen.syntax_asdl import loc, CompoundWord
26from core import error
27from core import shell
28from core import pyos
29from core import pyutil
30from core import util
31from frontend import args
32from frontend import match
33from frontend import py_readline
34from mycpp import mylib
35from mycpp.mylib import print_stderr, log
36from pylib import os_path
37
38if mylib.PYTHON:
39 from tools import readlink
40
41import fanos
42import posix_ as posix
43
44from typing import List, Dict
45
46
47def CaperDispatch():
48 # type: () -> int
49 log('Running Oils in ---caper mode')
50 fd_out = [] # type: List[int]
51 while True:
52 try:
53 msg = fanos.recv(0, fd_out)
54 except ValueError as e:
55 # TODO: recv() needs to detect EOF condition. Should it return ''
56 # like sys.stdin.readline(), or something else?
57 # Should that be distinguished from '0:,' ? with None perhaps?
58 log('FANOS error: %s', e)
59 fanos.send(1, 'ERROR %s' % e)
60 continue
61
62 log('msg = %r', msg)
63
64 command, arg = mylib.split_once(msg, ' ')
65 if command == 'GETPID':
66 pass
67 elif command == 'CHDIR':
68 pass
69 elif command == 'SETENV':
70 pass
71 elif command == 'MAIN':
72 #argv = ['TODO']
73 # I think we need to factor the oil.{py,ovm} condition out and call it like this:
74 # MainDispatch(main_name, argv) or
75 # MainDispatch(main_name, arg_r)
76 pass
77
78 # fanos.send(1, reply)
79
80 return 0 # Does this fail?
81
82
83def InitLocale(environ, lang):
84 # type: (Dict[str, str], str) -> None
85 """Set the GLOBAL libc locale from the env, and CHECK that it's valid."""
86 try:
87 # NOTE: We don't pass LC_ALL because that implies LC_NUMERIC. This
88 # messes up mycpp functions to_float() and str(double d).
89 # So unlike bash, numbers aren't localized.
90
91 # We need LC_CTYPE in YSH for so libc functions respect UTF-8.
92 locale_name = pylocale.setlocale(pylocale.LC_CTYPE, '')
93
94 except pylocale.Error:
95 # we could make this nicer
96 print_stderr('oils: setlocale(LC_CTYPE) failed')
97 locale_name = '' # unknown value
98 #log('LOC %s', locale_name)
99
100 if locale_name not in ('', 'C'):
101 # Check if the codeset is UTF-8, unless OILS_LOCALE_OK=1 is set
102 if environ.get('OILS_LOCALE_OK') == '1':
103 return
104
105 codeset = pylocale.nl_langinfo(pylocale.CODESET)
106 #log('codeset %s', codeset)
107
108 if not match.IsUtf8Codeset(codeset):
109 print_stderr("oils warning: codeset %r doesn't look like UTF-8" %
110 codeset)
111 print_stderr(' OILS_LOCALE_OK=1 removes this message')
112
113 assert lang in ('osh', 'ysh'), lang
114
115 if lang == 'osh':
116 try:
117 # On the other hand, we respect LC_COLLATE in OSH, so the glob sort
118 # order matches bash and zsh.
119 pylocale.setlocale(pylocale.LC_COLLATE, '')
120 except pylocale.Error:
121 # we could make this nicer
122 print_stderr('oils: setlocale(LC_COLLATE) failed')
123
124
125# TODO: Hook up valid applets (including these) to completion
126# APPLETS = ['osh', 'ysh', 'oil', 'readlink', 'true', 'false']
127
128
129def AppBundleMain(argv):
130 # type: (List[str]) -> int
131
132 # NOTE: This has a side effect of deleting _OVM_* from the environment!
133 loader = pyutil.GetResourceLoader()
134
135 b = os_path.basename(argv[0])
136 main_name, ext = os_path.splitext(b)
137
138 missing = None # type: CompoundWord
139 arg_r = args.Reader(argv, locs=[missing] * len(argv))
140
141 login_shell = False
142
143 # Are we running the C++ bundle or the Python bundle directly, without a
144 # symlink?
145 if mylib.PYTHON:
146 bundle = 'oils_for_unix' # bin/oils_for_unix.py
147 else:
148 bundle = 'oils-for-unix' # _bin/cxx-dbg/oils-for-unix
149
150 # oils-for-unix-static is allowed
151 # oils-ref.ovm
152 if main_name.startswith(bundle) or (main_name == 'oils-ref' and len(ext)):
153 arg_r.Next()
154 first_arg = arg_r.Peek()
155 if first_arg is None:
156 raise error.Usage('Missing required applet name.', loc.Missing)
157
158 # Special flags to the top level binary: bin/oil.py --help, ---caper, etc.
159 if first_arg in ('-h', '--help'):
160 util.HelpFlag(loader, 'oils-usage', mylib.Stdout())
161 return 0
162
163 if first_arg in ('-V', '--version'):
164 util.VersionFlag(loader, mylib.Stdout())
165 return 0
166
167 # This has THREE dashes since it isn't a normal flag
168 if first_arg == '---caper':
169 return CaperDispatch()
170
171 applet = first_arg
172 else:
173 applet = main_name
174
175 if applet.startswith('-'):
176 login_shell = True
177 applet = applet[1:]
178
179 readline = py_readline.MaybeGetReadline()
180
181 environ = pyos.Environ()
182
183 if applet.startswith('ysh'):
184 lang = 'ysh'
185 InitLocale(environ, lang)
186 return shell.Main(lang, arg_r, environ, login_shell, loader, readline)
187
188 # sh, osh, bash imply OSH
189 elif applet.startswith('osh') or applet.endswith('sh'):
190 lang = 'osh'
191 InitLocale(environ, lang)
192 return shell.Main(lang,
193 arg_r,
194 environ,
195 login_shell,
196 loader,
197 readline,
198 bash_compat=(applet == 'bash'))
199
200 # For testing latency
201 elif applet == 'true':
202 return 0
203 elif applet == 'false':
204 return 1
205
206 # TODO:
207 # - private builtins:
208 # - sleep, rm, cat
209 # - Others
210 # - printf, test
211 # - time keyword can change to something that outputs JSON / QTT8
212 # - readlink should be a builtin
213 #
214 # So then arg_locs - loc_t.Argument?
215 elif applet == 'readlink':
216 if mylib.PYTHON:
217 # TODO: Make this 'builtin readlink'
218 main_argv = arg_r.Rest()
219 return readlink.main(main_argv)
220 else:
221 print_stderr('readlink not translated')
222 return 2
223
224 else:
225 raise error.Usage("Invalid applet %r" % applet, loc.Missing)
226
227
228def main(argv):
229 # type: (List[str]) -> int
230
231 try:
232 return AppBundleMain(argv)
233
234 except error.Usage as e:
235 #builtin.Help(['oil-usage'], util.GetResourceLoader())
236 log('oils: %s', e.msg)
237 return 2
238
239 except KeyboardInterrupt:
240 # The interactive shell and the batch shell both handle
241 # KeyboardInterrupt themselves.
242 # This is a catch-all for --tool and so forth.
243 print('')
244 return 130 # 128 + 2
245
246 except (IOError, OSError) as e:
247 if 0:
248 import traceback
249 traceback.print_exc()
250
251 # test this with prlimit --nproc=1 --pid=$$
252 print_stderr('oils I/O error (main): %s' % posix.strerror(e.errno))
253
254 # dash gives status 2. Consider changing: it conflicts a bit with
255 # usage errors.
256 return 2
257
258 # We don't catch RuntimeError (including AssertionError/NotImplementedError),
259 # because those are simply bugs, and we want to see a Python stack trace.
260
261
262if __name__ == '__main__':
263 sys.exit(main(sys.argv))