# Copyright 2023 VMware, Inc.
# All rights reserved. -- VMware Confidential

"""Dynamic library loader.

Loading dynamic libraries with ctypes is not as trivial as it seems at first
sight, mostly due to platform-specific behaviors, python version-specific
behaviors, and undocumented behaviors.

This modules provides wrapper functions to abstract ctypes.CDLL() subtleties.
"""
from ctypes import CDLL
import os

def prependLdLibraryPath(path):
   """Prepend the given path to the $LD_LIBRARY_PATH environment variable.
   """
   currentPath = os.environ.get('LD_LIBRARY_PATH')
   ldPath = os.pathsep.join((path, currentPath)) if currentPath else path
   os.environ['LD_LIBRARY_PATH'] = ldPath

class SharedLibrary(CDLL):
   """A shared libary object (.so).
   """

   @staticmethod
   def find(name):
      """Locate a library.

      Locate a library from the given (relative) path. If the path does not
      exist, lookup $LD_LIBRARY_PATH environement variable, or resorts to the
      default system path (e.g. /lib64/).
      """
      if os.path.isfile(name):
         return os.path.realpath(name)

      # Lookup $LD_LIBRARY_PATH in case the environment has changed at
      # runtime, i.e. after $LD_LIBRARY_PATH was loaded by the dynamic
      # linker.
      for path in os.environ.get('LD_LIBRARY_PATH', "").split(os.pathsep):
         libPath = os.path.join(path, name)
         if os.path.isfile(libPath):
            return libPath

      # last resort, return the name provided in input and let dlopen() deal
      # with it.
      return name

   def __init__(self, name, *args, **kwargs):
      libPath = self.find(name)
      super().__init__(libPath, *args, **kwargs)

   def close(self):
      """Unload this shared library from the current process.

      Note:
      Libraries opened with ctypes.CDLL() are never closed with dlclose() (not
      even when the CDLL object is garbage-collected). On some platforms, that
      makes python hold a file descriptor open for each dlopen()'d library until
      the process finally exits. This is problematic for libraries that are
      loaded from a temporary directory because the latter can never be deleted
      since it contains a file with an open file descriptor.
      """
      from _ctypes import dlclose
      dlclose(self._handle)

   def __enter__(self):
      return self

   def __exit__(self, *args, **kwargs):
      self.close()
