1#!/usr/bin/env python 2 3r""" 4This module provides command execution functions such as cmd_fnc and cmd_fnc_u. 5""" 6 7import sys 8import subprocess 9import collections 10 11import gen_print as gp 12import gen_valid as gv 13import gen_misc as gm 14 15robot_env = gp.robot_env 16 17if robot_env: 18 import gen_robot_print as grp 19 from robot.libraries.BuiltIn import BuiltIn 20 21 22def cmd_fnc(cmd_buf, 23 quiet=None, 24 test_mode=None, 25 debug=0, 26 print_output=1, 27 show_err=1, 28 return_stderr=0, 29 ignore_err=1): 30 r""" 31 Run the given command in a shell and return the shell return code and the 32 output. 33 34 Description of arguments: 35 cmd_buf The command string to be run in a shell. 36 quiet Indicates whether this function should run 37 the print_issuing() function which prints 38 "Issuing: <cmd string>" to stdout. 39 test_mode If test_mode is set, this function will 40 not actually run the command. If 41 print_output is set, it will print 42 "(test_mode) Issuing: <cmd string>" to 43 stdout. 44 debug If debug is set, this function will print 45 extra debug info. 46 print_output If this is set, this function will print 47 the stdout/stderr generated by the shell 48 command. 49 show_err If show_err is set, this function will 50 print a standardized error report if the 51 shell command returns non-zero. 52 return_stderr If return_stderr is set, this function 53 will process the stdout and stderr streams 54 from the shell command separately. It 55 will also return stderr in addition to the 56 return code and the stdout. 57 """ 58 59 # Determine default values. 60 quiet = int(gm.global_default(quiet, 0)) 61 test_mode = int(gm.global_default(test_mode, 0)) 62 63 if debug: 64 gp.print_vars(cmd_buf, quiet, test_mode, debug) 65 66 err_msg = gv.svalid_value(cmd_buf) 67 if err_msg != "": 68 raise ValueError(err_msg) 69 70 if not quiet: 71 gp.pissuing(cmd_buf, test_mode) 72 73 if test_mode: 74 if return_stderr: 75 return 0, "", "" 76 else: 77 return 0, "" 78 79 if return_stderr: 80 err_buf = "" 81 stderr = subprocess.PIPE 82 else: 83 stderr = subprocess.STDOUT 84 85 sub_proc = subprocess.Popen(cmd_buf, 86 bufsize=1, 87 shell=True, 88 executable='/bin/bash', 89 stdout=subprocess.PIPE, 90 stderr=stderr) 91 out_buf = "" 92 if return_stderr: 93 for line in sub_proc.stderr: 94 err_buf += line 95 if not print_output: 96 continue 97 if robot_env: 98 grp.rprint(line) 99 else: 100 sys.stdout.write(line) 101 for line in sub_proc.stdout: 102 out_buf += line 103 if not print_output: 104 continue 105 if robot_env: 106 grp.rprint(line) 107 else: 108 sys.stdout.write(line) 109 if print_output and not robot_env: 110 sys.stdout.flush() 111 sub_proc.communicate() 112 shell_rc = sub_proc.returncode 113 if shell_rc != 0: 114 err_msg = "The prior shell command failed.\n" 115 err_msg += gp.sprint_var(shell_rc, 1) 116 if not print_output: 117 err_msg += "out_buf:\n" + out_buf 118 119 if show_err: 120 if robot_env: 121 grp.rprint_error_report(err_msg) 122 else: 123 gp.print_error_report(err_msg) 124 if not ignore_err: 125 if robot_env: 126 BuiltIn().fail(err_msg) 127 else: 128 raise ValueError(err_msg) 129 130 if return_stderr: 131 return shell_rc, out_buf, err_buf 132 else: 133 return shell_rc, out_buf 134 135 136def cmd_fnc_u(cmd_buf, 137 quiet=None, 138 debug=None, 139 print_output=1, 140 show_err=1, 141 return_stderr=0, 142 ignore_err=1): 143 r""" 144 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details. 145 146 Note the "u" in "cmd_fnc_u" stands for "unconditional". 147 """ 148 149 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug, 150 print_output=print_output, show_err=show_err, 151 return_stderr=return_stderr, ignore_err=ignore_err) 152 153 154def parse_command_string(command_string): 155 r""" 156 Parse a bash command-line command string and return the result as a 157 dictionary of parms. 158 159 This can be useful for answering questions like "What did the user specify 160 as the value for parm x in the command string?". 161 162 This function expects the command string to follow the following posix 163 conventions: 164 - Short parameters: 165 -<parm name><space><arg value> 166 - Long parameters: 167 --<parm name>=<arg value> 168 169 The first item in the string will be considered to be the command. All 170 values not conforming to the specifications above will be considered 171 positional parms. If there are multiple parms with the same name, they 172 will be put into a list (see illustration below where "-v" is specified 173 multiple times). 174 175 Description of argument(s): 176 command_string The complete command string including all 177 parameters and arguments. 178 179 Sample input: 180 181 robot_cmd_buf: robot -v 182 OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot no' -v 183 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v 184 quiet:0 -v test_mode:0 -v debug:0 185 --outputdir='/home/user1/status/children/' 186 --output=dummy1.Auto_reboot.170802.124544.output.xml 187 --log=dummy1.Auto_reboot.170802.124544.log.html 188 --report=dummy1.Auto_reboot.170802.124544.report.html 189 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 190 191 Sample output: 192 193 robot_cmd_buf_dict: 194 robot_cmd_buf_dict[command]: robot 195 robot_cmd_buf_dict[v]: 196 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1 197 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto 198 Reboot no 199 robot_cmd_buf_dict[v][2]: 200 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot 201 robot_cmd_buf_dict[v][3]: quiet:0 202 robot_cmd_buf_dict[v][4]: test_mode:0 203 robot_cmd_buf_dict[v][5]: debug:0 204 robot_cmd_buf_dict[outputdir]: 205 /home/user1/status/children/ 206 robot_cmd_buf_dict[output]: 207 dummy1.Auto_reboot.170802.124544.output.xml 208 robot_cmd_buf_dict[log]: 209 dummy1.Auto_reboot.170802.124544.log.html 210 robot_cmd_buf_dict[report]: 211 dummy1.Auto_reboot.170802.124544.report.html 212 robot_cmd_buf_dict[positional]: 213 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 214 """ 215 216 # We want the parms in the string broken down the way bash would do it, 217 # so we'll call upon bash to do that by creating a simple inline bash 218 # function. 219 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\ 220 " echo $parm ; done ; }" 221 222 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string, 223 quiet=1, print_output=0) 224 command_string_list = outbuf.rstrip("\n").split("\n") 225 226 command_string_dict = collections.OrderedDict() 227 ix = 1 228 command_string_dict['command'] = command_string_list[0] 229 while ix < len(command_string_list): 230 if command_string_list[ix].startswith("--"): 231 key, value = command_string_list[ix].split("=") 232 key = key.lstrip("-") 233 elif command_string_list[ix].startswith("-"): 234 key = command_string_list[ix].lstrip("-") 235 ix += 1 236 try: 237 value = command_string_list[ix] 238 except IndexError: 239 value = "" 240 else: 241 key = 'positional' 242 value = command_string_list[ix] 243 if key in command_string_dict: 244 if type(command_string_dict[key]) is str: 245 command_string_dict[key] = [command_string_dict[key]] 246 command_string_dict[key].append(value) 247 else: 248 command_string_dict[key] = value 249 ix += 1 250 251 return command_string_dict 252