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