| #!/usr/bin/env python |
| |
| # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Routines common to all posix systems.""" |
| |
| import errno |
| import glob |
| import os |
| import sys |
| import time |
| |
| from ._common import sdiskusage, usage_percent, memoize |
| from ._compat import PY3, unicode |
| |
| |
| class TimeoutExpired(Exception): |
| pass |
| |
| |
| def pid_exists(pid): |
| """Check whether pid exists in the current process table.""" |
| if pid == 0: |
| # According to "man 2 kill" PID 0 has a special meaning: |
| # it refers to <<every process in the process group of the |
| # calling process>> so we don't want to go any further. |
| # If we get here it means this UNIX platform *does* have |
| # a process with id 0. |
| return True |
| try: |
| os.kill(pid, 0) |
| except OSError as err: |
| if err.errno == errno.ESRCH: |
| # ESRCH == No such process |
| return False |
| elif err.errno == errno.EPERM: |
| # EPERM clearly means there's a process to deny access to |
| return True |
| else: |
| # According to "man 2 kill" possible error values are |
| # (EINVAL, EPERM, ESRCH) therefore we should never get |
| # here. If we do let's be explicit in considering this |
| # an error. |
| raise err |
| else: |
| return True |
| |
| |
| def wait_pid(pid, timeout=None): |
| """Wait for process with pid 'pid' to terminate and return its |
| exit status code as an integer. |
| |
| If pid is not a children of os.getpid() (current process) just |
| waits until the process disappears and return None. |
| |
| If pid does not exist at all return None immediately. |
| |
| Raise TimeoutExpired on timeout expired. |
| """ |
| def check_timeout(delay): |
| if timeout is not None: |
| if timer() >= stop_at: |
| raise TimeoutExpired() |
| time.sleep(delay) |
| return min(delay * 2, 0.04) |
| |
| timer = getattr(time, 'monotonic', time.time) |
| if timeout is not None: |
| def waitcall(): |
| return os.waitpid(pid, os.WNOHANG) |
| stop_at = timer() + timeout |
| else: |
| def waitcall(): |
| return os.waitpid(pid, 0) |
| |
| delay = 0.0001 |
| while True: |
| try: |
| retpid, status = waitcall() |
| except OSError as err: |
| if err.errno == errno.EINTR: |
| delay = check_timeout(delay) |
| continue |
| elif err.errno == errno.ECHILD: |
| # This has two meanings: |
| # - pid is not a child of os.getpid() in which case |
| # we keep polling until it's gone |
| # - pid never existed in the first place |
| # In both cases we'll eventually return None as we |
| # can't determine its exit status code. |
| while True: |
| if pid_exists(pid): |
| delay = check_timeout(delay) |
| else: |
| return |
| else: |
| raise |
| else: |
| if retpid == 0: |
| # WNOHANG was used, pid is still running |
| delay = check_timeout(delay) |
| continue |
| # process exited due to a signal; return the integer of |
| # that signal |
| if os.WIFSIGNALED(status): |
| return os.WTERMSIG(status) |
| # process exited using exit(2) system call; return the |
| # integer exit(2) system call has been called with |
| elif os.WIFEXITED(status): |
| return os.WEXITSTATUS(status) |
| else: |
| # should never happen |
| raise RuntimeError("unknown process exit status") |
| |
| |
| def disk_usage(path): |
| """Return disk usage associated with path.""" |
| try: |
| st = os.statvfs(path) |
| except UnicodeEncodeError: |
| if not PY3 and isinstance(path, unicode): |
| # this is a bug with os.statvfs() and unicode on |
| # Python 2, see: |
| # - https://github.com/giampaolo/psutil/issues/416 |
| # - http://bugs.python.org/issue18695 |
| try: |
| path = path.encode(sys.getfilesystemencoding()) |
| except UnicodeEncodeError: |
| pass |
| st = os.statvfs(path) |
| else: |
| raise |
| free = (st.f_bavail * st.f_frsize) |
| total = (st.f_blocks * st.f_frsize) |
| used = (st.f_blocks - st.f_bfree) * st.f_frsize |
| percent = usage_percent(used, total, _round=1) |
| # NB: the percentage is -5% than what shown by df due to |
| # reserved blocks that we are currently not considering: |
| # http://goo.gl/sWGbH |
| return sdiskusage(total, used, free, percent) |
| |
| |
| @memoize |
| def _get_terminal_map(): |
| ret = {} |
| ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') |
| for name in ls: |
| assert name not in ret |
| try: |
| ret[os.stat(name).st_rdev] = name |
| except OSError as err: |
| if err.errno != errno.ENOENT: |
| raise |
| return ret |