Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 4 | This module provides valuable argument processing functions like gen_get_options and sprint_args. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 5 | """ |
| 6 | |
| 7 | import sys |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 8 | import os |
| 9 | import re |
| 10 | try: |
| 11 | import psutil |
| 12 | psutil_imported = True |
| 13 | except ImportError: |
| 14 | psutil_imported = False |
George Keishing | 3b7115a | 2018-08-02 10:48:17 -0500 | [diff] [blame] | 15 | try: |
| 16 | import __builtin__ |
| 17 | except ImportError: |
| 18 | import builtins as __builtin__ |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 19 | import atexit |
| 20 | import signal |
| 21 | import argparse |
| 22 | |
| 23 | import gen_print as gp |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 24 | import gen_valid as gv |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 25 | import gen_cmd as gc |
| 26 | import gen_misc as gm |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 27 | |
| 28 | default_string = ' The default value is "%(default)s".' |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 29 | module = sys.modules["__main__"] |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 30 | |
| 31 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 32 | def gen_get_options(parser, |
| 33 | stock_list=[]): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 34 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 35 | Parse the command line arguments using the parser object passed and return True/False (i.e. pass/fail). |
| 36 | However, if gv.exit_on_error is set, simply exit the program on failure. Also set the following built in |
| 37 | values: |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 38 | |
| 39 | __builtin__.quiet This value is used by the qprint functions. |
| 40 | __builtin__.test_mode This value is used by command processing functions. |
| 41 | __builtin__.debug This value is used by the dprint functions. |
| 42 | __builtin__.arg_obj This value is used by print_program_header, etc. |
| 43 | __builtin__.parser This value is used by print_program_header, etc. |
| 44 | |
| 45 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 46 | parser A parser object. See argparse module documentation for details. |
| 47 | stock_list The caller can use this parameter to request certain stock parameters |
| 48 | offered by this function. For example, this function will define a |
| 49 | "quiet" option upon request. This includes stop help text and parm |
| 50 | checking. The stock_list is a list of tuples each of which consists of |
| 51 | an arg_name and a default value. Example: stock_list = [("test_mode", |
| 52 | 0), ("quiet", 1), ("debug", 0)] |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 53 | """ |
| 54 | |
| 55 | # This is a list of stock parms that we support. |
| 56 | master_stock_list = ["quiet", "test_mode", "debug", "loglevel"] |
| 57 | |
| 58 | # Process stock_list. |
| 59 | for ix in range(0, len(stock_list)): |
| 60 | if len(stock_list[ix]) < 1: |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 61 | error_message = "Programmer error - stock_list[" + str(ix) +\ |
| 62 | "] is supposed to be a tuple containing at" +\ |
| 63 | " least one element which is the name of" +\ |
| 64 | " the desired stock parameter:\n" +\ |
| 65 | gp.sprint_var(stock_list) |
| 66 | return gv.process_error_message(error_message) |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 67 | if isinstance(stock_list[ix], tuple): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 68 | arg_name = stock_list[ix][0] |
| 69 | default = stock_list[ix][1] |
| 70 | else: |
| 71 | arg_name = stock_list[ix] |
| 72 | default = None |
| 73 | |
| 74 | if arg_name not in master_stock_list: |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 75 | error_message = "Programmer error - arg_name \"" + arg_name +\ |
| 76 | "\" not found found in stock list:\n" +\ |
| 77 | gp.sprint_var(master_stock_list) |
| 78 | return gv.process_error_message(error_message) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 79 | |
| 80 | if arg_name == "quiet": |
| 81 | if default is None: |
| 82 | default = 0 |
| 83 | parser.add_argument( |
| 84 | '--quiet', |
| 85 | default=default, |
| 86 | type=int, |
| 87 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 88 | help='If this parameter is set to "1", %(prog)s' |
| 89 | + ' will print only essential information, i.e. it will' |
| 90 | + ' not echo parameters, echo commands, print the total' |
| 91 | + ' run time, etc.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 92 | elif arg_name == "test_mode": |
| 93 | if default is None: |
| 94 | default = 0 |
| 95 | parser.add_argument( |
| 96 | '--test_mode', |
| 97 | default=default, |
| 98 | type=int, |
| 99 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 100 | help='This means that %(prog)s should go through all the' |
| 101 | + ' motions but not actually do anything substantial.' |
| 102 | + ' This is mainly to be used by the developer of' |
| 103 | + ' %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 104 | elif arg_name == "debug": |
| 105 | if default is None: |
| 106 | default = 0 |
| 107 | parser.add_argument( |
| 108 | '--debug', |
| 109 | default=default, |
| 110 | type=int, |
| 111 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 112 | help='If this parameter is set to "1", %(prog)s will print' |
| 113 | + ' additional debug information. This is mainly to be' |
| 114 | + ' used by the developer of %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 115 | elif arg_name == "loglevel": |
| 116 | if default is None: |
| 117 | default = "info" |
| 118 | parser.add_argument( |
| 119 | '--loglevel', |
| 120 | default=default, |
| 121 | type=str, |
| 122 | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', |
| 123 | 'debug', 'info', 'warning', 'error', 'critical'], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 124 | help='If this parameter is set to "1", %(prog)s will print' |
| 125 | + ' additional debug information. This is mainly to be' |
| 126 | + ' used by the developer of %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 127 | |
| 128 | arg_obj = parser.parse_args() |
| 129 | |
| 130 | __builtin__.quiet = 0 |
| 131 | __builtin__.test_mode = 0 |
| 132 | __builtin__.debug = 0 |
| 133 | __builtin__.loglevel = 'WARNING' |
| 134 | for ix in range(0, len(stock_list)): |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 135 | if isinstance(stock_list[ix], tuple): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 136 | arg_name = stock_list[ix][0] |
| 137 | default = stock_list[ix][1] |
| 138 | else: |
| 139 | arg_name = stock_list[ix] |
| 140 | default = None |
| 141 | if arg_name == "quiet": |
| 142 | __builtin__.quiet = arg_obj.quiet |
| 143 | elif arg_name == "test_mode": |
| 144 | __builtin__.test_mode = arg_obj.test_mode |
| 145 | elif arg_name == "debug": |
| 146 | __builtin__.debug = arg_obj.debug |
| 147 | elif arg_name == "loglevel": |
| 148 | __builtin__.loglevel = arg_obj.loglevel |
| 149 | |
| 150 | __builtin__.arg_obj = arg_obj |
| 151 | __builtin__.parser = parser |
| 152 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 153 | # For each command line parameter, create a corresponding global variable and assign it the appropriate |
| 154 | # value. For example, if the command line contained "--last_name='Smith', we'll create a global variable |
| 155 | # named "last_name" with the value "Smith". |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 156 | module = sys.modules['__main__'] |
| 157 | for key in arg_obj.__dict__: |
| 158 | setattr(module, key, getattr(__builtin__.arg_obj, key)) |
| 159 | |
| 160 | return True |
| 161 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 162 | |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 163 | def set_pgm_arg(var_value, |
| 164 | var_name=None): |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 165 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 166 | Set the value of the arg_obj.__dict__ entry named in var_name with the var_value provided. Also, set |
| 167 | corresponding global variable. |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 168 | |
| 169 | Description of arguments: |
| 170 | var_value The value to set in the variable. |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 171 | var_name The name of the variable to set. This defaults to the name of the |
| 172 | variable used for var_value when calling this function. |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 173 | """ |
| 174 | |
| 175 | if var_name is None: |
| 176 | var_name = gp.get_arg_name(None, 1, 2) |
| 177 | |
| 178 | arg_obj.__dict__[var_name] = var_value |
| 179 | module = sys.modules['__main__'] |
| 180 | setattr(module, var_name, var_value) |
| 181 | if var_name == "quiet": |
| 182 | __builtin__.quiet = var_value |
| 183 | elif var_name == "debug": |
| 184 | __builtin__.debug = var_value |
| 185 | elif var_name == "test_mode": |
| 186 | __builtin__.test_mode = var_value |
| 187 | |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 188 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 189 | def sprint_args(arg_obj, |
| 190 | indent=0): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 191 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 192 | sprint_var all of the arguments found in arg_obj and return the result as a string. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 193 | |
| 194 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 195 | arg_obj An argument object such as is returned by the argparse parse_args() |
| 196 | method. |
| 197 | indent The number of spaces to indent each line of output. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 198 | """ |
| 199 | |
Michael Walsh | 0d5f96a | 2019-05-20 10:09:57 -0500 | [diff] [blame] | 200 | col1_width = gp.dft_col1_width + indent |
Michael Walsh | bec416d | 2016-11-10 08:54:52 -0600 | [diff] [blame] | 201 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 202 | buffer = "" |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 203 | for key in arg_obj.__dict__: |
Michael Walsh | bec416d | 2016-11-10 08:54:52 -0600 | [diff] [blame] | 204 | buffer += gp.sprint_varx(key, getattr(arg_obj, key), 0, indent, |
Michael Walsh | 0d5f96a | 2019-05-20 10:09:57 -0500 | [diff] [blame] | 205 | col1_width) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 206 | return buffer |
| 207 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 208 | |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 209 | term_options = None |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 210 | |
| 211 | |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 212 | def set_term_options(**kwargs): |
| 213 | r""" |
| 214 | Set the global term_options. |
| 215 | |
| 216 | If the global term_options is not None, gen_exit_function() will call terminate_descendants(). |
| 217 | |
| 218 | Description of arguments(): |
| 219 | kwargs Supported keyword options follow: |
| 220 | term_requests Requests to terminate specified descendants of this program. The |
| 221 | following values for term_requests are supported: |
| 222 | children Terminate the direct children of this program. |
| 223 | descendants Terminate all descendants of this program. |
| 224 | <dictionary> A dictionary with support for the following keys: |
| 225 | pgm_names A list of program names which will be used to identify which descendant |
| 226 | processes should be terminated. |
| 227 | """ |
| 228 | |
| 229 | global term_options |
| 230 | # Validation: |
| 231 | arg_names = list(kwargs.keys()) |
| 232 | gv.valid_list(arg_names, ['term_requests']) |
| 233 | if type(kwargs['term_requests']) is dict: |
| 234 | keys = list(kwargs['term_requests'].keys()) |
| 235 | gv.valid_list(keys, ['pgm_names']) |
| 236 | else: |
| 237 | gv.valid_value(kwargs['term_requests'], ['children', 'descendants']) |
| 238 | term_options = kwargs |
| 239 | |
| 240 | |
| 241 | if psutil_imported: |
| 242 | def match_process_by_pgm_name(process, pgm_name): |
| 243 | r""" |
| 244 | Return True or False to indicate whether the process matches the program name. |
| 245 | |
| 246 | Description of argument(s): |
| 247 | process A psutil process object such as the one returned by psutil.Process(). |
| 248 | pgm_name The name of a program to look for in the cmdline field of the process |
| 249 | object. |
| 250 | """ |
| 251 | |
| 252 | # This function will examine elements 0 and 1 of the cmdline field of the process object. The |
| 253 | # following examples will illustrate the reasons for this: |
| 254 | |
| 255 | # Example 1: Suppose a process was started like this: |
| 256 | |
| 257 | # shell_cmd('python_pgm_template --quiet=0', fork=1) |
| 258 | |
| 259 | # And then this function is called as follows: |
| 260 | |
| 261 | # match_process_by_pgm_name(process, "python_pgm_template") |
| 262 | |
| 263 | # The process object might contain the following for its cmdline field: |
| 264 | |
| 265 | # cmdline: |
| 266 | # [0]: /usr/bin/python |
| 267 | # [1]: /my_path/python_pgm_template |
| 268 | # [2]: --quiet=0 |
| 269 | |
| 270 | # Because "python_pgm_template" is a python program, the python interpreter (e.g. "/usr/bin/python") |
| 271 | # will appear in entry 0 of cmdline and the python_pgm_template will appear in entry 1 (with a |
| 272 | # qualifying dir path). |
| 273 | |
| 274 | # Example 2: Suppose a process was started like this: |
| 275 | |
| 276 | # shell_cmd('sleep 5', fork=1) |
| 277 | |
| 278 | # And then this function is called as follows: |
| 279 | |
| 280 | # match_process_by_pgm_name(process, "sleep") |
| 281 | |
| 282 | # The process object might contain the following for its cmdline field: |
| 283 | |
| 284 | # cmdline: |
| 285 | # [0]: sleep |
| 286 | # [1]: 5 |
| 287 | |
| 288 | # Because "sleep" is a compiled executable, it will appear in entry 0. |
| 289 | |
| 290 | optional_dir_path_regex = "(.*/)?" |
| 291 | cmdline = process.as_dict()['cmdline'] |
| 292 | return re.match(optional_dir_path_regex + pgm_name + '( |$)', cmdline[0]) \ |
| 293 | or re.match(optional_dir_path_regex + pgm_name + '( |$)', cmdline[1]) |
| 294 | |
| 295 | def select_processes_by_pgm_name(processes, pgm_name): |
| 296 | r""" |
| 297 | Select the processes that match pgm_name and return the result as a list of process objects. |
| 298 | |
| 299 | Description of argument(s): |
| 300 | processes A list of psutil process objects such as the one returned by |
| 301 | psutil.Process(). |
| 302 | pgm_name The name of a program to look for in the cmdline field of each process |
| 303 | object. |
| 304 | """ |
| 305 | |
| 306 | return [process for process in processes if match_process_by_pgm_name(process, pgm_name)] |
| 307 | |
| 308 | def sprint_process_report(pids): |
| 309 | r""" |
| 310 | Create a process report for the given pids and return it as a string. |
| 311 | |
| 312 | Description of argument(s): |
| 313 | pids A list of process IDs for processes to be included in the report. |
| 314 | """ |
| 315 | report = "\n" |
| 316 | cmd_buf = "echo ; ps wwo user,pgrp,pid,ppid,lstart,cmd --forest " + ' '.join(pids) |
| 317 | report += gp.sprint_issuing(cmd_buf) |
| 318 | rc, outbuf = gc.shell_cmd(cmd_buf, quiet=1) |
| 319 | report += outbuf + "\n" |
| 320 | |
| 321 | return report |
| 322 | |
| 323 | def get_descendant_info(process=psutil.Process()): |
| 324 | r""" |
| 325 | Get info about the descendants of the given process and return as a tuple of descendants, |
| 326 | descendant_pids and process_report. |
| 327 | |
| 328 | descendants will be a list of process objects. descendant_pids will be a list of pids (in str form) |
| 329 | and process_report will be a report produced by a call to sprint_process_report(). |
| 330 | |
| 331 | Description of argument(s): |
| 332 | process A psutil process object such as the one returned by psutil.Process(). |
| 333 | """ |
| 334 | descendants = process.children(recursive=True) |
| 335 | descendant_pids = [str(process.pid) for process in descendants] |
| 336 | if descendants: |
| 337 | process_report = sprint_process_report([str(process.pid)] + descendant_pids) |
| 338 | else: |
| 339 | process_report = "" |
| 340 | return descendants, descendant_pids, process_report |
| 341 | |
| 342 | def terminate_descendants(): |
| 343 | r""" |
| 344 | Terminate descendants of the current process according to the requirements layed out in global |
| 345 | term_options variable. |
| 346 | |
| 347 | Note: If term_options is not null, gen_exit_function() will automatically call this function. |
| 348 | |
| 349 | When this function gets called, descendant processes may be running and may be printing to the same |
| 350 | stdout stream being used by this process. If this function writes directly to stdout, its output can |
| 351 | be interspersed with any output generated by descendant processes. This makes it very difficult to |
| 352 | interpret the output. In order solve this problem, the activity of this process will be logged to a |
| 353 | temporary file. After descendant processes have been terminated successfully, the temporary file |
| 354 | will be printed to stdout and then deleted. However, if this function should fail to complete (i.e. |
| 355 | get hung waiting for descendants to terminate gracefully), the temporary file will not be deleted and |
| 356 | can be used by the developer for debugging. If no descendant processes are found, this function will |
| 357 | return before creating the temporary file. |
| 358 | |
| 359 | Note that a general principal being observed here is that each process is responsible for the |
| 360 | children it produces. |
| 361 | """ |
| 362 | |
| 363 | message = "\n" + gp.sprint_dashes(width=120) \ |
| 364 | + gp.sprint_executing() + "\n" |
| 365 | |
| 366 | current_process = psutil.Process() |
| 367 | |
| 368 | descendants, descendant_pids, process_report = get_descendant_info(current_process) |
| 369 | if not descendants: |
| 370 | # If there are no descendants, then we have nothing to do. |
| 371 | return |
| 372 | |
| 373 | terminate_descendants_temp_file_path = gm.create_temp_file_path() |
| 374 | gp.print_vars(terminate_descendants_temp_file_path) |
| 375 | |
| 376 | message += gp.sprint_varx("pgm_name", gp.pgm_name) \ |
| 377 | + gp.sprint_vars(term_options) \ |
| 378 | + process_report |
| 379 | |
| 380 | # Process the termination requests: |
| 381 | if term_options['term_requests'] == 'children': |
| 382 | term_processes = current_process.children(recursive=False) |
| 383 | term_pids = [str(process.pid) for process in term_processes] |
| 384 | elif term_options['term_requests'] == 'descendants': |
| 385 | term_processes = descendants |
| 386 | term_pids = descendant_pids |
| 387 | else: |
| 388 | # Process term requests by pgm_names. |
| 389 | term_processes = [] |
| 390 | for pgm_name in term_options['term_requests']['pgm_names']: |
| 391 | term_processes.extend(select_processes_by_pgm_name(descendants, pgm_name)) |
| 392 | term_pids = [str(process.pid) for process in term_processes] |
| 393 | |
| 394 | message += gp.sprint_timen("Processes to be terminated:") \ |
| 395 | + gp.sprint_var(term_pids) |
| 396 | for process in term_processes: |
| 397 | process.terminate() |
| 398 | message += gp.sprint_timen("Waiting on the following pids: " + ' '.join(descendant_pids)) |
| 399 | gm.append_file(terminate_descendants_temp_file_path, message) |
| 400 | psutil.wait_procs(descendants) |
| 401 | |
| 402 | # Checking after the fact to see whether any descendant processes are still alive. If so, a process |
| 403 | # report showing this will be included in the output. |
| 404 | descendants, descendant_pids, process_report = get_descendant_info(current_process) |
| 405 | if descendants: |
| 406 | message = "\n" + gp.sprint_timen("Not all of the processes terminated:") \ |
| 407 | + process_report |
| 408 | gm.append_file(terminate_descendants_temp_file_path, message) |
| 409 | |
| 410 | message = gp.sprint_dashes(width=120) |
| 411 | gm.append_file(terminate_descendants_temp_file_path, message) |
| 412 | gp.print_file(terminate_descendants_temp_file_path) |
| 413 | os.remove(terminate_descendants_temp_file_path) |
| 414 | |
| 415 | |
| 416 | def gen_exit_function(): |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 417 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 418 | Execute whenever the program ends normally or with the signals that we catch (i.e. TERM, INT). |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 419 | """ |
| 420 | |
Michael Walsh | e15e7e9 | 2019-11-04 10:40:53 -0600 | [diff] [blame] | 421 | # ignore_err influences the way shell_cmd processes errors. Since we're doing exit processing, we don't |
| 422 | # want to stop the program due to a shell_cmd failure. |
| 423 | ignore_err = 1 |
| 424 | |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 425 | if psutil_imported and term_options: |
| 426 | terminate_descendants() |
| 427 | |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 428 | # Call the main module's exit_function if it is defined. |
| 429 | exit_function = getattr(module, "exit_function", None) |
| 430 | if exit_function: |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 431 | exit_function() |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 432 | |
| 433 | gp.qprint_pgm_footer() |
| 434 | |
| 435 | |
| 436 | def gen_signal_handler(signal_number, |
| 437 | frame): |
| 438 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 439 | Handle signals. Without a function to catch a SIGTERM or SIGINT, the program would terminate immediately |
| 440 | with return code 143 and without calling the exit_function. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 441 | """ |
| 442 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 443 | # The convention is to set up exit_function with atexit.register() so there is no need to explicitly |
| 444 | # call exit_function from here. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 445 | |
Michael Walsh | c0e0ad4 | 2019-12-05 16:54:59 -0600 | [diff] [blame] | 446 | gp.qprint_executing() |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 447 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 448 | # Calling exit prevents control from returning to the code that was running when the signal was received. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 449 | exit(0) |
| 450 | |
| 451 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 452 | def gen_post_validation(exit_function=None, |
| 453 | signal_handler=None): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 454 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 455 | Do generic post-validation processing. By "post", we mean that this is to be called from a validation |
| 456 | function after the caller has done any validation desired. If the calling program passes exit_function |
| 457 | and signal_handler parms, this function will register them. In other words, it will make the |
| 458 | signal_handler functions get called for SIGINT and SIGTERM and will make the exit_function function run |
| 459 | prior to the termination of the program. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 460 | |
| 461 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 462 | exit_function A function object pointing to the caller's exit function. This defaults |
| 463 | to this module's gen_exit_function. |
| 464 | signal_handler A function object pointing to the caller's signal_handler function. This |
| 465 | defaults to this module's gen_signal_handler. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 466 | """ |
| 467 | |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 468 | # Get defaults. |
| 469 | exit_function = exit_function or gen_exit_function |
| 470 | signal_handler = signal_handler or gen_signal_handler |
| 471 | |
| 472 | atexit.register(exit_function) |
| 473 | signal.signal(signal.SIGINT, signal_handler) |
| 474 | signal.signal(signal.SIGTERM, signal_handler) |
| 475 | |
| 476 | |
| 477 | def gen_setup(): |
| 478 | r""" |
| 479 | Do general setup for a program. |
| 480 | """ |
| 481 | |
| 482 | # Set exit_on_error for gen_valid functions. |
| 483 | gv.set_exit_on_error(True) |
| 484 | |
| 485 | # Get main module variable values. |
| 486 | parser = getattr(module, "parser") |
| 487 | stock_list = getattr(module, "stock_list") |
| 488 | validate_parms = getattr(module, "validate_parms", None) |
| 489 | |
| 490 | gen_get_options(parser, stock_list) |
| 491 | |
| 492 | if validate_parms: |
| 493 | validate_parms() |
| 494 | gen_post_validation() |
| 495 | |
| 496 | gp.qprint_pgm_header() |