| 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
|