OILS / builtin / private_ysh.py View on Github | oils.pub

220 lines, 129 significant
1from __future__ import print_function
2
3from errno import EINTR, ENOENT
4import time as time_
5
6from _devbuild.gen import arg_types
7from core import error
8from core.error import e_die_status
9from core import pyos
10from core import pyutil
11from core import vm
12from display import ui
13from frontend import flag_util
14from mycpp import iolib
15from mycpp import mylib
16from mycpp.mylib import STDIN_FILENO, log
17
18import libc
19import posix_ as posix
20from posix_ import O_RDONLY
21
22_ = log
23
24from typing import List, TYPE_CHECKING
25if TYPE_CHECKING:
26 from _devbuild.gen.runtime_asdl import cmd_value
27 from osh import cmd_eval
28
29
30class Cat(vm._Builtin):
31 """Internal implementation detail for $(< file)."""
32
33 def __init__(self, errfmt):
34 # type: (ui.ErrorFormatter) -> None
35 vm._Builtin.__init__(self)
36 self.errfmt = errfmt
37 self.stdout_ = mylib.Stdout()
38
39 def _CatFile(self, fd):
40 # type: (int) -> int
41
42 chunks = [] # type: List[str]
43 while True:
44 n, err_num = pyos.Read(fd, 4096, chunks)
45
46 if n < 0:
47 if err_num == EINTR:
48 # Note: When running external cat, shells don't run traps
49 # until after cat is done. It seems like they could? In
50 # any case, we don't do it here, which makes it
51 # inconsistent with 'builtin sleep'. 'sleep' was modelled
52 # after 'read -t N'. Could have shopt for this? Maybe fix
53 # it in YSH?
54
55 pass # retry
56 else:
57 # Like the top level IOError handler
58 e_die_status(
59 2, 'oils I/O error: %s' % posix.strerror(err_num))
60 # TODO: Maybe just return 1?
61
62 elif n == 0: # EOF
63 break
64
65 else:
66 # Stream it to stdout
67 assert len(chunks) == 1
68 self.stdout_.write(chunks[0])
69 chunks.pop()
70
71 return 0
72
73 def Run(self, cmd_val):
74 # type: (cmd_value.Argv) -> int
75 _, arg_r = flag_util.ParseCmdVal('cat', cmd_val)
76
77 argv, locs = arg_r.Rest2()
78 #log('argv %r', argv)
79
80 if len(argv) == 0:
81 return self._CatFile(STDIN_FILENO)
82
83 status = 0
84 for i, path in enumerate(argv):
85 if path == '-':
86 st = self._CatFile(STDIN_FILENO)
87 if st != 0:
88 status = st
89 continue
90
91 # 0o666 is affected by umask, all shells use it.
92 opened = False
93 try:
94 my_fd = posix.open(path, O_RDONLY, 0)
95 opened = True
96 except (IOError, OSError) as e:
97 self.errfmt.Print_("Can't open %r: %s" %
98 (path, pyutil.strerror(e)),
99 blame_loc=locs[i])
100 status = 1
101
102 if opened:
103 st = self._CatFile(my_fd)
104 posix.close(my_fd)
105 if st != 0:
106 status = st
107
108 return status
109
110
111class Rm(vm._Builtin):
112
113 def __init__(self, errfmt):
114 # type: (ui.ErrorFormatter) -> None
115 vm._Builtin.__init__(self)
116 self.errfmt = errfmt
117
118 def Run(self, cmd_val):
119 # type: (cmd_value.Argv) -> int
120 attrs, arg_r = flag_util.ParseCmdVal('rm', cmd_val)
121 arg = arg_types.rm(attrs.attrs)
122
123 argv, locs = arg_r.Rest2()
124 #log('argv %r', argv)
125
126 if not arg.f and len(argv) == 0:
127 raise error.Usage('expected one or more files',
128 cmd_val.arg_locs[0])
129
130 status = 0
131 for i, path in enumerate(argv):
132 err_num = pyos.Unlink(path)
133
134 # -f ignores nonexistent files
135 if arg.f and err_num == ENOENT:
136 continue
137
138 if err_num != 0:
139 self.errfmt.Print_("Can't remove %r: %s" %
140 (path, posix.strerror(err_num)),
141 blame_loc=locs[i])
142 status = 1
143
144 return status
145
146
147class Sleep(vm._Builtin):
148 """Similar to external sleep, but runs pending traps.
149
150 It's related to bash 'read -t 5', which also runs pending traps.
151
152 There is a LARGE test matrix, see test/manual.sh
153
154 Untrapped SIGWINCH, SIGUSR1, ... - Ignore, but run PENDING TRAPS
155 Trapped SIGWINCH, SIGUSR1, ... - Run trap handler
156
157 Untrapped SIGINT / Ctrl-C
158 Interactive: back to prompt
159 Non-interactive: abort shell interpreter
160 Trapped SIGINT - Run trap handler
161 """
162
163 def __init__(self, cmd_ev, signal_safe):
164 # type: (cmd_eval.CommandEvaluator, iolib.SignalSafe) -> None
165 vm._Builtin.__init__(self)
166 self.cmd_ev = cmd_ev
167 self.signal_safe = signal_safe
168
169 def Run(self, cmd_val):
170 # type: (cmd_value.Argv) -> int
171 _, arg_r = flag_util.ParseCmdVal('sleep', cmd_val)
172
173 # Only supports integral seconds
174 # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html
175
176 duration, duration_loc = arg_r.Peek2()
177 if duration is None:
178 raise error.Usage('expected a number of seconds',
179 cmd_val.arg_locs[0])
180 arg_r.Next()
181 arg_r.Done()
182
183 msg = 'got invalid number of seconds %r' % duration
184 try:
185 total_seconds = float(duration)
186 except ValueError:
187 raise error.Usage(msg, duration_loc)
188
189 if total_seconds < 0:
190 raise error.Usage(msg, duration_loc)
191
192 # time_.time() is inaccurate!
193 deadline = time_.time() + total_seconds
194 secs = total_seconds # initial value is the total
195 while True:
196 err_num = libc.sleep_until_error(secs)
197 if err_num == 0:
198 # log('finished sleeping')
199 break
200 elif err_num == EINTR:
201 # log('EINTR')
202
203 # e.g. Run traps on SIGWINCH, and keep going
204 self.cmd_ev.RunPendingTraps()
205
206 if self.signal_safe.PollUntrappedSigInt():
207 # Ctrl-C aborts in non-interactive mode
208 # log('KeyboardInterrupt')
209 raise KeyboardInterrupt()
210 else:
211 # Abort sleep on other errors (should be rare)
212 break
213
214 # how much time is left?
215 secs = deadline - time_.time()
216 # log('continue sleeping %s', str(secs))
217 if secs <= 0: # only pass positive values to sleep_until_error()
218 break
219
220 return 0