| 1 | """
 | 
| 2 | pyos.py -- Wrappers for the operating system.
 | 
| 3 | 
 | 
| 4 | Like py{error,util}.py, it won't be translated to C++.
 | 
| 5 | """
 | 
| 6 | from __future__ import print_function
 | 
| 7 | 
 | 
| 8 | from errno import EINTR
 | 
| 9 | import pwd
 | 
| 10 | import resource
 | 
| 11 | import signal
 | 
| 12 | import select
 | 
| 13 | import sys
 | 
| 14 | import termios  # for read -n
 | 
| 15 | import time
 | 
| 16 | 
 | 
| 17 | from core import pyutil
 | 
| 18 | from mycpp import mops
 | 
| 19 | from mycpp.mylib import log
 | 
| 20 | 
 | 
| 21 | import posix_ as posix
 | 
| 22 | from posix_ import WUNTRACED
 | 
| 23 | 
 | 
| 24 | from typing import Optional, Tuple, List, Dict, cast, Any, TYPE_CHECKING
 | 
| 25 | if TYPE_CHECKING:
 | 
| 26 |     from core import error
 | 
| 27 | 
 | 
| 28 | _ = log
 | 
| 29 | 
 | 
| 30 | EOF_SENTINEL = 256  # bigger than any byte
 | 
| 31 | NEWLINE_CH = 10  # ord('\n')
 | 
| 32 | 
 | 
| 33 | 
 | 
| 34 | def FlushStdout():
 | 
| 35 |     # type: () -> Optional[error.IOError_OSError]
 | 
| 36 |     """Flush CPython buffers.
 | 
| 37 | 
 | 
| 38 |     Return error because we call this in a C++ destructor, and those can't
 | 
| 39 |     throw exceptions.
 | 
| 40 |     """
 | 
| 41 |     err = None  # type: Optional[error.IOError_OSError]
 | 
| 42 |     try:
 | 
| 43 |         sys.stdout.flush()
 | 
| 44 |     except (IOError, OSError) as e:
 | 
| 45 |         err = e
 | 
| 46 |     return err
 | 
| 47 | 
 | 
| 48 | 
 | 
| 49 | def WaitPid(waitpid_options):
 | 
| 50 |     # type: (int) -> Tuple[int, int]
 | 
| 51 |     """
 | 
| 52 |     Return value:
 | 
| 53 |       pid is 0 if WNOHANG passed, and nothing has changed state
 | 
| 54 |       status: value that can be parsed with WIFEXITED() etc.
 | 
| 55 |     """
 | 
| 56 |     try:
 | 
| 57 |         # Notes:
 | 
| 58 |         # - The arg -1 makes it like wait(), which waits for any process.
 | 
| 59 |         # - WUNTRACED is necessary to get stopped jobs.  What about WCONTINUED?
 | 
| 60 |         # - We don't retry on EINTR, because the 'wait' builtin should be
 | 
| 61 |         #   interruptible.
 | 
| 62 |         # - waitpid_options can be WNOHANG
 | 
| 63 |         pid, status = posix.waitpid(-1, WUNTRACED | waitpid_options)
 | 
| 64 |     except OSError as e:
 | 
| 65 |         return -1, e.errno
 | 
| 66 | 
 | 
| 67 |     return pid, status
 | 
| 68 | 
 | 
| 69 | 
 | 
| 70 | class ReadError(Exception):
 | 
| 71 |     """Wraps errno returned by read().
 | 
| 72 | 
 | 
| 73 |     Used by 'read' and 'mapfile' builtins.
 | 
| 74 |     """
 | 
| 75 | 
 | 
| 76 |     def __init__(self, err_num):
 | 
| 77 |         # type: (int) -> None
 | 
| 78 |         self.err_num = err_num
 | 
| 79 | 
 | 
| 80 | 
 | 
| 81 | def Read(fd, n, chunks):
 | 
| 82 |     # type: (int, int, List[str]) -> Tuple[int, int]
 | 
