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