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
9 import os
10 import sys
11 import logging
12 import subprocess
13 import shlex
14
15 _log = logging.getLogger('gm.shell')
16
17
19
20 _log.debug('cmd: [%s]', cmd)
21 dirname = os.path.dirname(cmd)
22 _log.debug('dir: [%s]', dirname)
23 if dirname != '':
24 _log.info('command with full or relative path, not searching in PATH for binary')
25 return (None, None)
26
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)
32 if os.access(candidate, os.X_OK):
33 _log.debug('found [%s]', candidate)
34 return (True, candidate)
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
44
45 if not cmd.startswith('wine'):
46 _log.debug('not a WINE call: %s', cmd)
47 return (False, None)
48
49 exe_path = cmd.encode(sys.getfilesystemencoding())
50
51 exe_path = exe_path[4:].strip().strip('"').strip()
52
53 if os.access(exe_path, os.R_OK):
54 _log.debug('WINE call with UNIX path: %s', exe_path)
55 return (True, cmd)
56
57
58 found, full_winepath_path = is_cmd_in_path(cmd = r'winepath')
59 if not found:
60 _log.error('[winepath] not found, cannot check WINE call for Windows path conformance: %s', exe_path)
61 return (False, None)
62
63
64 cmd_line = r'%s -u "%s"' % (
65 full_winepath_path.encode(sys.getfilesystemencoding()),
66 exe_path
67 )
68 _log.debug('converting Windows path to UNIX path: %s' % cmd_line)
69 cmd_line = shlex.split(cmd_line)
70 try:
71 winepath = subprocess.Popen (
72 cmd_line,
73 stdout = subprocess.PIPE,
74 stderr = subprocess.PIPE,
75 universal_newlines = True
76 )
77 except OSError:
78 _log.exception('cannot run <winepath>')
79 return (False, None)
80
81 stdout, stderr = winepath.communicate()
82 full_path = stdout.strip('\r\n')
83 _log.debug('UNIX path: %s', full_path)
84
85 if winepath.returncode != 0:
86 _log.error('<winepath -u> returned [%s], failed to convert path', winepath.returncode)
87 return (False, None)
88
89 if os.access(full_path, os.R_OK):
90 _log.debug('WINE call with Windows path')
91 return (True, cmd)
92
93 _log.warning('Windows path [%s] not verifiable under UNIX: %s', exe_path, full_path)
94 return (False, None)
95
96
98 """<binary> is the name of the executable with or without .exe/.bat"""
99
100 _log.debug('searching for [%s]', binary)
101
102 binary = binary.lstrip()
103
104
105 if os.access(binary, os.X_OK):
106 _log.debug('found: executable explicit path')
107 return (True, binary)
108
109
110 found, full_path = is_cmd_in_path(cmd = binary)
111 if found:
112 if os.access(full_path, os.X_OK):
113 _log.debug('found: executable in ${PATH}')
114 return (True, full_path)
115
116
117 is_wine_call, full_path = is_executable_by_wine(cmd = binary)
118 if is_wine_call:
119 _log.debug('found: is valid WINE call')
120 return (True, full_path)
121
122
123 if os.name == 'nt':
124
125 if not (binary.endswith('.exe') or binary.endswith('.bat')):
126 exe_binary = binary + r'.exe'
127 _log.debug('re-testing as %s', exe_binary)
128 found_dot_exe_binary, full_path = detect_external_binary(binary = exe_binary)
129 if found_dot_exe_binary:
130 return (True, full_path)
131
132 bat_binary = binary + r'.bat'
133 _log.debug('re-testing as %s', bat_binary)
134 found_bat_binary, full_path = detect_external_binary(binary = bat_binary)
135 if found_bat_binary:
136 return (True, full_path)
137 else:
138 _log.debug('not running under Windows, not testing .exe/.bat')
139
140 return (False, None)
141
142
144 found = False
145 binary = None
146
147 for cmd in binaries:
148 _log.debug('looking for [%s]', cmd)
149 if cmd is None:
150 continue
151 found, binary = detect_external_binary(binary = cmd)
152 if found:
153 break
154
155 return (found, binary)
156
157
159 """Runs a command in a subshell via standard-C system().
160
161 <command>
162 The shell command to run including command line options.
163 <blocking>
164 This will make the code *block* until the shell command exits.
165 It will likely only work on UNIX shells where "cmd &" makes sense.
166
167 http://stackoverflow.com/questions/35817/how-to-escape-os-system-calls-in-python
168 """
169 if acceptable_return_codes is None:
170 acceptable_return_codes = [0]
171
172 _log.debug('shell command >>>%s<<<', command)
173 _log.debug('blocking: %s', blocking)
174 _log.debug('acceptable return codes: %s', str(acceptable_return_codes))
175
176
177 command = command.strip()
178
179 if os.name == 'nt':
180
181 if blocking is False:
182 if not command.startswith('start '):
183 command = 'start "GNUmed" /B "%s"' % command
184
185
186
187 else:
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 if blocking is True:
207 command = command.rstrip(' &')
208 elif blocking is False:
209 if not command.strip().endswith('&'):
210 command += ' &'
211
212 _log.info('running shell command >>>%s<<<', command)
213
214 ret_val = os.system(command.encode(sys.getfilesystemencoding()))
215 _log.debug('os.system() returned: [%s]', ret_val)
216
217 exited_normally = False
218
219 if not hasattr(os, 'WIFEXITED'):
220 _log.error('platform does not support exit status differentiation')
221 if ret_val in acceptable_return_codes:
222 _log.info('os.system() return value contained in acceptable return codes')
223 _log.info('continuing and hoping for the best')
224 return True
225 return exited_normally
226
227 _log.debug('exited via exit(): %s', os.WIFEXITED(ret_val))
228 if os.WIFEXITED(ret_val):
229 _log.debug('exit code: [%s]', os.WEXITSTATUS(ret_val))
230 exited_normally = (os.WEXITSTATUS(ret_val) in acceptable_return_codes)
231 _log.debug('normal exit: %s', exited_normally)
232 _log.debug('dumped core: %s', os.WCOREDUMP(ret_val))
233 _log.debug('stopped by signal: %s', os.WIFSIGNALED(ret_val))
234 if os.WIFSIGNALED(ret_val):
235 try:
236 _log.debug('STOP signal was: [%s]', os.WSTOPSIG(ret_val))
237 except AttributeError:
238 _log.debug('platform does not support os.WSTOPSIG()')
239 try:
240 _log.debug('TERM signal was: [%s]', os.WTERMSIG(ret_val))
241 except AttributeError:
242 _log.debug('platform does not support os.WTERMSIG()')
243
244 return exited_normally
245
246
248
249 found, binary = find_first_binary(binaries = binaries)
250
251 if not found:
252 _log.warning('cannot find any of: %s', binaries)
253 if run_last_one_anyway:
254 binary = binaries[-1]
255 _log.debug('falling back to trying to run [%s] anyway', binary)
256 else:
257 return False
258
259 return run_command_in_shell(command = '%s %s' % (binary, args), blocking = blocking, acceptable_return_codes = acceptable_return_codes)
260
261
263 lines2log = ['process output:']
264 if stdout is not None:
265 lines2log.extend([ ' STDOUT: %s' % line for line in stdout.split('\n') ])
266 if stderr is not None:
267 lines2log.extend([ ' STDERR: %s' % line for line in stderr.split('\n') ])
268 _log.log(level, '\n'.join(lines2log))
269
270
271 -def run_process(cmd_line=None, timeout=None, encoding='utf8', input_data=None, acceptable_return_codes=None, verbose=False):
272 assert (cmd_line is not None), '<cmd_line> must not be None'
273
274 if acceptable_return_codes is None:
275 acceptable_return_codes = [0]
276 _log.info('running: %s' % cmd_line)
277 try:
278 if input_data is None:
279 proc_result = subprocess.run (
280 args = cmd_line,
281 stdin = subprocess.PIPE,
282 stdout = subprocess.PIPE,
283 stderr = subprocess.PIPE,
284 timeout = timeout,
285 encoding = encoding,
286 errors = 'replace'
287 )
288 else:
289 proc_result = subprocess.run (
290 args = cmd_line,
291 input = input_data,
292 stdout = subprocess.PIPE,
293 stderr = subprocess.PIPE,
294 timeout = timeout,
295 encoding = encoding,
296 errors = 'replace'
297 )
298 except (subprocess.TimeoutExpired, FileNotFoundError):
299 _log.exception('there was a problem running external process')
300 return False, -1, ''
301
302 _log.info('exit code [%s]', proc_result.returncode)
303 if verbose:
304 _log_output(logging.DEBUG, stdout = proc_result.stdout, stderr = proc_result.stderr)
305 if proc_result.returncode not in acceptable_return_codes:
306 _log.error('there was a problem executing the external process')
307 _log.debug('expected one of: %s', acceptable_return_codes)
308 if not verbose:
309 _log_output(logging.ERROR, stdout = proc_result.stdout, stderr = proc_result.stderr)
310 return False, proc_result.returncode, ''
311
312 return True, proc_result.returncode, proc_result.stdout
313
314
315
316
317 if __name__ == '__main__':
318
319 if len(sys.argv) < 2:
320 sys.exit()
321
322 if sys.argv[1] != 'test':
323 sys.exit()
324
325 logging.basicConfig(level = logging.DEBUG)
326
328 found, path = detect_external_binary(binary = sys.argv[2])
329 if found:
330 print("found as:", path)
331 else:
332 print(sys.argv[2], "not found")
333
335 print("-------------------------------------")
336 print("running:", sys.argv[2])
337 if run_command_in_shell(command=sys.argv[2], blocking=False):
338 print("-------------------------------------")
339 print("success")
340 else:
341 print("-------------------------------------")
342 print("failure, consult log")
343
346
349
350
351
352 test_is_cmd_in_path()
353
354
355
356