| 83 |     """C-style wrapper around Python's posix.read() that uses return values
 | 
| 84 |     instead of exceptions for errors.  We will implement this directly in C++
 | 
| 85 |     and not use exceptions at all.
 | 
| 86 | 
 | 
| 87 |     It reads n bytes from the given file descriptor and appends it to chunks.
 | 
| 88 | 
 | 
| 89 |     Returns:
 | 
| 90 |       (-1, errno) on failure
 | 
| 91 |       (number of bytes read, 0) on success.  Where 0 bytes read indicates EOF.
 | 
| 92 |     """
 | 
| 93 |     try:
 | 
| 94 |         chunk = posix.read(fd, n)
 | 
| 95 |     except OSError as e:
 | 
| 96 |         return -1, e.errno
 | 
| 97 |     else:
 | 
| 98 |         length = len(chunk)
 | 
| 99 |         if length:
 | 
| 100 |             chunks.append(chunk)
 | 
| 101 |         return length, 0
 | 
| 102 | 
 | 
| 103 | 
 | 
| 104 | def ReadByte(fd):
 | 
| 105 |     # type: (int) -> Tuple[int, int]
 | 
| 106 |     """Another low level interface with a return value interface.  Used by
 | 
| 107 |     _ReadUntilDelim() and _ReadLineSlowly().
 | 
| 108 | 
 | 
| 109 |     Returns:
 | 
| 110 |       failure: (-1, errno) on failure
 | 
| 111 |       success: (ch integer value or EOF_SENTINEL, 0)
 | 
| 112 |     """
 | 
| 113 |     try:
 | 
| 114 |         b = posix.read(fd, 1)
 | 
| 115 |     except OSError as e:
 | 
| 116 |         return -1, e.errno
 | 
| 117 |     else:
 | 
| 118 |         if len(b):
 | 
| 119 |             return ord(b), 0
 | 
| 120 |         else:
 | 
| 121 |             return EOF_SENTINEL, 0
 | 
| 122 | 
 | 
| 123 | 
 | 
| 124 | def Environ():
 | 
| 125 |     # type: () -> Dict[str, str]
 | 
| 126 |     return posix.environ
 | 
| 127 | 
 | 
| 128 | 
 | 
| 129 | def Chdir(dest_dir):
 | 
| 130 |     # type: (str) -> int
 | 
| 131 |     """Returns 0 for success and nonzero errno for error."""
 | 
| 132 |     try:
 | 
| 133 |         posix.chdir(dest_dir)
 | 
| 134 |     except OSError as e:
 | 
| 135 |         return e.errno
 | 
| 136 |     return 0
 | 
| 137 | 
 | 
| 138 | 
 | 
| 139 | def GetMyHomeDir():
 | 
| 140 |     # type: () -> Optional[str]
 | 
| 141 |     """Get the user's home directory from the /etc/pyos.
 | 
| 142 | 
 | 
| 143 |     Used by $HOME initialization in osh/state.py.  Tilde expansion and
 | 
| 144 |     readline initialization use mem.GetValue('HOME').
 | 
| 145 |     """
 | 
| 146 |     uid = posix.getuid()
 | 
| 147 |     try:
 | 
| 148 |         e = pwd.getpwuid(uid)
 | 
| 149 |     except KeyError:
 | 
| 150 |         return None
 | 
| 151 | 
 | 
| 152 |     return e.pw_dir
 | 
| 153 | 
 | 
| 154 | 
 | 
| 155 | def GetHomeDir(user_name):
 | 
| 156 |     # type: (str) -> Optional[str]
 | 
| 157 |     """For ~otheruser/src.
 | 
| 158 | 
 | 
| 159 |     TODO: Should this be cached?
 | 
| 160 |     """
 | 
| 161 |     # http://linux.die.net/man/3/getpwnam
 | 
| 162 |     try:
 | 
| 163 |         e = pwd.getpwnam(user_name)
 | 
| 164 |     except KeyError:
 | 
| 165 |         return None
 | 
| 166 | 
 | 
| 167 |     return e.pw_dir
 | 
| 168 | 
 | 
| 169 | 
 | 
| 170 | class PasswdEntry(object):
 | 
| 171 | 
 | 
| 172 |     def __init__(self, pw_name, uid, gid):
 | 
