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