1#!/usr/bin/env python
2
3import sys
4import __builtin__
5import subprocess
6import os
7import argparse
8
9# python puts the program's directory path in sys.path[0].  In other words,
10# the user ordinarily has no way to override python's choice of a module from
11# its own dir.  We want to have that ability in our environment.  However, we
12# don't want to break any established python modules that depend on this
13# behavior.  So, we'll save the value from sys.path[0], delete it, import our
14# modules and then restore sys.path to its original value.
15
16save_path_0 = sys.path[0]
17del sys.path[0]
18
19from gen_print import *
20from gen_valid import *
21from gen_arg import *
22from gen_plug_in import *
23from gen_cmd import *
24
25# Restore sys.path[0].
26sys.path.insert(0, save_path_0)
27# I use this variable in calls to print_var.
28hex = 1
29
30# Create parser object to process command line parameters and args.
31
32# Create parser object.
33parser = argparse.ArgumentParser(
34    usage='%(prog)s [OPTIONS]',
35    description="%(prog)s will process the plug-in packages passed to it." +
36                "  A plug-in package is essentially a directory containing" +
37                " one or more call point programs.  Each of these call point" +
38                " programs must have a prefix of \"cp_\".  When calling" +
39                " %(prog)s, a user must provide a call_point parameter" +
40                " (described below).  For each plug-in package passed," +
41                " %(prog)s will check for the presence of the specified call" +
42                " point program in the plug-in directory.  If it is found," +
43                " %(prog)s will run it.  It is the responsibility of the" +
44                " caller to set any environment variables needed by the call" +
45                " point programs.\n\nAfter each call point program" +
46                " has been run, %(prog)s will print the following values in" +
47                " the following formats for use by the calling program:\n" +
48                "  failed_plug_in_name:               <failed plug-in value," +
49                " if any>\n  shell_rc:                          " +
50                "<shell return code value of last call point program - this" +
51                " will be printed in hexadecimal format.  Also, be aware" +
52                " that if a call point program returns a value it will be" +
53                " shifted left 2 bytes (e.g. rc of 2 will be printed as" +
54                " 0x00000200).  That is because the rightmost byte is" +
55                " reserverd for errors in calling the call point program" +
56                " rather than errors generated by the call point program.>",
57    formatter_class=argparse.RawTextHelpFormatter,
58    prefix_chars='-+')
59
60# Create arguments.
61parser.add_argument(
62    'plug_in_dir_paths',
63    nargs='?',
64    default="",
65    help=plug_in_dir_paths_help_text + default_string)
66
67parser.add_argument(
68    '--call_point',
69    default="setup",
70    required=True,
71    help='The call point program name.  This value must not include the' +
72         ' "cp_" prefix.  For each plug-in package passed to this program,' +
73         ' the specified call_point program will be called if it exists in' +
74         ' the plug-in directory.' + default_string)
75
76parser.add_argument(
77    '--allow_shell_rc',
78    default="0x00000000",
79    help='The user may supply a value other than zero to indicate an' +
80         ' acceptable non-zero return code.  For example, if this value' +
81         ' equals 0x00000200, it means that for each plug-in call point that' +
82         ' runs, a 0x00000200 will not be counted as a failure.  See note' +
83         ' above regarding left-shifting of return codes.' + default_string)
84
85parser.add_argument(
86    '--stop_on_plug_in_failure',
87    default=1,
88    type=int,
89    choices=[1, 0],
90    help='If this parameter is set to 1, this program will stop and return ' +
91         'non-zero if the call point program from any plug-in directory ' +
92         'fails.  Conversely, if it is set to false, this program will run ' +
93         'the call point program from each and every plug-in directory ' +
94         'regardless of their return values.  Typical example cases where ' +
95         'you\'d want to run all plug-in call points regardless of success ' +
96         'or failure would be "cleanup" or "ffdc" call points.')
97
98parser.add_argument(
99    '--stop_on_non_zero_rc',
100    default=0,
101    type=int,
102    choices=[1, 0],
103    help='If this parm is set to 1 and a plug-in call point program returns ' +
104         'a valid non-zero return code (see "allow_shell_rc" parm above),' +
105         ' this program will stop processing and return 0 (success).  Since' +
106         ' this constitutes a successful exit, this would normally be used' +
107         ' where the caller wishes to stop processing if one of the plug-in' +
108         ' directory call point programs returns a special value indicating' +
109         ' that some special case has been found.  An example might be in' +
110         ' calling some kind of "check_errl" call point program.  Such a' +
111         ' call point program might return a 2 (i.e. 0x00000200) to indicate' +
112         ' that a given error log entry was found in an "ignore" list and is' +
113         ' therefore to be ignored.  That being the case, no other' +
114         ' "check_errl" call point program would need to be called.' +
115         default_string)
116
117parser.add_argument(
118    '--mch_class',
119    default="obmc",
120    help=mch_class_help_text + default_string)
121
122# The stock_list will be passed to gen_get_options.  We populate it with the
123# names of stock parm options we want.  These stock parms are pre-defined by
124# gen_get_options.
125stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
126
127
128def exit_function(signal_number=0,
129                  frame=None):
130
131    r"""
132    Execute whenever the program ends normally or with the signals that we
133    catch (i.e. TERM, INT).
134    """
135
136    dprint_executing()
137    dprint_var(signal_number)
138
139    qprint_pgm_footer()
140
141
142def signal_handler(signal_number, frame):
143
144    r"""
145    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
146    program would terminate immediately with return code 143 and without
147    calling our exit_function.
148    """
149
150    # Our convention is to set up exit_function with atexit.registr() so
151    # there is no need to explicitly call exit_function from here.
152
153    dprint_executing()
154
155    # Calling exit prevents us from returning to the code that was running
156    # when we received the signal.
157    exit(0)
158
159
160def validate_parms():
161
162    r"""
163    Validate program parameters, etc.  Return True or False accordingly.
164    """
165
166    if not valid_value(call_point):
167        return False
168
169    global allow_shell_rc
170    if not valid_integer(allow_shell_rc):
171        return False
172
173    # Convert to hex string for consistency in printout.
174    allow_shell_rc = "0x%08x" % int(allow_shell_rc, 0)
175    set_pgm_arg(allow_shell_rc)
176
177    gen_post_validation(exit_function, signal_handler)
178
179    return True
180
181
182def run_pgm(plug_in_dir_path,
183            call_point,
184            allow_shell_rc):
185
186    r"""
187    Run the call point program in the given plug_in_dir_path.  Return the
188    following:
189    rc                              The return code - 0 = PASS, 1 = FAIL.
190    shell_rc                        The shell return code returned by
191                                    process_plug_in_packages.py.
192    failed_plug_in_name             The failed plug in name (if any).
193
194    Description of arguments:
195    plug_in_dir_path                The directory path where the call_point
196                                    program may be located.
197    call_point                      The call point (e.g. "setup").  This
198                                    program will look for a program named
199                                    "cp_" + call_point in the
200                                    plug_in_dir_path.  If no such call point
201                                    program is found, this function returns an
202                                    rc of 0 (i.e. success).
203    allow_shell_rc                  The user may supply a value other than
204                                    zero to indicate an acceptable non-zero
205                                    return code.  For example, if this value
206                                    equals 0x00000200, it means that for each
207                                    plug-in call point that runs, a 0x00000200
208                                    will not be counted as a failure.  See
209                                    note above regarding left-shifting of
210                                    return codes.
211    """
212
213    global autoscript
214
215    rc = 0
216    failed_plug_in_name = ""
217    shell_rc = 0x00000000
218
219    plug_in_name = os.path.basename(os.path.normpath(plug_in_dir_path))
220    cp_prefix = "cp_"
221    plug_in_pgm_path = plug_in_dir_path + cp_prefix + call_point
222    if not os.path.exists(plug_in_pgm_path):
223        # No such call point in this plug in dir path.  This is legal so we
224        # return 0, etc.
225        return rc, shell_rc, failed_plug_in_name
226
227    # Get some stats on the file.
228    cmd_buf = "stat -c '%n %s %z' " + plug_in_pgm_path
229    dpissuing(cmd_buf)
230    sub_proc = subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
231                                stderr=subprocess.STDOUT)
232    out_buf, err_buf = sub_proc.communicate()
233    shell_rc = sub_proc.returncode
234    if shell_rc != 0:
235        rc = 1
236        print_var(shell_rc, hex)
237        failed_plug_in_name = plug_in_name
238        print(out_buf)
239        print_var(failed_plug_in_name)
240        print_var(shell_rc, hex)
241        return rc, shell_rc, failed_plug_in_name
242
243    print("------------------------------------------------- Starting plug-" +
244          "in -----------------------------------------------")
245    print(out_buf)
246    if autoscript:
247        stdout = 1 - quiet
248        if AUTOBOOT_OPENBMC_NICKNAME != "":
249            autoscript_prefix = AUTOBOOT_OPENBMC_NICKNAME + "."
250        else:
251            autoscript_prefix = ""
252        autoscript_prefix += plug_in_name + ".cp_" + call_point
253        autoscript_subcmd = "autoscript --quiet=1 --show_url=y --prefix=" +\
254            autoscript_prefix + " --stdout=" + str(stdout) + " -- "
255    else:
256        autoscript_subcmd = ""
257
258    cmd_buf = "PATH=" + plug_in_dir_path + ":${PATH} ; " + autoscript_subcmd +\
259              cp_prefix + call_point
260    pissuing(cmd_buf)
261
262    sub_proc = subprocess.Popen(cmd_buf, shell=True)
263    sub_proc.communicate()
264    shell_rc = sub_proc.returncode
265    # Shift to left.
266    shell_rc *= 0x100
267    if shell_rc != 0 and shell_rc != allow_shell_rc:
268        rc = 1
269        failed_plug_in_name = plug_in_name
270
271    print("------------------------------------------------- Ending plug-in" +
272          " -------------------------------------------------")
273    if failed_plug_in_name != "":
274        print_var(failed_plug_in_name)
275    print_var(shell_rc, hex)
276
277    return rc, shell_rc, failed_plug_in_name
278
279
280def main():
281
282    r"""
283    This is the "main" function.  The advantage of having this function vs
284    just doing this in the true mainline is that you can:
285    - Declare local variables
286    - Use "return" instead of "exit".
287    - Indent 4 chars like you would in any function.
288    This makes coding more consistent, i.e. it's easy to move code from here
289    into a function and vice versa.
290    """
291
292    if not gen_get_options(parser, stock_list):
293        return False
294
295    if not validate_parms():
296        return False
297
298    qprint_pgm_header()
299
300    # Access program parameter globals.
301    global plug_in_dir_paths
302    global mch_class
303    global allow_shell_rc
304    global stop_on_plug_in_failure
305    global stop_on_non_zero_rc
306
307    plug_in_packages_list = return_plug_in_packages_list(plug_in_dir_paths,
308                                                         mch_class)
309
310    qpvar(plug_in_packages_list)
311    qprint("\n")
312
313    allow_shell_rc = int(allow_shell_rc, 0)
314    shell_rc = 0
315    failed_plug_in_name = ""
316
317    # If the autoscript program is present, we will use it to direct call point
318    # program output to a separate status file.  This keeps the output of the
319    # main program (i.e. OBMC Boot Test) cleaner and yet preserves call point
320    # output if it is needed for debug.
321    global autoscript
322    global AUTOBOOT_OPENBMC_NICKNAME
323    autoscript = 0
324    AUTOBOOT_OPENBMC_NICKNAME = ""
325    rc, out_buf = cmd_fnc("which autoscript", quiet=1, print_output=0,
326                          show_err=0)
327    if rc == 0:
328        autoscript = 1
329        AUTOBOOT_OPENBMC_NICKNAME = os.environ.get("AUTOBOOT_OPENBMC_NICKNAME",
330                                                   "")
331    ret_code = 0
332    for plug_in_dir_path in plug_in_packages_list:
333        rc, shell_rc, failed_plug_in_name = \
334            run_pgm(plug_in_dir_path, call_point, allow_shell_rc)
335        if rc != 0:
336            ret_code = 1
337            if stop_on_plug_in_failure:
338                break
339        if shell_rc != 0 and stop_on_non_zero_rc:
340            qprint_time("Stopping on non-zero shell return code as requested" +
341                        " by caller.\n")
342            break
343
344    if ret_code == 0:
345        return True
346    else:
347        if not stop_on_plug_in_failure:
348            # We print a summary error message to make the failure more
349            # obvious.
350            print_error("At least one plug-in failed.\n")
351        return False
352
353
354# Main
355
356if not main():
357    exit(1)
358