| 173 |         # type: (str, int, int) -> None
 | 
| 174 |         self.pw_name = pw_name
 | 
| 175 |         self.pw_uid = uid
 | 
| 176 |         self.pw_gid = gid
 | 
| 177 | 
 | 
| 178 | 
 | 
| 179 | def GetAllUsers():
 | 
| 180 |     # type: () -> List[PasswdEntry]
 | 
| 181 |     users = [
 | 
| 182 |         PasswdEntry(u.pw_name, u.pw_uid, u.pw_gid) for u in pwd.getpwall()
 | 
| 183 |     ]
 | 
| 184 |     return users
 | 
| 185 | 
 | 
| 186 | 
 | 
| 187 | def GetUserName(uid):
 | 
| 188 |     # type: (int) -> str
 | 
| 189 |     try:
 | 
| 190 |         e = pwd.getpwuid(uid)
 | 
| 191 |     except KeyError:
 | 
| 192 |         return "<ERROR: Couldn't determine user name for uid %d>" % uid
 | 
| 193 |     else:
 | 
| 194 |         return e.pw_name
 | 
| 195 | 
 | 
| 196 | 
 | 
| 197 | def GetRLimit(res):
 | 
| 198 |     # type: (int) -> Tuple[mops.BigInt, mops.BigInt]
 | 
| 199 |     """
 | 
| 200 |     Raises IOError
 | 
| 201 |     """
 | 
| 202 |     soft, hard = resource.getrlimit(res)
 | 
| 203 |     return (mops.IntWiden(soft), mops.IntWiden(hard))
 | 
| 204 | 
 | 
| 205 | 
 | 
| 206 | def SetRLimit(res, soft, hard):
 | 
| 207 |     # type: (int, mops.BigInt, mops.BigInt) -> None
 | 
| 208 |     """
 | 
| 209 |     Raises IOError
 | 
| 210 |     """
 | 
| 211 |     resource.setrlimit(res, (soft.i, hard.i))
 | 
| 212 | 
 | 
| 213 | 
 | 
| 214 | def Time():
 | 
| 215 |     # type: () -> Tuple[float, float, float]
 | 
| 216 |     t = time.time()  # calls gettimeofday() under the hood
 | 
| 217 |     u = resource.getrusage(resource.RUSAGE_SELF)
 | 
| 218 |     return t, u.ru_utime, u.ru_stime
 | 
| 219 | 
 | 
| 220 | 
 | 
| 221 | def PrintTimes():
 | 
| 222 |     # type: () -> None
 | 
| 223 |     utime, stime, cutime, cstime, elapsed = posix.times()
 | 
| 224 |     print("%dm%.3fs %dm%.3fs" %
 | 
| 225 |           (utime / 60, utime % 60, stime / 60, stime % 60))
 | 
| 226 |     print("%dm%.3fs %dm%.3fs" %
 | 
| 227 |           (cutime / 60, cutime % 60, cstime / 60, cstime % 60))
 | 
| 228 | 
 | 
| 229 | 
 | 
| 230 | # So builtin_misc.py doesn't depend on termios, which makes C++ translation
 | 
| 231 | # easier
 | 
| 232 | TERM_ICANON = termios.ICANON
 | 
| 233 | TERM_ECHO = termios.ECHO
 | 
| 234 | 
 | 
| 235 | 
 | 
| 236 | def PushTermAttrs(fd, mask):
 | 
| 237 |     # type: (int, int) -> Tuple[int, Any]
 | 
| 238 |     """Returns opaque type (void* in C++) to be reused in the PopTermAttrs()"""
 | 
| 239 |     # https://docs.python.org/2/library/termios.html
 | 
| 240 |     term_attrs = termios.tcgetattr(fd)
 | 
| 241 | 
 | 
| 242 |     # Flip the bits in one field, e.g. ICANON to disable canonical (buffered)
 | 
| 243 |     # mode.
 | 
| 244 |     orig_local_modes = cast(int, term_attrs[3])
 | 
| 245 |     term_attrs[3] = orig_local_modes & mask
 | 
| 246 | 
 | 
| 247 |     termios.tcsetattr(fd, termios.TCSANOW, term_attrs)
 | 
