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                " reserved for errors in calling the call point program" +
56                " rather than errors generated by the call point program.>",
57    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
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    r"""
131    Execute whenever the program ends normally or with the signals that we
132    catch (i.e. TERM, INT).
133    """
134
135    dprint_executing()
136    dprint_var(signal_number)
137
138    qprint_pgm_footer()
139
140
141def signal_handler(signal_number, frame):
142    r"""
143    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
144    program would terminate immediately with return code 143 and without
145    calling our exit_function.
146    """
147
148    # Our convention is to set up exit_function with atexit.registr() so
149    # there is no need to explicitly call exit_function from here.
150
151    dprint_executing()
152
153    # Calling exit prevents us from returning to the code that was running
154    # when we received the signal.
155    exit(0)
156
157
158def validate_parms():
159    r"""
160    Validate program parameters, etc.  Return True or False accordingly.
161    """
162
163    if not valid_value(call_point):
164        return False
165
166    global allow_shell_rc
167    if not valid_integer(allow_shell_rc):
168        return False
169
170    # Convert to hex string for consistency in printout.
171    allow_shell_rc = "0x%08x" % int(allow_shell_rc, 0)
172    set_pgm_arg(allow_shell_rc)
173
174    gen_post_validation(exit_function, signal_handler)
175
176    return True
177
178
179def run_pgm(plug_in_dir_path,
180            call_point,
181            allow_shell_rc):
182    r"""
183    Run the call point program in the given plug_in_dir_path.  Return the
184    following:
185    rc                              The return code - 0 = PASS, 1 = FAIL.
186    shell_rc                        The shell return code returned by
187                                    process_plug_in_packages.py.
188    failed_plug_in_name             The failed plug in name (if any).
189
190    Description of arguments:
191    plug_in_dir_path                The directory path where the call_point
192                                    program may be located.
193    call_point                      The call point (e.g. "setup").  This
194                                    program will look for a program named
195                                    "cp_" + call_point in the
196                                    plug_in_dir_path.  If no such call point
197                                    program is found, this function returns an
198                                    rc of 0 (i.e. success).
199    allow_shell_rc                  The user may supply a value other than
200                                    zero to indicate an acceptable non-zero
201                                    return code.  For example, if this value
202                                    equals 0x00000200, it means that for each
203                                    plug-in call point that runs, a 0x00000200
204                                    will not be counted as a failure.  See
205                                    note above regarding left-shifting of
206                                    return codes.
207    """
208
209    global autoscript
210
211    rc = 0
212    failed_plug_in_name = ""
213    shell_rc = 0x00000000
214
215    plug_in_name = os.path.basename(os.path.normpath(plug_in_dir_path))
216    cp_prefix = "cp_"
217    plug_in_pgm_path = plug_in_dir_path + cp_prefix + call_point
218    if not os.path.exists(plug_in_pgm_path):
219        # No such call point in this plug in dir path.  This is legal so we
220        # return 0, etc.
221        return rc, shell_rc, failed_plug_in_name
222
223    # Get some stats on the file.
224    cmd_buf = "stat -c '%n %s %z' " + plug_in_pgm_path
225    dpissuing(cmd_buf)
226    sub_proc = subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
227                                stderr=subprocess.STDOUT)
228    out_buf, err_buf = sub_proc.communicate()
229    shell_rc = sub_proc.returncode
230    if shell_rc != 0:
231        rc = 1
232        print_var(shell_rc, hex)
233        failed_plug_in_name = plug_in_name
234        print(out_buf)
235        print_var(failed_plug_in_name)
236        print_var(shell_rc, hex)
237        return rc, shell_rc, failed_plug_in_name
238
239    print("------------------------------------------------- Starting plug-" +
240          "in -----------------------------------------------")
241    print(out_buf)
242    if autoscript:
243        stdout = 1 - quiet
244        if AUTOBOOT_OPENBMC_NICKNAME != "":
245            autoscript_prefix = AUTOBOOT_OPENBMC_NICKNAME + "."
246        else:
247            autoscript_prefix = ""
248        autoscript_prefix += plug_in_name + ".cp_" + call_point
249        autoscript_subcmd = "autoscript --quiet=1 --show_url=y --prefix=" +\
250            autoscript_prefix + " --stdout=" + str(stdout) + " -- "
251    else:
252        autoscript_subcmd = ""
253
254    cmd_buf = "PATH=" + plug_in_dir_path.rstrip("/") + ":${PATH} ; " +\
255        autoscript_subcmd + cp_prefix + call_point
256    pissuing(cmd_buf)
257
258    sub_proc = subprocess.Popen(cmd_buf, shell=True)
259    sub_proc.communicate()
260    shell_rc = sub_proc.returncode
261    # Shift to left.
262    shell_rc *= 0x100
263    if shell_rc != 0 and shell_rc != allow_shell_rc:
264        rc = 1
265        failed_plug_in_name = plug_in_name
266    if shell_rc != 0:
267        failed_plug_in_name = plug_in_name
268
269    print("------------------------------------------------- Ending plug-in" +
270          " -------------------------------------------------")
271    if failed_plug_in_name != "":
272        print_var(failed_plug_in_name)
273    print_var(shell_rc, hex)
274
275    return rc, shell_rc, failed_plug_in_name
276
277
278def main():
279    r"""
280    This is the "main" function.  The advantage of having this function vs
281    just doing this in the true mainline is that you can:
282    - Declare local variables
283    - Use "return" instead of "exit".
284    - Indent 4 chars like you would in any function.
285    This makes coding more consistent, i.e. it's easy to move code from here
286    into a function and vice versa.
287    """
288
289    if not gen_get_options(parser, stock_list):
290        return False
291
292    if not validate_parms():
293        return False
294
295    qprint_pgm_header()
296
297    # Access program parameter globals.
298    global plug_in_dir_paths
299    global mch_class
300    global allow_shell_rc
301    global stop_on_plug_in_failure
302    global stop_on_non_zero_rc
303
304    plug_in_packages_list = return_plug_in_packages_list(plug_in_dir_paths,
305                                                         mch_class)
306
307    qpvar(plug_in_packages_list)
308    qprint("\n")
309
310    allow_shell_rc = int(allow_shell_rc, 0)
311    shell_rc = 0
312    failed_plug_in_name = ""
313
314    # If the autoscript program is present, we will use it to direct call point
315    # program output to a separate status file.  This keeps the output of the
316    # main program (i.e. OBMC Boot Test) cleaner and yet preserves call point
317    # output if it is needed for debug.
318    global autoscript
319    global AUTOBOOT_OPENBMC_NICKNAME
320    autoscript = 0
321    AUTOBOOT_OPENBMC_NICKNAME = ""
322    rc, out_buf = cmd_fnc("which autoscript", quiet=1, print_output=0,
323                          show_err=0)
324    if rc == 0:
325        autoscript = 1
326        AUTOBOOT_OPENBMC_NICKNAME = os.environ.get("AUTOBOOT_OPENBMC_NICKNAME",
327                                                   "")
328    ret_code = 0
329    for plug_in_dir_path in plug_in_packages_list:
330        rc, shell_rc, failed_plug_in_name = \
331            run_pgm(plug_in_dir_path, call_point, allow_shell_rc)
332        if rc != 0:
333            ret_code = 1
334            if stop_on_plug_in_failure:
335                break
336        if shell_rc != 0 and stop_on_non_zero_rc:
337            qprint_time("Stopping on non-zero shell return code as requested" +
338                        " by caller.\n")
339            break
340
341    if ret_code == 0:
342        return True
343    else:
344        if not stop_on_plug_in_failure:
345            # We print a summary error message to make the failure more
346            # obvious.
347            print_error("At least one plug-in failed.\n")
348        return False
349
350
351# Main
352
353if not main():
354    exit(1)
355