OILS / core / pyos.py View on Github | oilshell.org

399 lines, 176 significant
1"""
2pyos.py -- Wrappers for the operating system.
3
4Like py{error,util}.py, it won't be translated to C++.
5"""
6from __future__ import print_function
7
8from errno import EINTR
9import pwd
10import resource
11import signal
12import select
13import sys
14import termios # for read -n
15import time
16
17from core import pyutil
18from mycpp import mops
19from mycpp.mylib import log
20
21import posix_ as posix
22from posix_ import WUNTRACED
23
24from typing import Optional, Tuple, List, Dict, cast, Any, TYPE_CHECKING
25if TYPE_CHECKING:
26 from core import error
27
28_ = log
29
30EOF_SENTINEL = 256 # bigger than any byte
31NEWLINE_CH = 10 # ord('\n')
32
33
34def 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
49def 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
70class 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
81def 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
104def 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
124def Environ():
125 # type: () -> Dict[str, str]
126 return posix.environ
127
128
129def 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
139def 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
155def 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
170class 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
179def 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
187def 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
197def 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
206def 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
214def 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
221def 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
232TERM_ICANON = termios.ICANON
233TERM_ECHO = termios.ECHO
234
235
236def 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
251def 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
264def OsType():
265 # type: () -> str
266 """Compute $OSTYPE variable."""
267 return posix.uname()[0].lower()
268
269
270def 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
278UNTRAPPED_SIGWINCH = -1
279
280
281class 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
370gSignalSafe = None # type: SignalSafe
371
372
373def 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
381def Sigaction(sig_num, handler):
382 # type: (int, Any) -> None
383 """Register a signal handler."""
384 signal.signal(sig_num, handler)
385
386
387def 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
394def 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))