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

Source Code for Module Gnumed.pycommon.gmShellAPI

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