1#!/usr/bin/env python3
2
3r"""
4See help text for details.
5"""
6
7import os
8import subprocess
9import sys
10
11save_dir_path = sys.path.pop(0)
12
13modules = [
14    "gen_arg",
15    "gen_print",
16    "gen_valid",
17    "gen_plug_in",
18    "gen_cmd",
19    "gen_misc",
20]
21for module in modules:
22    exec("from " + module + " import *")
23
24sys.path.insert(0, save_dir_path)
25
26# Create parser object.
27parser = argparse.ArgumentParser(
28    usage="%(prog)s [OPTIONS]",
29    description="%(prog)s will process the plug-in packages passed to it."
30    + "  A plug-in package is essentially a directory containing"
31    + " one or more call point programs.  Each of these call point"
32    + ' programs must have a prefix of "cp_".  When calling'
33    + " %(prog)s, a user must provide a call_point parameter"
34    + " (described below).  For each plug-in package passed,"
35    + " %(prog)s will check for the presence of the specified call"
36    + " point program in the plug-in directory.  If it is found,"
37    + " %(prog)s will run it.  It is the responsibility of the"
38    + " caller to set any environment variables needed by the call"
39    + " point programs.\n\nAfter each call point program"
40    + " has been run, %(prog)s will print the following values in"
41    + " the following formats for use by the calling program:\n"
42    + "  failed_plug_in_name:               <failed plug-in value,"
43    + " if any>\n  shell_rc:                          "
44    + "<shell return code value of last call point program - this"
45    + " will be printed in hexadecimal format.  Also, be aware"
46    + " that if a call point program returns a value it will be"
47    + " shifted left 2 bytes (e.g. rc of 2 will be printed as"
48    + " 0x00000200).  That is because the rightmost byte is"
49    + " reserved for errors in calling the call point program"
50    + " rather than errors generated by the call point program.>",
51    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
52    prefix_chars="-+",
53)
54
55# Create arguments.
56parser.add_argument(
57    "plug_in_dir_paths",
58    nargs="?",
59    default="",
60    help=plug_in_dir_paths_help_text + default_string,
61)
62
63parser.add_argument(
64    "--call_point",
65    default="setup",
66    required=True,
67    help="The call point program name.  This value must not include the"
68    + ' "cp_" prefix.  For each plug-in package passed to this program,'
69    + " the specified call_point program will be called if it exists in"
70    + " the plug-in directory."
71    + default_string,
72)
73
74parser.add_argument(
75    "--allow_shell_rc",
76    default="0x00000000",
77    help="The user may supply a value other than zero to indicate an"
78    + " acceptable non-zero return code.  For example, if this value"
79    + " equals 0x00000200, it means that for each plug-in call point that"
80    + " runs, a 0x00000200 will not be counted as a failure.  See note"
81    + " above regarding left-shifting of return codes."
82    + default_string,
83)
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)
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 "allow_shell_rc" parm above),'
106    + " this program will stop processing and return 0 (success).  Since"
107    + " this constitutes a successful exit, this would normally be used"
108    + " where 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)
118
119parser.add_argument(
120    "--mch_class", default="obmc", help=mch_class_help_text + default_string
121)
122
123# Populate stock_list with options we want.
124stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
125
126original_path = os.environ.get("PATH")
127
128
129def validate_parms():
130    r"""
131    Validate program parameters, etc.  Return True or False accordingly.
132    """
133
134    valid_value(call_point)
135
136    global allow_shell_rc
137    valid_integer(allow_shell_rc)
138
139    # Convert to hex string for consistency in printout.
140    allow_shell_rc = "0x%08x" % int(allow_shell_rc, 0)
141    set_pgm_arg(allow_shell_rc)
142
143
144def run_pgm(plug_in_dir_path, call_point, allow_shell_rc):
145    r"""
146    Run the call point program in the given plug_in_dir_path.  Return the following:
147    rc                              The return code - 0 = PASS, 1 = FAIL.
148    shell_rc                        The shell return code returned by process_plug_in_packages.py.
149    failed_plug_in_name             The failed plug in name (if any).
150
151    Description of arguments:
152    plug_in_dir_path                The directory path where the call_point program may be located.
153    call_point                      The call point (e.g. "setup").  This program will look for a program
154                                    named "cp_" + call_point in the plug_in_dir_path.  If no such call point
155                                    program is found, this function returns an rc of 0 (i.e. success).
156    allow_shell_rc                  The user may supply a value other than zero to indicate an acceptable
157                                    non-zero return code.  For example, if this value equals 0x00000200, it
158                                    means that for each plug-in call point that runs, a 0x00000200 will not
159                                    be counted as a failure.  See note above regarding left-shifting of
160                                    return codes.
161    """
162
163    rc = 0
164    failed_plug_in_name = ""
165    shell_rc = 0x00000000
166
167    plug_in_name = os.path.basename(os.path.normpath(plug_in_dir_path))
168    cp_prefix = "cp_"
169    plug_in_pgm_path = plug_in_dir_path + cp_prefix + call_point
170    if not os.path.exists(plug_in_pgm_path):
171        # No such call point in this plug in dir path.  This is legal so we return 0, etc.
172        return rc, shell_rc, failed_plug_in_name
173
174    print(
175        "------------------------------------------------- Starting plug-"
176        + "in -----------------------------------------------"
177    )
178
179    print_timen("Running " + plug_in_name + "/" + cp_prefix + call_point + ".")
180
181    stdout = 1 - quiet
182    if AUTOBOOT_OPENBMC_NICKNAME != "":
183        auto_status_file_prefix = AUTOBOOT_OPENBMC_NICKNAME + "."
184    else:
185        auto_status_file_prefix = ""
186    auto_status_file_prefix += plug_in_name + ".cp_" + call_point
187    status_dir_path = add_trailing_slash(
188        os.environ.get("STATUS_DIR_PATH", os.environ["HOME"] + "/status/")
189    )
190    if not os.path.isdir(status_dir_path):
191        AUTOBOOT_EXECDIR = add_trailing_slash(
192            os.environ.get("AUTOBOOT_EXECDIR", "")
193        )
194        status_dir_path = AUTOBOOT_EXECDIR + "logs/"
195        if not os.path.exists(status_dir_path):
196            os.makedirs(status_dir_path)
197    status_file_name = (
198        auto_status_file_prefix + "." + file_date_time_stamp() + ".status"
199    )
200    auto_status_file_subcmd = (
201        "auto_status_file.py --status_dir_path="
202        + status_dir_path
203        + " --status_file_name="
204        + status_file_name
205        + " --quiet=1 --show_url=1 --prefix="
206        + auto_status_file_prefix
207        + " --stdout="
208        + str(stdout)
209        + " "
210    )
211
212    cmd_buf = "PATH=" + plug_in_dir_path.rstrip("/") + ":${PATH}"
213    print_issuing(cmd_buf)
214    os.environ["PATH"] = (
215        plug_in_dir_path.rstrip("/") + os.pathsep + original_path
216    )
217    cmd_buf = auto_status_file_subcmd + cp_prefix + call_point
218    print_issuing(cmd_buf)
219
220    sub_proc = subprocess.Popen(cmd_buf, shell=True)
221    sub_proc.communicate()
222    shell_rc = sub_proc.returncode
223    # Shift to left.
224    shell_rc *= 0x100
225    if shell_rc != 0 and shell_rc != allow_shell_rc:
226        rc = 1
227        failed_plug_in_name = plug_in_name + "/" + cp_prefix + call_point
228    if shell_rc != 0:
229        failed_plug_in_name = plug_in_name + "/" + cp_prefix + call_point
230    if failed_plug_in_name != "" and not stdout:
231        # Use tail to avoid double-printing of status_file_url.
232        shell_cmd(
233            "tail -n +2 " + status_dir_path + status_file_name,
234            quiet=1,
235            print_output=1,
236        )
237
238    print(
239        "------------------------------------------------- Ending plug-in"
240        + " -------------------------------------------------"
241    )
242    if failed_plug_in_name != "":
243        print_var(failed_plug_in_name)
244    print_var(shell_rc, hexa())
245
246    return rc, shell_rc, failed_plug_in_name
247
248
249def main():
250    gen_setup()
251
252    set_term_options(term_requests="children")
253
254    # Access program parameter globals.
255    global plug_in_dir_paths
256    global mch_class
257    global allow_shell_rc
258    global stop_on_plug_in_failure
259    global stop_on_non_zero_rc
260
261    plug_in_packages_list = return_plug_in_packages_list(
262        plug_in_dir_paths, mch_class
263    )
264
265    qprint_var(plug_in_packages_list)
266    qprint("\n")
267
268    allow_shell_rc = int(allow_shell_rc, 0)
269    shell_rc = 0
270    failed_plug_in_name = ""
271
272    global AUTOBOOT_OPENBMC_NICKNAME
273    AUTOBOOT_OPENBMC_NICKNAME = os.environ.get("AUTOBOOT_OPENBMC_NICKNAME", "")
274
275    ret_code = 0
276    for plug_in_dir_path in plug_in_packages_list:
277        rc, shell_rc, failed_plug_in_name = run_pgm(
278            plug_in_dir_path, call_point, allow_shell_rc
279        )
280        if rc != 0:
281            ret_code = 1
282            if stop_on_plug_in_failure:
283                break
284        if shell_rc != 0 and stop_on_non_zero_rc:
285            qprint_time(
286                "Stopping on non-zero shell return code as requested"
287                + " by caller.\n"
288            )
289            break
290
291    if ret_code != 0:
292        print_error("At least one plug-in failed.\n")
293        exit(1)
294
295
296main()
297