| 248 |     return orig_local_modes, term_attrs
 | 
| 249 | 
 | 
| 250 | 
 | 
| 251 | def PopTermAttrs(fd, orig_local_modes, term_attrs):
 | 
| 252 |     # type: (int, int, Any) -> None
 | 
| 253 | 
 | 
| 254 |     term_attrs[3] = orig_local_modes
 | 
| 255 |     try:
 | 
| 256 |         termios.tcsetattr(fd, termios.TCSANOW, term_attrs)
 | 
| 257 |     except termios.error as e:
 | 
| 258 |         # Superficial fix for issue #1001.  I'm not sure why we get errno.EIO,
 | 
| 259 |         # but we can't really handle it here.  In C++ I guess we ignore the
 | 
| 260 |         # error.
 | 
| 261 |         pass
 | 
| 262 | 
 | 
| 263 | 
 | 
| 264 | def OsType():
 | 
| 265 |     # type: () -> str
 | 
| 266 |     """Compute $OSTYPE variable."""
 | 
| 267 |     return posix.uname()[0].lower()
 | 
| 268 | 
 | 
| 269 | 
 | 
| 270 | def InputAvailable(fd):
 | 
| 271 |     # type: (int) -> bool
 | 
| 272 |     # similar to lib/sh/input_avail.c in bash
 | 
| 273 |     # read, write, except
 | 
| 274 |     r, w, exc = select.select([fd], [], [fd], 0)
 | 
| 275 |     return len(r) != 0
 | 
| 276 | 
 | 
| 277 | 
 | 
| 278 | UNTRAPPED_SIGWINCH = -1
 | 
| 279 | 
 | 
| 280 | 
 | 
| 281 | class SignalSafe(object):
 | 
| 282 |     """State that is shared between the main thread and signal handlers.
 | 
| 283 | 
 | 
| 284 |     See C++ implementation in cpp/core.h
 | 
| 285 |     """
 | 
| 286 | 
 | 
| 287 |     def __init__(self):
 | 
| 288 |         # type: () -> None
 | 
| 289 |         self.pending_signals = []  # type: List[int]
 | 
| 290 |         self.last_sig_num = 0  # type: int
 | 
| 291 |         self.received_sigint = False
 | 
| 292 |         self.received_sigwinch = False
 | 
| 293 |         self.sigwinch_code = UNTRAPPED_SIGWINCH
 | 
| 294 | 
 | 
| 295 |     def UpdateFromSignalHandler(self, sig_num, unused_frame):
 | 
| 296 |         # type: (int, Any) -> None
 | 
| 297 |         """Receive the given signal, and update shared state.
 | 
| 298 | 
 | 
| 299 |         This method is registered as a Python signal handler.
 | 
| 300 |         """
 | 
| 301 |         self.pending_signals.append(sig_num)
 | 
| 302 | 
 | 
| 303 |         if sig_num == signal.SIGINT:
 | 
| 304 |             self.received_sigint = True
 | 
| 305 | 
 | 
| 306 |         if sig_num == signal.SIGWINCH:
 | 
| 307 |             self.received_sigwinch = True
 | 
| 308 |             sig_num = self.sigwinch_code  # mutate param
 | 
| 309 | 
 | 
| 310 |         self.last_sig_num = sig_num
 | 
| 311 | 
 | 
| 312 |     def LastSignal(self):
 | 
| 313 |         # type: () -> int
 | 
| 314 |         """Return the number of the last signal that fired."""
 | 
| 315 |         return self.last_sig_num
 | 
| 316 | 
 | 
| 317 |     def PollSigInt(self):
 | 
| 318 |         # type: () -> bool
 | 
| 319 |         """Has SIGINT received since the last time PollSigInt() was called?"""
 | 
| 320 |         result = self.received_sigint
 | 
| 321 |         self.received_sigint = False
 | 
| 322 |         return result
 | 
| 323 | 
 | 
| 324 |     def SetSigWinchCode(self, code):
 | 
| 325 |         # type: (int) -> None
 | 
