blob: b20239658ed1c489636fef380ead80d640507cfe [file] [log] [blame]
Michael Walshc3b512e2017-02-20 15:59:01 -06001#!/usr/bin/env python
2
3r"""
4This module provides command execution functions such as cmd_fnc and cmd_fnc_u.
5"""
6
Michael Walsh21083d22018-06-01 14:19:32 -05007import os
Michael Walshc3b512e2017-02-20 15:59:01 -06008import sys
9import subprocess
Michael Walshf41fac82017-08-02 15:05:24 -050010import collections
Michael Walsh21083d22018-06-01 14:19:32 -050011import signal
12import time
George Keishing16244c22019-01-31 16:16:14 +000013import re
14import inspect
Michael Walshc3b512e2017-02-20 15:59:01 -060015
Michael Walshc3b512e2017-02-20 15:59:01 -060016import gen_print as gp
17import gen_valid as gv
18import gen_misc as gm
Michael Walsh1429e122019-05-20 10:06:18 -050019import func_args as fa
Michael Walshd6901502017-11-14 12:58:37 -060020
21robot_env = gp.robot_env
22
Michael Walshafc53a22017-04-12 15:52:28 -050023if robot_env:
Michael Walsha3e2f532018-01-10 13:43:42 -060024 from robot.libraries.BuiltIn import BuiltIn
Michael Walshc3b512e2017-02-20 15:59:01 -060025
26
Michael Walsh21083d22018-06-01 14:19:32 -050027# cmd_fnc and cmd_fnc_u should now be considered deprecated. shell_cmd and
28# t_shell_cmd should be used instead.
Michael Walshc3b512e2017-02-20 15:59:01 -060029def cmd_fnc(cmd_buf,
30 quiet=None,
31 test_mode=None,
Michael Walshafc53a22017-04-12 15:52:28 -050032 debug=0,
Michael Walshc3b512e2017-02-20 15:59:01 -060033 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -050034 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -060035 return_stderr=0,
36 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -060037 r"""
Michael Walshcfe9fed2017-09-12 17:13:10 -050038 Run the given command in a shell and return the shell return code and the
39 output.
Michael Walshc3b512e2017-02-20 15:59:01 -060040
41 Description of arguments:
42 cmd_buf The command string to be run in a shell.
43 quiet Indicates whether this function should run
Michael Walshcfe9fed2017-09-12 17:13:10 -050044 the print_issuing() function which prints
45 "Issuing: <cmd string>" to stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060046 test_mode If test_mode is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050047 not actually run the command. If
48 print_output is set, it will print
49 "(test_mode) Issuing: <cmd string>" to
50 stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060051 debug If debug is set, this function will print
52 extra debug info.
53 print_output If this is set, this function will print
Michael Walshcfe9fed2017-09-12 17:13:10 -050054 the stdout/stderr generated by the shell
55 command.
Michael Walshc3b512e2017-02-20 15:59:01 -060056 show_err If show_err is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050057 print a standardized error report if the
58 shell command returns non-zero.
59 return_stderr If return_stderr is set, this function
60 will process the stdout and stderr streams
61 from the shell command separately. It
62 will also return stderr in addition to the
63 return code and the stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060064 """
65
Michael Walshcfe9fed2017-09-12 17:13:10 -050066 # Determine default values.
Michael Walshc3b512e2017-02-20 15:59:01 -060067 quiet = int(gm.global_default(quiet, 0))
68 test_mode = int(gm.global_default(test_mode, 0))
Michael Walshc3b512e2017-02-20 15:59:01 -060069
70 if debug:
Michael Walshafc53a22017-04-12 15:52:28 -050071 gp.print_vars(cmd_buf, quiet, test_mode, debug)
Michael Walshc3b512e2017-02-20 15:59:01 -060072
Michael Walshec01a6f2019-08-01 12:43:20 -050073 err_msg = gv.valid_value(cmd_buf)
Michael Walshc3b512e2017-02-20 15:59:01 -060074 if err_msg != "":
75 raise ValueError(err_msg)
76
77 if not quiet:
Michael Walshafc53a22017-04-12 15:52:28 -050078 gp.pissuing(cmd_buf, test_mode)
Michael Walshc3b512e2017-02-20 15:59:01 -060079
80 if test_mode:
Michael Walshcfe9fed2017-09-12 17:13:10 -050081 if return_stderr:
82 return 0, "", ""
83 else:
84 return 0, ""
85
86 if return_stderr:
87 err_buf = ""
88 stderr = subprocess.PIPE
89 else:
90 stderr = subprocess.STDOUT
Michael Walshc3b512e2017-02-20 15:59:01 -060091
92 sub_proc = subprocess.Popen(cmd_buf,
93 bufsize=1,
94 shell=True,
Michael Walsh3ba8ecd2018-04-24 11:33:25 -050095 executable='/bin/bash',
Michael Walshc3b512e2017-02-20 15:59:01 -060096 stdout=subprocess.PIPE,
Michael Walshcfe9fed2017-09-12 17:13:10 -050097 stderr=stderr)
Michael Walshc3b512e2017-02-20 15:59:01 -060098 out_buf = ""
Michael Walshcfe9fed2017-09-12 17:13:10 -050099 if return_stderr:
100 for line in sub_proc.stderr:
George Keishing36efbc02018-12-12 10:18:23 -0600101 try:
102 err_buf += line
103 except TypeError:
104 line = line.decode("utf-8")
105 err_buf += line
Michael Walshcfe9fed2017-09-12 17:13:10 -0500106 if not print_output:
107 continue
Michael Walshc108e422019-03-28 12:27:18 -0500108 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600109 for line in sub_proc.stdout:
George Keishing36efbc02018-12-12 10:18:23 -0600110 try:
111 out_buf += line
112 except TypeError:
113 line = line.decode("utf-8")
114 out_buf += line
Michael Walshc3b512e2017-02-20 15:59:01 -0600115 if not print_output:
116 continue
Michael Walshc108e422019-03-28 12:27:18 -0500117 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600118 if print_output and not robot_env:
119 sys.stdout.flush()
120 sub_proc.communicate()
121 shell_rc = sub_proc.returncode
Michael Walsha3e2f532018-01-10 13:43:42 -0600122 if shell_rc != 0:
123 err_msg = "The prior shell command failed.\n"
Michael Walsh1429e122019-05-20 10:06:18 -0500124 err_msg += gp.sprint_var(shell_rc, gp.hexa())
Michael Walshcfe9fed2017-09-12 17:13:10 -0500125 if not print_output:
126 err_msg += "out_buf:\n" + out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600127
Michael Walsha3e2f532018-01-10 13:43:42 -0600128 if show_err:
Michael Walshc108e422019-03-28 12:27:18 -0500129 gp.print_error_report(err_msg)
Michael Walsha3e2f532018-01-10 13:43:42 -0600130 if not ignore_err:
131 if robot_env:
132 BuiltIn().fail(err_msg)
133 else:
134 raise ValueError(err_msg)
Michael Walshcfe9fed2017-09-12 17:13:10 -0500135
136 if return_stderr:
137 return shell_rc, out_buf, err_buf
138 else:
139 return shell_rc, out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600140
Michael Walshc3b512e2017-02-20 15:59:01 -0600141
Michael Walshc3b512e2017-02-20 15:59:01 -0600142def cmd_fnc_u(cmd_buf,
143 quiet=None,
144 debug=None,
145 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500146 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -0600147 return_stderr=0,
148 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -0600149 r"""
150 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details.
151
152 Note the "u" in "cmd_fnc_u" stands for "unconditional".
153 """
154
155 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500156 print_output=print_output, show_err=show_err,
Michael Walsha3e2f532018-01-10 13:43:42 -0600157 return_stderr=return_stderr, ignore_err=ignore_err)
Michael Walshc3b512e2017-02-20 15:59:01 -0600158
Michael Walshf41fac82017-08-02 15:05:24 -0500159
Michael Walshf41fac82017-08-02 15:05:24 -0500160def parse_command_string(command_string):
Michael Walshf41fac82017-08-02 15:05:24 -0500161 r"""
162 Parse a bash command-line command string and return the result as a
163 dictionary of parms.
164
165 This can be useful for answering questions like "What did the user specify
166 as the value for parm x in the command string?".
167
168 This function expects the command string to follow the following posix
169 conventions:
170 - Short parameters:
171 -<parm name><space><arg value>
172 - Long parameters:
173 --<parm name>=<arg value>
174
175 The first item in the string will be considered to be the command. All
176 values not conforming to the specifications above will be considered
177 positional parms. If there are multiple parms with the same name, they
178 will be put into a list (see illustration below where "-v" is specified
179 multiple times).
180
181 Description of argument(s):
182 command_string The complete command string including all
183 parameters and arguments.
184
185 Sample input:
186
187 robot_cmd_buf: robot -v
188 OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot no' -v
189 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
190 quiet:0 -v test_mode:0 -v debug:0
191 --outputdir='/home/user1/status/children/'
192 --output=dummy1.Auto_reboot.170802.124544.output.xml
193 --log=dummy1.Auto_reboot.170802.124544.log.html
194 --report=dummy1.Auto_reboot.170802.124544.report.html
195 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
196
197 Sample output:
198
199 robot_cmd_buf_dict:
200 robot_cmd_buf_dict[command]: robot
201 robot_cmd_buf_dict[v]:
202 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1
203 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto
204 Reboot no
205 robot_cmd_buf_dict[v][2]:
206 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
207 robot_cmd_buf_dict[v][3]: quiet:0
208 robot_cmd_buf_dict[v][4]: test_mode:0
209 robot_cmd_buf_dict[v][5]: debug:0
210 robot_cmd_buf_dict[outputdir]:
211 /home/user1/status/children/
212 robot_cmd_buf_dict[output]:
213 dummy1.Auto_reboot.170802.124544.output.xml
214 robot_cmd_buf_dict[log]:
215 dummy1.Auto_reboot.170802.124544.log.html
216 robot_cmd_buf_dict[report]:
217 dummy1.Auto_reboot.170802.124544.report.html
218 robot_cmd_buf_dict[positional]:
219 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
220 """
221
222 # We want the parms in the string broken down the way bash would do it,
223 # so we'll call upon bash to do that by creating a simple inline bash
224 # function.
225 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
226 " echo $parm ; done ; }"
227
228 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
229 quiet=1, print_output=0)
230 command_string_list = outbuf.rstrip("\n").split("\n")
231
232 command_string_dict = collections.OrderedDict()
233 ix = 1
234 command_string_dict['command'] = command_string_list[0]
235 while ix < len(command_string_list):
236 if command_string_list[ix].startswith("--"):
237 key, value = command_string_list[ix].split("=")
238 key = key.lstrip("-")
239 elif command_string_list[ix].startswith("-"):
240 key = command_string_list[ix].lstrip("-")
241 ix += 1
242 try:
243 value = command_string_list[ix]
244 except IndexError:
245 value = ""
246 else:
247 key = 'positional'
248 value = command_string_list[ix]
249 if key in command_string_dict:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500250 if isinstance(command_string_dict[key], str):
Michael Walshf41fac82017-08-02 15:05:24 -0500251 command_string_dict[key] = [command_string_dict[key]]
252 command_string_dict[key].append(value)
253 else:
254 command_string_dict[key] = value
255 ix += 1
256
257 return command_string_dict
Michael Walsh21083d22018-06-01 14:19:32 -0500258
259
260# Save the original SIGALRM handler for later restoration by shell_cmd.
261original_sigalrm_handler = signal.getsignal(signal.SIGALRM)
262
263
264def shell_cmd_timed_out(signal_number,
265 frame):
266 r"""
267 Handle an alarm signal generated during the shell_cmd function.
268 """
269
270 gp.dprint_executing()
Michael Walsh3fb26182019-08-28 16:51:05 -0500271 global command_timed_out
272 command_timed_out = True
Michael Walsh21083d22018-06-01 14:19:32 -0500273 # Get subprocess pid from shell_cmd's call stack.
274 sub_proc = gp.get_stack_var('sub_proc', 0)
275 pid = sub_proc.pid
Michael Walsh3fb26182019-08-28 16:51:05 -0500276 gp.dprint_var(pid)
277 # Terminate the child process group.
278 os.killpg(pid, signal.SIGKILL)
Michael Walsh21083d22018-06-01 14:19:32 -0500279 # Restore the original SIGALRM handler.
280 signal.signal(signal.SIGALRM, original_sigalrm_handler)
281
282 return
283
284
285def shell_cmd(command_string,
286 quiet=None,
Michael Walsh55abd1b2018-08-31 13:00:50 -0500287 print_output=None,
Michael Walsh21083d22018-06-01 14:19:32 -0500288 show_err=1,
289 test_mode=0,
290 time_out=None,
291 max_attempts=1,
292 retry_sleep_time=5,
Michael Walsh3fb26182019-08-28 16:51:05 -0500293 valid_rcs=[0],
Michael Walsh21083d22018-06-01 14:19:32 -0500294 ignore_err=None,
Michael Walshfaafa9c2018-06-27 16:39:31 -0500295 return_stderr=0,
296 fork=0):
Michael Walsh21083d22018-06-01 14:19:32 -0500297 r"""
298 Run the given command string in a shell and return a tuple consisting of
299 the shell return code and the output.
300
301 Description of argument(s):
302 command_string The command string to be run in a shell
303 (e.g. "ls /tmp").
304 quiet If set to 0, this function will print
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500305 "Issuing: <cmd string>" to stdout. When
306 the quiet argument is set to None, this
307 function will assign a default value by
308 searching upward in the stack for the
309 quiet variable value. If no such value is
310 found, quiet is set to 0.
Michael Walsh21083d22018-06-01 14:19:32 -0500311 print_output If this is set, this function will print
312 the stdout/stderr generated by the shell
313 command to stdout.
314 show_err If show_err is set, this function will
315 print a standardized error report if the
316 shell command fails (i.e. if the shell
317 command returns a shell_rc that is not in
Michael Walsh3fb26182019-08-28 16:51:05 -0500318 valid_rcs). Note: Error text is only
319 printed if ALL attempts to run the
Michael Walsh21083d22018-06-01 14:19:32 -0500320 command_string fail. In other words, if
321 the command execution is ultimately
322 successful, initial failures are hidden.
323 test_mode If test_mode is set, this function will
324 not actually run the command. If
325 print_output is also set, this function
326 will print "(test_mode) Issuing: <cmd
327 string>" to stdout. A caller should call
328 shell_cmd directly if they wish to have
329 the command string run unconditionally.
330 They should call the t_shell_cmd wrapper
331 (defined below) if they wish to run the
332 command string only if the prevailing
333 test_mode variable is set to 0.
334 time_out A time-out value expressed in seconds. If
335 the command string has not finished
336 executing within <time_out> seconds, it
337 will be halted and counted as an error.
338 max_attempts The max number of attempts that should be
339 made to run the command string.
340 retry_sleep_time The number of seconds to sleep between
341 attempts.
Michael Walsh3fb26182019-08-28 16:51:05 -0500342 valid_rcs A list of integers indicating which
Michael Walsh21083d22018-06-01 14:19:32 -0500343 shell_rc values are not to be considered
344 errors.
345 ignore_err Ignore error means that a failure
346 encountered by running the command string
347 will not be raised as a python exception.
348 When the ignore_err argument is set to
349 None, this function will assign a default
350 value by searching upward in the stack for
351 the ignore_err variable value. If no such
352 value is found, ignore_err is set to 1.
353 return_stderr If return_stderr is set, this function
354 will process the stdout and stderr streams
355 from the shell command separately. In
356 such a case, the tuple returned by this
357 function will consist of three values
358 rather than just two: rc, stdout, stderr.
Michael Walshfaafa9c2018-06-27 16:39:31 -0500359 fork Run the command string asynchronously
360 (i.e. don't wait for status of the child
361 process and don't try to get
Michael Walsh4d5f6682019-09-24 14:23:40 -0500362 stdout/stderr) and return the Popen object
363 created by the subprocess.popen()
364 function. See the kill_cmd function for
365 details on how to process the popen object.
Michael Walsh21083d22018-06-01 14:19:32 -0500366 """
367
Michael Walsh3fb26182019-08-28 16:51:05 -0500368 err_msg = gv.valid_value(command_string)
369 if err_msg:
370 raise ValueError(err_msg)
371
Michael Walsh21083d22018-06-01 14:19:32 -0500372 # Assign default values to some of the arguments to this function.
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500373 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
Michael Walsh55abd1b2018-08-31 13:00:50 -0500374 print_output = int(gm.dft(print_output, not quiet))
Michael Walsh45fead42018-09-26 17:20:48 -0500375 show_err = int(show_err)
Michael Walsh3fb26182019-08-28 16:51:05 -0500376 ignore_err = int(gm.dft(ignore_err, gp.get_stack_var('ignore_err', 1)))
Michael Walsh21083d22018-06-01 14:19:32 -0500377
Michael Walsh3fb26182019-08-28 16:51:05 -0500378 gp.qprint_issuing(command_string, test_mode)
Michael Walsh21083d22018-06-01 14:19:32 -0500379 if test_mode:
Michael Walsh3fb26182019-08-28 16:51:05 -0500380 return (0, "", "") if return_stderr else (0, "")
Michael Walsh21083d22018-06-01 14:19:32 -0500381
Michael Walsh3fb26182019-08-28 16:51:05 -0500382 # Convert a string python dictionary definition to a dictionary.
383 valid_rcs = fa.source_to_object(valid_rcs)
Michael Walsh21083d22018-06-01 14:19:32 -0500384 # Convert each list entry to a signed value.
Michael Walsh3fb26182019-08-28 16:51:05 -0500385 valid_rcs = [gm.to_signed(x) for x in valid_rcs]
Michael Walsh21083d22018-06-01 14:19:32 -0500386
Michael Walsh3fb26182019-08-28 16:51:05 -0500387 stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT
Michael Walsh21083d22018-06-01 14:19:32 -0500388
Michael Walsh3fb26182019-08-28 16:51:05 -0500389 # Write all output to func_out_history_buf rather than directly to
390 # stdout. This allows us to decide what to print after all attempts to
391 # run the command string have been made. func_out_history_buf will
392 # contain the complete history from the current invocation of this
393 # function.
394 global command_timed_out
395 command_timed_out = False
396 func_out_history_buf = ""
Michael Walsh21083d22018-06-01 14:19:32 -0500397 for attempt_num in range(1, max_attempts + 1):
398 sub_proc = subprocess.Popen(command_string,
Michael Walsh3fb26182019-08-28 16:51:05 -0500399 preexec_fn=os.setsid,
Michael Walsh21083d22018-06-01 14:19:32 -0500400 bufsize=1,
401 shell=True,
Michael Walsh3fb26182019-08-28 16:51:05 -0500402 universal_newlines=True,
Michael Walsh21083d22018-06-01 14:19:32 -0500403 executable='/bin/bash',
404 stdout=subprocess.PIPE,
405 stderr=stderr)
Michael Walshfaafa9c2018-06-27 16:39:31 -0500406 if fork:
Michael Walsh4d5f6682019-09-24 14:23:40 -0500407 return sub_proc
Michael Walsh3fb26182019-08-28 16:51:05 -0500408
409 if time_out:
410 command_timed_out = False
Michael Walsh21083d22018-06-01 14:19:32 -0500411 # Designate a SIGALRM handling function and set alarm.
412 signal.signal(signal.SIGALRM, shell_cmd_timed_out)
413 signal.alarm(time_out)
414 try:
Michael Walsh3fb26182019-08-28 16:51:05 -0500415 stdout_buf, stderr_buf = sub_proc.communicate()
Michael Walsh21083d22018-06-01 14:19:32 -0500416 except IOError:
417 command_timed_out = True
Michael Walsh21083d22018-06-01 14:19:32 -0500418 # Restore the original SIGALRM handler and clear the alarm.
419 signal.signal(signal.SIGALRM, original_sigalrm_handler)
420 signal.alarm(0)
Michael Walsh3fb26182019-08-28 16:51:05 -0500421
422 # Output from this loop iteration is written to func_out_buf for
423 # later processing. This can include stdout, stderr and our own error
424 # messages.
425 func_out_buf = ""
426 if print_output:
427 if return_stderr:
428 func_out_buf += stderr_buf
429 func_out_buf += stdout_buf
430 shell_rc = sub_proc.returncode
431 if shell_rc in valid_rcs:
Michael Walsh21083d22018-06-01 14:19:32 -0500432 break
433 err_msg = "The prior shell command failed.\n"
Michael Walsh21083d22018-06-01 14:19:32 -0500434 err_msg += gp.sprint_var(attempt_num)
Michael Walsh3fb26182019-08-28 16:51:05 -0500435 err_msg += gp.sprint_vars(command_string, command_timed_out, time_out)
436 err_msg += gp.sprint_varx("child_pid", sub_proc.pid)
437 err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa())
Michael Walsh21083d22018-06-01 14:19:32 -0500438 if not print_output:
439 if return_stderr:
Michael Walsh3fb26182019-08-28 16:51:05 -0500440 err_msg += "stderr_buf:\n" + stderr_buf
441 err_msg += "stdout_buf:\n" + stdout_buf
Michael Walsh21083d22018-06-01 14:19:32 -0500442 if show_err:
Michael Walsh3fb26182019-08-28 16:51:05 -0500443 func_out_buf += gp.sprint_error_report(err_msg)
Michael Walsh21083d22018-06-01 14:19:32 -0500444 if attempt_num < max_attempts:
Michael Walsh3fb26182019-08-28 16:51:05 -0500445 cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")"
446 if show_err:
447 func_out_buf += gp.sprint_issuing(cmd_buf)
448 exec(cmd_buf)
449 func_out_history_buf += func_out_buf
Michael Walsh21083d22018-06-01 14:19:32 -0500450
Michael Walsh3fb26182019-08-28 16:51:05 -0500451 if shell_rc in valid_rcs:
452 gp.gp_print(func_out_buf)
453 else:
454 if show_err:
455 gp.gp_print(func_out_history_buf, stream='stderr')
456 else:
457 # There is no error information to show so just print output from
458 # last loop iteration.
459 gp.gp_print(func_out_buf)
Michael Walsh21083d22018-06-01 14:19:32 -0500460 if not ignore_err:
Michael Walsh3fb26182019-08-28 16:51:05 -0500461 # If the caller has already asked to show error info, avoid
462 # repeating that in the failure message.
463 err_msg = "The prior shell command failed.\n" if show_err \
464 else err_msg
Michael Walsh21083d22018-06-01 14:19:32 -0500465 if robot_env:
466 BuiltIn().fail(err_msg)
467 else:
Michael Walsh3fb26182019-08-28 16:51:05 -0500468 raise ValueError(err_msg)
Michael Walsh21083d22018-06-01 14:19:32 -0500469
Michael Walsh3fb26182019-08-28 16:51:05 -0500470 return (shell_rc, stdout_buf, stderr_buf) if return_stderr \
471 else (shell_rc, stdout_buf)
Michael Walsh21083d22018-06-01 14:19:32 -0500472
473
474def t_shell_cmd(command_string, **kwargs):
475 r"""
476 Search upward in the the call stack to obtain the test_mode argument, add
477 it to kwargs and then call shell_cmd and return the result.
478
479 See shell_cmd prolog for details on all arguments.
480 """
481
482 if 'test_mode' in kwargs:
483 error_message = "Programmer error - test_mode is not a valid" +\
484 " argument to this function."
485 gp.print_error_report(error_message)
486 exit(1)
487
Michael Walsh3fb26182019-08-28 16:51:05 -0500488 test_mode = int(gp.get_stack_var('test_mode', 0))
Michael Walsh21083d22018-06-01 14:19:32 -0500489 kwargs['test_mode'] = test_mode
490
491 return shell_cmd(command_string, **kwargs)
George Keishing16244c22019-01-31 16:16:14 +0000492
493
Michael Walsh4d5f6682019-09-24 14:23:40 -0500494def kill_cmd(popen, sig=signal.SIGTERM):
495 r"""
496 Kill the subprocess represented by the Popen object and return a tuple
497 consisting of the shell return code and the output.
498
499 This function is meant to be used as the follow-up for a call to
500 shell_cmd(..., fork=1).
501
502 Example:
503 popen = shell_cmd("some_pgm.py", fork=1)
504 ...
505 shell_rc, output = kill_cmd(popen)
506
507 Description of argument(s):
508 popen A Popen object returned by the
509 subprocess.Popen() command.
510 sig The signal to be sent to the child process.
511 """
512
513 gp.dprint_var(popen.pid)
514 os.killpg(popen.pid, sig)
515 stdout, stderr = popen.communicate()
516 shell_rc = popen.returncode
517 return (shell_rc, stdout, stderr) if stderr else (shell_rc, stdout)
518
519
George Keishing16244c22019-01-31 16:16:14 +0000520def re_order_kwargs(stack_frame_ix, **kwargs):
521 r"""
522 Re-order the kwargs to match the order in which they were specified on a
523 function invocation and return as an ordered dictionary.
524
525 Note that this re_order_kwargs function should not be necessary in python
526 versions 3.6 and beyond.
527
528 Example:
529
530 The caller calls func1 like this:
531
532 func1('mike', arg1='one', arg2='two', arg3='three')
533
534 And func1 is defined as follows:
535
536 def func1(first_arg, **kwargs):
537
538 kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs)
539
540 The kwargs dictionary before calling re_order_kwargs (where order is not
541 guaranteed):
542
543 kwargs:
544 kwargs[arg3]: three
545 kwargs[arg2]: two
546 kwargs[arg1]: one
547
548 The kwargs dictionary after calling re_order_kwargs:
549
550 kwargs:
551 kwargs[arg1]: one
552 kwargs[arg2]: two
553 kwargs[arg3]: three
554
555 Note that the re-ordered kwargs match the order specified on the call to
556 func1.
557
558 Description of argument(s):
559 stack_frame_ix The stack frame of the function whose
560 kwargs values must be re-ordered. 0 is
561 the stack frame of re_order_kwargs, 1 is
562 the stack from of its caller and so on.
563 kwargs The keyword argument dictionary which is
564 to be re-ordered.
565 """
566
567 new_kwargs = collections.OrderedDict()
568
569 # Get position number of first keyword on the calling line of code.
570 (args, varargs, keywords, locals) =\
571 inspect.getargvalues(inspect.stack()[stack_frame_ix][0])
572 first_kwarg_pos = 1 + len(args)
573 if varargs is not None:
574 first_kwarg_pos += len(locals[varargs])
575 for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)):
576 # This will result in an arg_name value such as "arg1='one'".
577 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2)
578 # Continuing with the prior example, the following line will result
579 # in key being set to 'arg1'.
580 key = arg_name.split('=')[0]
581 new_kwargs[key] = kwargs[key]
582
583 return new_kwargs
584
585
586def default_arg_delim(arg_dashes):
587 r"""
588 Return the default argument delimiter value for the given arg_dashes value.
589
590 Note: this function is useful for functions that manipulate bash command
591 line arguments (e.g. --parm=1 or -parm 1).
592
593 Description of argument(s):
594 arg_dashes The argument dashes specifier (usually,
595 "-" or "--").
596 """
597
598 if arg_dashes == "--":
599 return "="
600
601 return " "
602
603
604def create_command_string(command, *pos_parms, **options):
605 r"""
606 Create and return a bash command string consisting of the given arguments
607 formatted as text.
608
609 The default formatting of options is as follows:
610
611 <single dash><option name><space delim><option value>
612
613 Example:
614
615 -parm value
616
617 The caller can change the kind of dashes/delimiters used by specifying
618 "arg_dashes" and/or "arg_delims" as options. These options are processed
619 specially by the create_command_string function and do NOT get inserted
620 into the resulting command string. All options following the
621 arg_dashes/arg_delims options will then use the specified values for
622 dashes/delims. In the special case of arg_dashes equal to "--", the
623 arg_delim will automatically be changed to "=". See examples below.
624
625 Quoting rules:
626
627 The create_command_string function will single quote option values as
628 needed to prevent bash expansion. If the caller wishes to defeat this
629 action, they may single or double quote the option value themselves. See
630 examples below.
631
632 pos_parms are NOT automatically quoted. The caller is advised to either
633 explicitly add quotes or to use the quote_bash_parm functions to quote any
634 pos_parms.
635
636 Examples:
637
638 command_string = create_command_string('cd', '~')
639
640 Result:
641 cd ~
642
643 Note that the pos_parm ("~") does NOT get quoted, as per the
644 aforementioned rules. If quotes are desired, they may be added explicitly
645 by the caller:
646
647 command_string = create_command_string('cd', '\'~\'')
648
649 Result:
650 cd '~'
651
652 command_string = create_command_string('grep', '\'^[^ ]*=\'',
653 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
654
655 Result:
656 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
657
658 In the preceding example, note the use of None to cause the "i" parm to be
659 treated as a flag (i.e. no argument value is generated). Also, note the
660 use of arg_dashes to change the type of dashes used on all subsequent
661 options. The following example is equivalent to the prior. Note that
662 quote_bash_parm is used instead of including the quotes explicitly.
663
664 command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='),
665 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
666
667 Result:
668 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
669
670 In the following example, note the automatic quoting of the password
671 option, as per the aforementioned rules.
672
673 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
674 m='1', arg_dashes='--', password='${my_pw}')
675
676 However, let's say that the caller wishes to have bash expand the password
677 value. To achieve this, the caller can use double quotes:
678
679 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
680 m='1', arg_dashes='--', password='"${my_pw}"')
681
682 Result:
683 my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile
684
685 command_string = create_command_string('ipmitool', 'power status',
686 I='lanplus', C='3', U='root', P='0penBmc', H='wsbmc010')
687
688 Result:
689 ipmitool -I lanplus -C 3 -U root -P 0penBmc -H wsbmc010 power status
690
691 By default create_command_string will take measures to preserve the order
692 of the callers options. In some cases, this effort may fail (as when
693 calling directly from a robot program). In this case, the caller can
694 accept the responsibility of keeping an ordered list of options by calling
695 this function with the last positional parm as some kind of dictionary
696 (preferably an OrderedDict) and avoiding the use of any actual option args.
697
698 Example:
699 kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)])
700 command_string = create_command_string('my program', 'pos_parm1', kwargs)
701
702 Result:
703
704 my program -pass 0 -fail 0 pos_parm1
705
Michael Walshfffddca2019-08-20 17:19:13 +0000706 Note to programmers who wish to write a wrapper to this function: If the
707 python version is less than 3.6, to get the options to be processed
708 correctly, the wrapper function must include a _stack_frame_ix_ keyword
709 argument to allow this function to properly re-order options:
George Keishing16244c22019-01-31 16:16:14 +0000710
711 def create_ipmi_ext_command_string(command, **kwargs):
712
713 return create_command_string('ipmitool', command, _stack_frame_ix_=2,
714 **kwargs)
715
716 Example call of wrapper function:
717
718 command_string = create_ipmi_ext_command_string('power status',
719 I='lanplus')
720
721 Description of argument(s):
722 command The command (e.g. "cat", "sort",
723 "ipmitool", etc.).
724 pos_parms The positional parms for the command (e.g.
725 PATTERN, FILENAME, etc.). These will be
726 placed at the end of the resulting command
727 string.
728 options The command options (e.g. "-m 1",
729 "--max-count=NUM", etc.). Note that if
730 the value of any option is None, then it
731 will be understood to be a flag (for which
732 no value is required).
733 """
734
735 arg_dashes = "-"
736 delim = default_arg_delim(arg_dashes)
737
738 command_string = command
739
Michael Walsh23ac9b42019-02-20 16:46:04 -0600740 if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]):
George Keishing16244c22019-01-31 16:16:14 +0000741 # Convert pos_parms from tuple to list.
742 pos_parms = list(pos_parms)
743 # Re-assign options to be the last pos_parm value (which is a
744 # dictionary).
745 options = pos_parms[-1]
746 # Now delete the last pos_parm.
747 del pos_parms[-1]
748 else:
749 # Either get stack_frame_ix from the caller via options or set it to
750 # the default value.
Michael Walshfffddca2019-08-20 17:19:13 +0000751 stack_frame_ix = options.pop('_stack_frame_ix_', 1)
752 if gm.python_version < gm.ordered_dict_version:
753 # Re-establish the original options order as specified on the
754 # original line of code. This function depends on correct order.
755 options = re_order_kwargs(stack_frame_ix, **options)
George Keishing16244c22019-01-31 16:16:14 +0000756 for key, value in options.items():
757 # Check for special values in options and process them.
758 if key == "arg_dashes":
759 arg_dashes = str(value)
760 delim = default_arg_delim(arg_dashes)
761 continue
762 if key == "arg_delim":
763 delim = str(value)
764 continue
765 # Format the options elements into the command string.
766 command_string += " " + arg_dashes + key
767 if value is not None:
768 command_string += delim
769 if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)):
770 # Already quoted.
771 command_string += str(value)
772 else:
773 command_string += gm.quote_bash_parm(str(value))
Michael Walsh23ac9b42019-02-20 16:46:04 -0600774 # Finally, append the pos_parms to the end of the command_string. Use
775 # filter to eliminate blank pos parms.
776 command_string = ' '.join([command_string] + list(filter(None, pos_parms)))
George Keishing16244c22019-01-31 16:16:14 +0000777
778 return command_string