1 | from __future__ import print_function
|
2 |
|
3 | from errno import EINTR, ENOENT
|
4 | import time as time_
|
5 |
|
6 | from _devbuild.gen import arg_types
|
7 | from core import error
|
8 | from core.error import e_die_status
|
9 | from core import pyos
|
10 | from core import pyutil
|
11 | from core import vm
|
12 | from display import ui
|
13 | from frontend import flag_util
|
14 | from mycpp import iolib
|
15 | from mycpp import mylib
|
16 | from mycpp.mylib import STDIN_FILENO, log
|
17 |
|
18 | import libc
|
19 | import posix_ as posix
|
20 | from posix_ import O_RDONLY
|
21 |
|
22 | _ = log
|
23 |
|
24 | from typing import List, TYPE_CHECKING
|
25 | if TYPE_CHECKING:
|
26 | from _devbuild.gen.runtime_asdl import cmd_value
|
27 | from osh import cmd_eval
|
28 |
|
29 |
|
30 | class 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 |
|
111 | class 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 |
|
147 | class 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
|