Package Gnumed :: Package pycommon :: Module gmShellAPI |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmShellAPI

      1  __doc__ = """GNUmed general tools."""
      2
      3  #===========================================================================
      4  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
      5  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      6
      7
      8  # stdlib
      9  import os
     10  import sys
     11  import logging
     12  import subprocess
     13  import shlex
     14
     15
     16  _log = logging.getLogger('gm.shell')
     17
     18  #===========================================================================
     19 -def is_cmd_in_path(cmd=None):
     20
     21          _log.debug('cmd: [%s]', cmd)
     22          dirname = os.path.dirname(cmd)
     23          _log.debug('dir: [%s]', dirname)
     24          if dirname != u'':
     25                  _log.info('command with full or relative path, not searching in PATH for binary')
     26                  return (None, None)
     27
     28          env_paths = os.environ['PATH']
     29          _log.debug('${PATH}: %s', env_paths)
     30          for path in env_paths.split(os.pathsep):
     31                  candidate = os.path.join(path, cmd).encode(sys.getfilesystemencoding())
     32                  if os.access(candidate, os.X_OK):
     33                          _log.debug('found [%s]', candidate)
     34                          return (True, candidate.decode(sys.getfilesystemencoding()))
     35                  else:
     36                          _log.debug('not found: %s', candidate)
     37
     38          _log.debug('command not found in PATH')
     39
     40          return (False, None)
     41  #===========================================================================
     42 -def is_executable_by_wine(cmd=None):
     43
     44          if not cmd.startswith('wine'):
     45                  _log.debug('not a WINE call: %s', cmd)
     46                  return (False, None)
     47
     48          exe_path = cmd.encode(sys.getfilesystemencoding())
     49
     50          exe_path = exe_path[4:].strip().strip('"').strip()
     51          # [wine "/standard/unix/path/to/binary.exe"] ?
     52          if os.access(exe_path, os.R_OK):
     53                  _log.debug('WINE call with UNIX path: %s', exe_path)
     54                  return (True, cmd)
     55
     56          # detect [winepath]
     57          found, full_winepath_path = is_cmd_in_path(cmd = r'winepath')
     58          if not found:
     59                  _log.error('[winepath] not found, cannot check WINE call for Windows path conformance: %s', exe_path)
     60                  return (False, None)
     61
     62          # [wine "drive:\a\windows\path\to\binary.exe"] ?
     63          cmd_line = r'%s -u "%s"' % (
     64                  full_winepath_path.encode(sys.getfilesystemencoding()),
     65                  exe_path
     66          )
     67          _log.debug('converting Windows path to UNIX path: %s' % cmd_line)
     68          cmd_line = shlex.split(cmd_line)
     69          try:
     70                  winepath = subprocess.Popen (
     71                          cmd_line,
     72                          stdout = subprocess.PIPE,
     73                          stderr = subprocess.PIPE,
     74                          universal_newlines = True
     75                  )
     76          except OSError:
     77                  _log.exception('cannot run <winepath>')
     78                  return (False, None)
     79
     80          stdout, stderr = winepath.communicate()
     81          full_path = stdout.strip('\r\n')
     82          _log.debug('UNIX path: %s', full_path)
     83
     84          if winepath.returncode != 0:
     85                  _log.error('<winepath -u> returned [%s], failed to convert path', winepath.returncode)
     86                  return (False, None)
     87
     88          if os.access(full_path, os.R_OK):
     89                  _log.debug('WINE call with Windows path')
     90                  return (True, cmd)
     91
     92          _log.warning('Windows path [%s] not verifiable under UNIX: %s', exe_path, full_path)
     93          return (False, None)
     94  #===========================================================================
     95 -def detect_external_binary(binary=None):
     96          """<binary> is the name of the executable with or without .exe/.bat"""
     97
     98          _log.debug('searching for [%s]', binary)
     99
    100          binary = binary.lstrip()
    101
    102          # is it a sufficiently qualified, directly usable, explicit path ?
    103          if os.access(binary, os.X_OK):
    104                  _log.debug('found: executable explicit path')
    105                  return (True, binary)
    106
    107          # can it be found in PATH ?
    108          found, full_path = is_cmd_in_path(cmd = binary)
    109          if found:
    110                  if os.access(full_path, os.X_OK):
    111                          _log.debug('found: executable in ${PATH}')
    112                          return (True, full_path)
    113
    114          # does it seem to be a call via WINE ?
    115          is_wine_call, full_path = is_executable_by_wine(cmd = binary)
    116          if is_wine_call:
    117                  _log.debug('found: is valid WINE call')
    118                  return (True, full_path)
    119
    120          # maybe we can be a bit smart about Windows ?
    121          if os.name == 'nt':
    122                  # try .exe (but not if already .bat or .exe)
    123                  if not (binary.endswith('.exe') or binary.endswith('.bat')):
    124                          exe_binary = binary + r'.exe'
    125                          _log.debug('re-testing as %s', exe_binary)
    126                          found_dot_exe_binary, full_path = detect_external_binary(binary = exe_binary)
    127                          if found_dot_exe_binary:
    128                                  return (True, full_path)
    129                          # not found with .exe, so try .bat:
    130                          bat_binary = binary + r'.bat'
    131                          _log.debug('re-testing as %s', bat_binary)
    132                          found_bat_binary, full_path = detect_external_binary(binary = bat_binary)
    133                          if found_bat_binary:
    134                                  return (True, full_path)
    135          else:
    136                  _log.debug('not running under Windows, not testing .exe/.bat')
    137
    138          return (False, None)
    139  #===========================================================================
    140 -def find_first_binary(binaries=None):
    141          found = False
    142          binary = None
    143
    144          for cmd in binaries:
    145                  _log.debug('looking for [%s]', cmd)
    146                  if cmd is None:
    147                          continue
    148                  found, binary = detect_external_binary(binary = cmd)
    149                  if found:
    150                          break
    151
    152          return (found, binary)
    153  #===========================================================================
    154 -def run_command_in_shell(command=None, blocking=False, acceptable_return_codes=None):
    155          """Runs a command in a subshell via standard-C system().
    156
    157          <command>
    158                  The shell command to run including command line options.
    159          <blocking>
    160                  This will make the code *block* until the shell command exits.
    161                  It will likely only work on UNIX shells where "cmd &" makes sense.
    162
    163          http://stackoverflow.com/questions/35817/how-to-escape-os-system-calls-in-python
    164          """
    165          if acceptable_return_codes is None:
    166                  acceptable_return_codes = [0]
    167
    168          _log.debug('shell command >>>%s<<<', command)
    169          _log.debug('blocking: %s', blocking)
    170          _log.debug('acceptable return codes: %s', str(acceptable_return_codes))
    171
    172          # FIXME: command should be checked for shell exploits
    173          command = command.strip()
    174
    175          if os.name == 'nt':
    176                  # http://stackoverflow.com/questions/893203/bat-files-nonblocking-run-launch
    177                  if blocking is False:
    178                          if not command.startswith('start '):
    179                                  command = 'start "GNUmed" /B "%s"' % command
    180  #               elif blocking is True:
    181  #                       if not command.startswith('start '):
    182  #                               command = 'start "GNUmed" /WAIT /B "%s"' % command
    183          else:
    184                  # what the following hack does is this: the user indicated
    185                  # whether she wants non-blocking external display of files
    186                  # - the real way to go about this is to have a non-blocking command
    187                  #   in the line in the mailcap file for the relevant mime types
    188                  # - as non-blocking may not be desirable when *not* displaying
    189                  #   files from within GNUmed the really right way would be to
    190                  #   add a "test" clause to the non-blocking mailcap entry which
    191                  #   yields true if and only if GNUmed is running
    192                  # - however, this is cumbersome at best and not supported in
    193                  #   some mailcap implementations
    194                  # - so we allow the user to attempt some control over the process
    195                  #   from within GNUmed by setting a configuration option
    196                  # - leaving it None means to use the mailcap default or whatever
    197                  #   was specified in the command itself
    198                  # - True means: tack " &" onto the shell command if necessary
    199                  # - False means: remove " &" from the shell command if its there
    200                  # - all this, of course, only works in shells which support
    201                  #   detaching jobs with " &" (so, most POSIX shells)
    202                  if blocking is True:
    203                          if command[-2:] == ' &':
    204                                  command = command[:-2]
    205                  elif blocking is False:
    206                          if command[-2:] != ' &':
    207                                  command += ' &'
    208
    209          _log.info('running shell command >>>%s<<<', command)
    210          # FIXME: use subprocess.Popen()
    211          ret_val = os.system(command.encode(sys.getfilesystemencoding()))
    212          _log.debug('os.system() returned: [%s]', ret_val)
    213
    214          exited_normally = False
    215
    216          if not hasattr(os, 'WIFEXITED'):
    217                  _log.error('platform does not support exit status differentiation')
    218                  if ret_val in acceptable_return_codes:
    219                          _log.info('os.system() return value contained in acceptable return codes')
    220                          _log.info('continuing and hoping for the best')
    221                          return True
    222                  return exited_normally
    223
    224          _log.debug('exited via exit(): %s', os.WIFEXITED(ret_val))
    225          if os.WIFEXITED(ret_val):
    226                  _log.debug('exit code: [%s]', os.WEXITSTATUS(ret_val))
    227                  exited_normally = (os.WEXITSTATUS(ret_val) in acceptable_return_codes)
    228                  _log.debug('normal exit: %s', exited_normally)
    229          _log.debug('dumped core: %s', os.WCOREDUMP(ret_val))
    230          _log.debug('stopped by signal: %s', os.WIFSIGNALED(ret_val))
    231          if os.WIFSIGNALED(ret_val):
    232                  try:
    233                          _log.debug('STOP signal was: [%s]', os.WSTOPSIG(ret_val))
    234                  except AttributeError:
    235                          _log.debug('platform does not support os.WSTOPSIG()')
    236                  try:
    237                          _log.debug('TERM signal was: [%s]', os.WTERMSIG(ret_val))
    238                  except AttributeError:
    239                          _log.debug('platform does not support os.WTERMSIG()')
    240
    241          return exited_normally
    242  #===========================================================================
    243 -def run_first_available_in_shell(binaries=None, args=None, blocking=False, run_last_one_anyway=False, acceptable_return_codes=None):
    244
    245          found, binary = find_first_binary(binaries = binaries)
    246
    247          if not found:
    248                  _log.warning('cannot find any of: %s', binaries)
    249                  if run_last_one_anyway:
    250                          binary = binaries[-1]
    251                          _log.debug('falling back to trying to run [%s] anyway', binary)
    252                  else:
    253                          return False
    254
    255          return run_command_in_shell(command = '%s %s' % (binary, args), blocking = blocking, acceptable_return_codes = acceptable_return_codes)
    256  #===========================================================================
    257  # main
    258  #---------------------------------------------------------------------------
    259  if __name__ == '__main__':
    260
    261          if len(sys.argv) < 2:
    262                  sys.exit()
    263
    264          if sys.argv[1] != u'test':
    265                  sys.exit()
    266
    267          logging.basicConfig(level = logging.DEBUG)
    268          #---------------------------------------------------------
    269 -        def test_detect_external_binary():
    270                  found, path = detect_external_binary(binary = sys.argv[2])
    271                  if found:
    272                          print "found as:", path
    273                  else:
    274                          print sys.argv[2], "not found"
    275          #---------------------------------------------------------
    276 -        def test_run_command_in_shell():
    277                  print "-------------------------------------"
    278                  print "running:", sys.argv[2]
    279                  if run_command_in_shell(command=sys.argv[2], blocking=False):
    280                          print "-------------------------------------"
    281                          print "success"
    282                  else:
    283                          print "-------------------------------------"
    284                          print "failure, consult log"
    285          #---------------------------------------------------------
    286 -        def test_is_cmd_in_path():
    287                  print is_cmd_in_path(cmd = sys.argv[2])
    288          #---------------------------------------------------------
    289 -        def test_is_executable_by_wine():
    290                  print is_executable_by_wine(cmd = sys.argv[2])
    291          #---------------------------------------------------------
    292          test_run_command_in_shell()
    293          #test_detect_external_binary()
    294          #test_is_cmd_in_path()
    295          #test_is_executable_by_wine()
    296
    297  #===========================================================================
    298