text: # 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.
  
  """Common objects shared by __init__.py and _ps*.py modules."""
  
  # Note: this module is imported by setup.py so it should not import
  # psutil or third-party modules.
  
  from __future__ import division
  from __future__ import print_function
  
  import collections
  import contextlib
  import errno
  import functools
  import os
  import socket
  import stat
  import sys
  import threading
  import warnings
  from collections import namedtuple
  from socket import AF_INET
  from socket import SOCK_DGRAM
  from socket import SOCK_STREAM
  
  
  try:
      from socket import AF_INET6
  except ImportError:
      AF_INET6 = None
  try:
      from socket import AF_UNIX
  except ImportError:
      AF_UNIX = None
  
  if sys.version_info >= (3, 4):
      import enum
  else:
      enum = None
  
  
  # can't take it from _common.py as this script is imported by setup.py
  PY3 = sys.version_info[0] == 3
  PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG', 0))
  _DEFAULT = object()
  
  __all__ = [
      # OS constants
      'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
      'SUNOS', 'WINDOWS',
      # connection constants
      'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
      'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
      'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
      # net constants
      'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
      # process status constants
      'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
      'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
      'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
      'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
      # other constants
      'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
      # named tuples
      'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
      'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
      'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser',
      # utility functions
      'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
      'parse_environ_block', 'path_exists_strict', 'usage_percent',
      'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
      'open_text', 'open_binary', 'cat', 'bcat',
      'bytes2human', 'conn_to_ntuple', 'debug',
      # shell utils
      'hilite', 'term_supports_colors', 'print_color',
  ]
  
  
  # ===================================================================
  # --- OS constants
  # ===================================================================
  
  
  POSIX = os.name == "posix"
  WINDOWS = os.name == "nt"
  LINUX = sys.platform.startswith("linux")
  MACOS = sys.platform.startswith("darwin")
  OSX = MACOS  # deprecated alias
  FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
  OPENBSD = sys.platform.startswith("openbsd")
  NETBSD = sys.platform.startswith("netbsd")
  BSD = FREEBSD or OPENBSD or NETBSD
  SUNOS = sys.platform.startswith(("sunos", "solaris"))
  AIX = sys.platform.startswith("aix")
  
  
  # ===================================================================
  # --- API constants
  # ===================================================================
  
  
  # Process.status()
  STATUS_RUNNING = "running"
  STATUS_SLEEPING = "sleeping"
  STATUS_DISK_SLEEP = "disk-sleep"
  STATUS_STOPPED = "stopped"
  STATUS_TRACING_STOP = "tracing-stop"
  STATUS_ZOMBIE = "zombie"
  STATUS_DEAD = "dead"
  STATUS_WAKE_KILL = "wake-kill"
  STATUS_WAKING = "waking"
  STATUS_IDLE = "idle"  # Linux, macOS, FreeBSD
  STATUS_LOCKED = "locked"  # FreeBSD
  STATUS_WAITING = "waiting"  # FreeBSD
  STATUS_SUSPENDED = "suspended"  # NetBSD
  STATUS_PARKED = "parked"  # Linux
  
  # Process.connections() and psutil.net_connections()
  CONN_ESTABLISHED = "ESTABLISHED"
  CONN_SYN_SENT = "SYN_SENT"
  CONN_SYN_RECV = "SYN_RECV"
  CONN_FIN_WAIT1 = "FIN_WAIT1"
  CONN_FIN_WAIT2 = "FIN_WAIT2"
  CONN_TIME_WAIT = "TIME_WAIT"
  CONN_CLOSE = "CLOSE"
  CONN_CLOSE_WAIT = "CLOSE_WAIT"
  CONN_LAST_ACK = "LAST_ACK"
  CONN_LISTEN = "LISTEN"
  CONN_CLOSING = "CLOSING"
  CONN_NONE = "NONE"
  
  # net_if_stats()
  if enum is None:
      NIC_DUPLEX_FULL = 2
      NIC_DUPLEX_HALF = 1
      NIC_DUPLEX_UNKNOWN = 0
  else:
      class NicDuplex(enum.IntEnum):
          NIC_DUPLEX_FULL = 2
          NIC_DUPLEX_HALF = 1
          NIC_DUPLEX_UNKNOWN = 0
  
      globals().update(NicDuplex.__members__)
  
  # sensors_battery()
  if enum is None:
      POWER_TIME_UNKNOWN = -1
      POWER_TIME_UNLIMITED = -2
  else:
      class BatteryTime(enum.IntEnum):
          POWER_TIME_UNKNOWN = -1
          POWER_TIME_UNLIMITED = -2
  
      globals().update(BatteryTime.__members__)
  
  # --- others
  
  ENCODING = sys.getfilesystemencoding()
  if not PY3:
      ENCODING_ERRS = "replace"
  else:
      try:
          ENCODING_ERRS = sys.getfilesystemencodeerrors()  # py 3.6
      except AttributeError:
          ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
  
  
  # ===================================================================
  # --- namedtuples
  # ===================================================================
  
  # --- for system functions
  
  # psutil.swap_memory()
  sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
                               'sout'])
  # psutil.disk_usage()
  sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
  # psutil.disk_io_counters()
  sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
                                   'read_bytes', 'write_bytes',
                                   'read_time', 'write_time'])
  # psutil.disk_partitions()
  sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts',
                                       'maxfile', 'maxpath'])
  # psutil.net_io_counters()
  snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
                                 'packets_sent', 'packets_recv',
                                 'errin', 'errout',
                                 'dropin', 'dropout'])
  # psutil.users()
  suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
  # psutil.net_connections()
  sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
                               'status', 'pid'])
  # psutil.net_if_addrs()
  snicaddr = namedtuple('snicaddr',
                        ['family', 'address', 'netmask', 'broadcast', 'ptp'])
  # psutil.net_if_stats()
  snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
  # psutil.cpu_stats()
  scpustats = namedtuple(
      'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
  # psutil.cpu_freq()
  scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
  # psutil.sensors_temperatures()
  shwtemp = namedtuple(
      'shwtemp', ['label', 'current', 'high', 'critical'])
  # psutil.sensors_battery()
  sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
  # psutil.sensors_fans()
  sfan = namedtuple('sfan', ['label', 'current'])
  
  # --- for Process methods
  
  # psutil.Process.cpu_times()
  pcputimes = namedtuple('pcputimes',
                         ['user', 'system', 'children_user', 'children_system'])
  # psutil.Process.open_files()
  popenfile = namedtuple('popenfile', ['path', 'fd'])
  # psutil.Process.threads()
  pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
  # psutil.Process.uids()
  puids = namedtuple('puids', ['real', 'effective', 'saved'])
  # psutil.Process.gids()
  pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
  # psutil.Process.io_counters()
  pio = namedtuple('pio', ['read_count', 'write_count',
                           'read_bytes', 'write_bytes'])
  # psutil.Process.ionice()
  pionice = namedtuple('pionice', ['ioclass', 'value'])
  # psutil.Process.ctx_switches()
  pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
  # psutil.Process.connections()
  pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
                               'status'])
  
  # psutil.connections() and psutil.Process.connections()
  addr = namedtuple('addr', ['ip', 'port'])
  
  
  # ===================================================================
  # --- Process.connections() 'kind' parameter mapping
  # ===================================================================
  
  
  conn_tmap = {
      "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
      "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
      "tcp4": ([AF_INET], [SOCK_STREAM]),
      "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
      "udp4": ([AF_INET], [SOCK_DGRAM]),
      "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
      "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
      "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
  }
  
  if AF_INET6 is not None:
      conn_tmap.update({
          "tcp6": ([AF_INET6], [SOCK_STREAM]),
          "udp6": ([AF_INET6], [SOCK_DGRAM]),
      })
  
  if AF_UNIX is not None:
      conn_tmap.update({
          "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
      })
  
  
  # =====================================================================
  # --- Exceptions
  # =====================================================================
  
  
  class Error(Exception):
      """Base exception class. All other psutil exceptions inherit
      from this one.
      """
      __module__ = 'psutil'
  
      def _infodict(self, attrs):
          info = collections.OrderedDict()
          for name in attrs:
              value = getattr(self, name, None)
              if value:
                  info[name] = value
              elif name == "pid" and value == 0:
                  info[name] = value
          return info
  
      def __str__(self):
          # invoked on `raise Error`
          info = self._infodict(("pid", "ppid", "name"))
          if info:
              details = "(%s)" % ", ".join(
                  ["%s=%r" % (k, v) for k, v in info.items()])
          else:
              details = None
          return " ".join([x for x in (getattr(self, "msg", ""), details) if x])
  
      def __repr__(self):
          # invoked on `repr(Error)`
          info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
          details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()])
          return "psutil.%s(%s)" % (self.__class__.__name__, details)
  
  
  class NoSuchProcess(Error):
      """Exception raised when a process with a certain PID doesn't
      or no longer exists.
      """
      __module__ = 'psutil'
  
      def __init__(self, pid, name=None, msg=None):
          Error.__init__(self)
          self.pid = pid
          self.name = name
          self.msg = msg or "process no longer exists"
  
  
  class ZombieProcess(NoSuchProcess):
      """Exception raised when querying a zombie process. This is
      raised on macOS, BSD and Solaris only, and not always: depending
      on the query the OS may be able to succeed anyway.
      On Linux all zombie processes are querable (hence this is never
      raised). Windows doesn't have zombie processes.
      """
      __module__ = 'psutil'
  
      def __init__(self, pid, name=None, ppid=None, msg=None):
          NoSuchProcess.__init__(self, pid, name, msg)
          self.ppid = ppid
          self.msg = msg or "PID still exists but it's a zombie"
  
  
  class AccessDenied(Error):
      """Exception raised when permission to perform an action is denied."""
      __module__ = 'psutil'
  
      def __init__(self, pid=None, name=None, msg=None):
          Error.__init__(self)
          self.pid = pid
          self.name = name
          self.msg = msg or ""
  
  
  class TimeoutExpired(Error):
      """Raised on Process.wait(timeout) if timeout expires and process
      is still alive.
      """
      __module__ = 'psutil'
  
      def __init__(self, seconds, pid=None, name=None):
          Error.__init__(self)
          self.seconds = seconds
          self.pid = pid
          self.name = name
          self.msg = "timeout after %s seconds" % seconds
  
  
  # ===================================================================
  # --- utils
  # ===================================================================
  
  
  def usage_percent(used, total, round_=None):
      """Calculate percentage usage of 'used' against 'total'."""
      try:
          ret = (float(used) / total) * 100
      except ZeroDivisionError:
          return 0.0
      else:
          if round_ is not None:
              ret = round(ret, round_)
          return ret
  
  
  def memoize(fun):
      """A simple memoize decorator for functions supporting (hashable)
      positional arguments.
      It also provides a cache_clear() function for clearing the cache:
  
      >>> @memoize
      ... def foo()
      ...     return 1
          ...
      >>> foo()
      1
      >>> foo.cache_clear()
      >>>
      """
      @functools.wraps(fun)
      def wrapper(*args, **kwargs):
          key = (args, frozenset(sorted(kwargs.items())))
          try:
              return cache[key]
          except KeyError:
              ret = cache[key] = fun(*args, **kwargs)
              return ret
  
      def cache_clear():
          """Clear cache."""
          cache.clear()
  
      cache = {}
      wrapper.cache_clear = cache_clear
      return wrapper
  
  
  def memoize_when_activated(fun):
      """A memoize decorator which is disabled by default. It can be
      activated and deactivated on request.
      For efficiency reasons it can be used only against class methods
      accepting no arguments.
  
      >>> class Foo:
      ...     @memoize
      ...     def foo()
      ...         print(1)
      ...
      >>> f = Foo()
      >>> # deactivated (default)
      >>> foo()
      1
      >>> foo()
      1
      >>>
      >>> # activated
      >>> foo.cache_activate(self)
      >>> foo()
      1
      >>> foo()
      >>> foo()
      >>>
      """
      @functools.wraps(fun)
      def wrapper(self):
          try:
              # case 1: we previously entered oneshot() ctx
              ret = self._cache[fun]
          except AttributeError:
              # case 2: we never entered oneshot() ctx
              return fun(self)
          except KeyError:
              # case 3: we entered oneshot() ctx but there's no cache
              # for this entry yet
              ret = fun(self)
              try:
                  self._cache[fun] = ret
              except AttributeError:
                  # multi-threading race condition, see:
                  # https://github.com/giampaolo/psutil/issues/1948
                  pass
          return ret
  
      def cache_activate(proc):
          """Activate cache. Expects a Process instance. Cache will be
          stored as a "_cache" instance attribute."""
          proc._cache = {}
  
      def cache_deactivate(proc):
          """Deactivate and clear cache."""
          try:
              del proc._cache
          except AttributeError:
              pass
  
      wrapper.cache_activate = cache_activate
      wrapper.cache_deactivate = cache_deactivate
      return wrapper
  
  
  def isfile_strict(path):
      """Same as os.path.isfile() but does not swallow EACCES / EPERM
      exceptions, see:
      http://mail.python.org/pipermail/python-dev/2012-June/120787.html
      """
      try:
          st = os.stat(path)
      except OSError as err:
          if err.errno in (errno.EPERM, errno.EACCES):
              raise
          return False
      else:
          return stat.S_ISREG(st.st_mode)
  
  
  def path_exists_strict(path):
      """Same as os.path.exists() but does not swallow EACCES / EPERM
      exceptions, see:
      http://mail.python.org/pipermail/python-dev/2012-June/120787.html
      """
      try:
          os.stat(path)
      except OSError as err:
          if err.errno in (errno.EPERM, errno.EACCES):
              raise
          return False
      else:
          return True
  
  
  @memoize
  def supports_ipv6():
      """Return True if IPv6 is supported on this platform."""
      if not socket.has_ipv6 or AF_INET6 is None:
          return False
      try:
          sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
          with contextlib.closing(sock):
              sock.bind(("::1", 0))
          return True
      except socket.error:
          return False
  
  
  def parse_environ_block(data):
      """Parse a C environ block of environment variables into a dictionary."""
      # The block is usually raw data from the target process.  It might contain
      # trailing garbage and lines that do not look like assignments.
      ret = {}
      pos = 0
  
      # localize global variable to speed up access.
      WINDOWS_ = WINDOWS
      while True:
          next_pos = data.find("\0", pos)
          # nul byte at the beginning or double nul byte means finish
          if next_pos <= pos:
              break
          # there might not be an equals sign
          equal_pos = data.find("=", pos, next_pos)
          if equal_pos > pos:
              key = data[pos:equal_pos]
              value = data[equal_pos + 1:next_pos]
              # Windows expects environment variables to be uppercase only
              if WINDOWS_:
                  key = key.upper()
              ret[key] = value
          pos = next_pos + 1
  
      return ret
  
  
  def sockfam_to_enum(num):
      """Convert a numeric socket family value to an IntEnum member.
      If it's not a known member, return the numeric value itself.
      """
      if enum is None:
          return num
      else:  # pragma: no cover
          try:
              return socket.AddressFamily(num)
          except ValueError:
              return num
  
  
  def socktype_to_enum(num):
      """Convert a numeric socket type value to an IntEnum member.
      If it's not a known member, return the numeric value itself.
      """
      if enum is None:
          return num
      else:  # pragma: no cover
          try:
              return socket.SocketKind(num)
          except ValueError:
              return num
  
  
  def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
      """Convert a raw connection tuple to a proper ntuple."""
      if fam in (socket.AF_INET, AF_INET6):
          if laddr:
              laddr = addr(*laddr)
          if raddr:
              raddr = addr(*raddr)
      if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
          status = status_map.get(status, CONN_NONE)
      else:
          status = CONN_NONE  # ignore whatever C returned to us
      fam = sockfam_to_enum(fam)
      type_ = socktype_to_enum(type_)
      if pid is None:
          return pconn(fd, fam, type_, laddr, raddr, status)
      else:
          return sconn(fd, fam, type_, laddr, raddr, status, pid)
  
  
  def deprecated_method(replacement):
      """A decorator which can be used to mark a method as deprecated
      'replcement' is the method name which will be called instead.
      """
      def outer(fun):
          msg = "%s() is deprecated and will be removed; use %s() instead" % (
              fun.__name__, replacement)
          if fun.__doc__ is None:
              fun.__doc__ = msg
  
          @functools.wraps(fun)
          def inner(self, *args, **kwargs):
              warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
              return getattr(self, replacement)(*args, **kwargs)
          return inner
      return outer
  
  
  class _WrapNumbers:
      """Watches numbers so that they don't overflow and wrap
      (reset to zero).
      """
  
      def __init__(self):
          self.lock = threading.Lock()
          self.cache = {}
          self.reminders = {}
          self.reminder_keys = {}
  
      def _add_dict(self, input_dict, name):
          assert name not in self.cache
          assert name not in self.reminders
          assert name not in self.reminder_keys
          self.cache[name] = input_dict
          self.reminders[name] = collections.defaultdict(int)
          self.reminder_keys[name] = collections.defaultdict(set)
  
      def _remove_dead_reminders(self, input_dict, name):
          """In case the number of keys changed between calls (e.g. a
          disk disappears) this removes the entry from self.reminders.
          """
          old_dict = self.cache[name]
          gone_keys = set(old_dict.keys()) - set(input_dict.keys())
          for gone_key in gone_keys:
              for remkey in self.reminder_keys[name][gone_key]:
                  del self.reminders[name][remkey]
              del self.reminder_keys[name][gone_key]
  
      def run(self, input_dict, name):
          """Cache dict and sum numbers which overflow and wrap.
          Return an updated copy of `input_dict`
          """
          if name not in self.cache:
              # This was the first call.
              self._add_dict(input_dict, name)
              return input_dict
  
          self._remove_dead_reminders(input_dict, name)
  
          old_dict = self.cache[name]
          new_dict = {}
          for key in input_dict.keys():
              input_tuple = input_dict[key]
              try:
                  old_tuple = old_dict[key]
              except KeyError:
                  # The input dict has a new key (e.g. a new disk or NIC)
                  # which didn't exist in the previous call.
                  new_dict[key] = input_tuple
                  continue
  
              bits = []
              for i in range(len(input_tuple)):
                  input_value = input_tuple[i]
                  old_value = old_tuple[i]
                  remkey = (key, i)
                  if input_value < old_value:
                      # it wrapped!
                      self.reminders[name][remkey] += old_value
                      self.reminder_keys[name][key].add(remkey)
                  bits.append(input_value + self.reminders[name][remkey])
  
              new_dict[key] = tuple(bits)
  
          self.cache[name] = input_dict
          return new_dict
  
      def cache_clear(self, name=None):
          """Clear the internal cache, optionally only for function 'name'."""
          with self.lock:
              if name is None:
                  self.cache.clear()
                  self.reminders.clear()
                  self.reminder_keys.clear()
              else:
                  self.cache.pop(name, None)
                  self.reminders.pop(name, None)
                  self.reminder_keys.pop(name, None)
  
      def cache_info(self):
          """Return internal cache dicts as a tuple of 3 elements."""
          with self.lock:
              return (self.cache, self.reminders, self.reminder_keys)
  
  
  def wrap_numbers(input_dict, name):
      """Given an `input_dict` and a function `name`, adjust the numbers
      which "wrap" (restart from zero) across different calls by adding
      "old value" to "new value" and return an updated dict.
      """
      with _wn.lock:
          return _wn.run(input_dict, name)
  
  
  _wn = _WrapNumbers()
  wrap_numbers.cache_clear = _wn.cache_clear
  wrap_numbers.cache_info = _wn.cache_info
  
  
  # The read buffer size for open() builtin. This (also) dictates how
  # much data we read(2) when iterating over file lines as in:
  #   >>> with open(file) as f:
  #   ...    for line in f:
  #   ...        ...
  # Default per-line buffer size for binary files is 1K. For text files
  # is 8K. We use a bigger buffer (32K) in order to have more consistent
  # results when reading /proc pseudo files on Linux, see:
  # https://github.com/giampaolo/psutil/issues/2050
  # On Python 2 this also speeds up the reading of big files:
  # (namely /proc/{pid}/smaps and /proc/net/*):
  # https://github.com/giampaolo/psutil/issues/708
  FILE_READ_BUFFER_SIZE = 32 * 1024
  
  
  def open_binary(fname):
      return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
  
  
  def open_text(fname):
      """On Python 3 opens a file in text mode by using fs encoding and
      a proper en/decoding errors handler.
      On Python 2 this is just an alias for open(name, 'rt').
      """
      if not PY3:
          return open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE)
  
      # See:
      # https://github.com/giampaolo/psutil/issues/675
      # https://github.com/giampaolo/psutil/pull/733
      fobj = open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE,
                  encoding=ENCODING, errors=ENCODING_ERRS)
      try:
          # Dictates per-line read(2) buffer size. Defaults is 8k. See:
          # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
          fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
      except AttributeError:
          pass
      except Exception:
          fobj.close()
          raise
  
      return fobj
  
  
  def cat(fname, fallback=_DEFAULT, _open=open_text):
      """Read entire file content and return it as a string. File is
      opened in text mode. If specified, `fallback` is the value
      returned in case of error, either if the file does not exist or
      it can't be read().
      """
      if fallback is _DEFAULT:
          with _open(fname) as f:
              return f.read()
      else:
          try:
              with _open(fname) as f:
                  return f.read()
          except (IOError, OSError):
              return fallback
  
  
  def bcat(fname, fallback=_DEFAULT):
      """Same as above but opens file in binary mode."""
      return cat(fname, fallback=fallback, _open=open_binary)
  
  
  def bytes2human(n, format="%(value).1f%(symbol)s"):
      """Used by various scripts. See:
      http://goo.gl/zeJZl
  
      >>> bytes2human(10000)
      '9.8K'
      >>> bytes2human(100001221)
      '95.4M'
      """
      symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
      prefix = {}
      for i, s in enumerate(symbols[1:]):
          prefix[s] = 1 << (i + 1) * 10
      for symbol in reversed(symbols[1:]):
          if n >= prefix[symbol]:
              value = float(n) / prefix[symbol]
              return format % locals()
      return format % dict(symbol=symbols[0], value=n)
  
  
  def get_procfs_path():
      """Return updated psutil.PROCFS_PATH constant."""
      return sys.modules['psutil'].PROCFS_PATH
  
  
  if PY3:
      def decode(s):
          return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
  else:
      def decode(s):
          return s
  
  
  # =====================================================================
  # --- shell utils
  # =====================================================================
  
  
  @memoize
  def term_supports_colors(file=sys.stdout):  # pragma: no cover
      if os.name == 'nt':
          return True
      try:
          import curses
          assert file.isatty()
          curses.setupterm()
          assert curses.tigetnum("colors") > 0
      except Exception:
          return False
      else:
          return True
  
  
  def hilite(s, color=None, bold=False):  # pragma: no cover
      """Return an highlighted version of 'string'."""
      if not term_supports_colors():
          return s
      attr = []
      colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
                    violet='35', lightblue='36', grey='37', darkgrey='30')
      colors[None] = '29'
      try:
          color = colors[color]
      except KeyError:
          raise ValueError("invalid color %r; choose between %s" % (
              list(colors.keys())))
      attr.append(color)
      if bold:
          attr.append('1')
      return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
  
  
  def print_color(
          s, color=None, bold=False, file=sys.stdout):  # pragma: no cover
      """Print a colorized version of string."""
      if not term_supports_colors():
          print(s, file=file)  # NOQA
      elif POSIX:
          print(hilite(s, color, bold), file=file)  # NOQA
      else:
          import ctypes
  
          DEFAULT_COLOR = 7
          GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
          SetConsoleTextAttribute = \
              ctypes.windll.Kernel32.SetConsoleTextAttribute
  
          colors = dict(green=2, red=4, brown=6, yellow=6)
          colors[None] = DEFAULT_COLOR
          try:
              color = colors[color]
          except KeyError:
              raise ValueError("invalid color %r; choose between %r" % (
                  color, list(colors.keys())))
          if bold and color <= 7:
              color += 8
  
          handle_id = -12 if file is sys.stderr else -11
          GetStdHandle.restype = ctypes.c_ulong
          handle = GetStdHandle(handle_id)
          SetConsoleTextAttribute(handle, color)
          try:
              print(s, file=file)    # NOQA
          finally:
              SetConsoleTextAttribute(handle, DEFAULT_COLOR)
  
  
  def debug(msg):
      """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
      if PSUTIL_DEBUG:
          import inspect
          fname, lineno, func_name, lines, index = inspect.getframeinfo(
              inspect.currentframe().f_back)
          if isinstance(msg, Exception):
              if isinstance(msg, (OSError, IOError, EnvironmentError)):
                  # ...because str(exc) may contain info about the file name
                  msg = "ignoring %s" % msg
              else:
                  msg = "ignoring %r" % msg
          print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg),  # NOQA
                file=sys.stderr)
  