| 326 |         """Depending on whether or not SIGWINCH is trapped by a user, it is
 | 
| 327 |         expected to report a different code to `wait`.
 | 
| 328 | 
 | 
| 329 |         SetSigwinchCode() lets us set which code is reported.
 | 
| 330 |         """
 | 
| 331 |         self.sigwinch_code = code
 | 
| 332 | 
 | 
| 333 |     def PollSigWinch(self):
 | 
| 334 |         # type: () -> bool
 | 
| 335 |         """Has SIGWINCH been received since the last time PollSigWinch() was
 | 
| 336 |         called?"""
 | 
| 337 |         result = self.received_sigwinch
 | 
| 338 |         self.received_sigwinch = False
 | 
| 339 |         return result
 | 
| 340 | 
 | 
| 341 |     def TakePendingSignals(self):
 | 
| 342 |         # type: () -> List[int]
 | 
| 343 |         # A note on signal-safety here. The main loop might be calling this function
 | 
| 344 |         # at the same time a signal is firing and appending to
 | 
| 345 |         # `self.pending_signals`. We can forgoe using a lock here
 | 
| 346 |         # (which would be problematic for the signal handler) because mutual
 | 
| 347 |         # exclusivity should be maintained by the atomic nature of pointer
 | 
| 348 |         # assignment (i.e. word-sized writes) on most modern platforms.
 | 
| 349 |         # The replacement run list is allocated before the swap, so it can be
 | 
| 350 |         # interuppted at any point without consequence.
 | 
| 351 |         # This means the signal handler always has exclusive access to
 | 
| 352 |         # `self.pending_signals`. In the worst case the signal handler might write to
 | 
| 353 |         # `new_queue` and the corresponding trap handler won't get executed
 | 
| 354 |         # until the main loop calls this function again.
 | 
| 355 |         # NOTE: It's important to distinguish between signal-safety an
 | 
| 356 |         # thread-safety here. Signals run in the same process context as the main
 | 
| 357 |         # loop, while concurrent threads do not and would have to worry about
 | 
| 358 |         # cache-coherence and instruction reordering.
 | 
| 359 |         new_queue = []  #  type: List[int]
 | 
| 360 |         ret = self.pending_signals
 | 
| 361 |         self.pending_signals = new_queue
 | 
| 362 |         return ret
 | 
| 363 | 
 | 
| 364 |     def ReuseEmptyList(self, empty_list):
 | 
| 365 |         # type: (List[int]) -> None
 | 
| 366 |         """This optimization only happens in C++."""
 | 
| 367 |         pass
 | 
| 368 | 
 | 
| 369 | 
 | 
| 370 | gSignalSafe = None  #  type: SignalSafe
 | 
| 371 | 
 | 
| 372 | 
 | 
| 373 | def InitSignalSafe():
 | 
| 374 |     # type: () -> SignalSafe
 | 
| 375 |     """Set global instance so the signal handler can access it."""
 | 
| 376 |     global gSignalSafe
 | 
| 377 |     gSignalSafe = SignalSafe()
 | 
| 378 |     return gSignalSafe
 | 
| 379 | 
 | 
| 380 | 
 | 
| 381 | def Sigaction(sig_num, handler):
 | 
| 382 |     # type: (int, Any) -> None
 | 
| 383 |     """Register a signal handler."""
 | 
| 384 |     signal.signal(sig_num, handler)
 | 
| 385 | 
 | 
| 386 | 
 | 
| 387 | def RegisterSignalInterest(sig_num):
 | 
| 388 |     # type: (int) -> None
 | 
| 389 |     """Have the kernel notify the main loop about the given signal."""
 | 
| 390 |     assert gSignalSafe is not None
 | 
| 391 |     signal.signal(sig_num, gSignalSafe.UpdateFromSignalHandler)
 | 
| 392 | 
 | 
| 393 | 
 | 
| 394 | def MakeDirCacheKey(path):
 | 
| 395 |     # type: (str) -> Tuple[str, int]
 | 
| 396 |     """Returns a pair (path with last modified time) that can be used to cache
 | 
| 397 |     directory accesses."""
 | 
| 398 |     st = posix.stat(path)
 | 
| 399 |     return (path, int(st.st_mtime))
 |