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