• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import argparse
17import os
18import re
19import sys
20import traceback
21from clade import Clade
22from clang.cindex import Index
23from clang.cindex import TokenKind
24from clang.cindex import CursorKind
25from clang.cindex import TranslationUnitLoadError
26import yamale
27import yaml
28
29BINDINGS_FILE_NAME = "bindings.yml"
30
31# File describing structure of bindings.yml files and other restrictions on their content
32# This file is used to validate bindings.yml with yamale module
33BINDINGS_SCHEMA_FILE = '%s/%s' % (os.path.dirname(os.path.realpath(__file__)), "bindings_schema.yml")
34
35CPP_CODE_TO_GET_TEST_ID_IN_PANDA = "IntrusiveTest::GetId()"
36CPP_HEADER_COMPILE_OPTION_LIST = ["-x", "c++-header"]
37CLANG_CUR_WORKING_DIR_OPT = "-working-directory"
38HEADER_FILE_EXTENSION = ".h"
39
40
41# Parser of arguments and options
42class ArgsParser:
43
44    def __init__(self):
45        parser = argparse.ArgumentParser(description="Source code instrumentation for intrusive testing")
46        parser.add_argument(
47            'src_dir',
48            metavar='SRC_DIR',
49            nargs=1,
50            type=str,
51            help="Directory with source code files for instrumentation")
52
53        parser.add_argument(
54            'clade_file',
55            metavar='CLADE_FILE',
56            nargs=1,
57            type=str,
58            help="File with build commands intercepted by 'clade' tool")
59
60        parser.add_argument(
61            'test_suite_dir',
62            metavar='TEST_SUITE_DIR',
63            nargs=1,
64            type=str,
65            help="Directory of intrusive test suite")
66
67        self.__args = parser.parse_args()
68        self.__src_dir = None
69        self.__clade_file = None
70        self.__test_suite_dir = None
71
72    # raise: FatalError
73    def get_src_dir(self):
74        if(self.__src_dir is None):
75            self.__src_dir = os.path.abspath(self.__args.src_dir[0])
76            if(not os.path.isdir(self.__src_dir)):
77                err_msg = "%s argument %s is not an existing directory: %s"\
78                    % ("1st", "SRC_DIR", self.__args.src_dir[0])
79                raise FatalError(err_msg)
80        return self.__src_dir
81
82    # raise: FatalError
83    def get_clade_file(self):
84        if(self.__clade_file is None):
85            self.__clade_file = os.path.abspath(self.__args.clade_file[0])
86            if(not os.path.isfile(self.__clade_file)):
87                err_msg = "%s argument %s is not an existing file: %s"\
88                    % ("2nd", "CLADE_FIlE", self.__args.clade_file[0])
89                raise FatalError(err_msg)
90        return self.__clade_file
91
92    # raise: FatalError
93    def get_test_suite_dir(self):
94        if(self.__test_suite_dir is None):
95            self.__test_suite_dir = os.path.abspath(self.__args.test_suite_dir[0])
96            if(not os.path.isdir(self.__test_suite_dir)):
97                err_msg = "%s argument %s is not an existing directory: %s"\
98                        % ("3d", "TEST_SUITE_DIR", self.__args.test_suite_dir[0])
99                raise FatalError(err_msg)
100        return self.__test_suite_dir
101
102
103# User defined exception class
104class FatalError(Exception):
105
106    def __init__(self, msg, *args):
107        super().__init__(args)
108        self.__msg = msg
109
110    def __str__(self):
111        return self.get_err_msg()
112
113    def get_err_msg(self):
114        return self.__msg
115
116
117# Class for auxillary functions
118class Util:
119
120    # raise: OSError
121    @staticmethod
122    def find_files_by_name(directory, file_name, file_set):
123        for name in os.listdir(directory):
124            path = os.path.join(directory, name)
125            if(os.path.isfile(path) and (name == file_name)):
126                file_set.add(path)
127            elif(os.path.isdir(path) and (name != "..") and (name != ".")):
128                Util.find_files_by_name(path, file_name, file_set)
129
130    # raise: OSError
131    @staticmethod
132    def find_files_by_extension(directory, extension, is_recursive, file_set):
133        for name in os.listdir(directory):
134            path = os.path.join(directory, name)
135            if(os.path.isfile(path)):
136                root, ext = os.path.splitext(path)
137                if(ext == extension):
138                    file_set.add(path)
139            elif(is_recursive \
140                and os.path.isdir(path) \
141                and (name != "..") \
142                and (name != ".")):
143                Util.find_files_by_extension(
144                    path,
145                    extension,
146                    is_recursive,
147                    file_set)
148
149    # raise: OSError
150    @staticmethod
151    def read_file_lines(file_path):
152        with open(file_path, "r") as fd:
153            lines = fd.readlines()
154        return lines
155
156    # raise: OSError
157    @staticmethod
158    def write_lines_to_file(file_path, lines):
159        fd = os.open(file_path, os.O_RDWR, 0o777)
160        fo = os.fdopen(fd, "w+")
161        try:
162            fo.writelines(lines)
163        finally:
164            fo.close()
165
166    @staticmethod
167    def blank_string_to_none(s):
168        if(s is not None):
169            match = re.match(r'^\s*$', s)
170            if(match):
171                return None
172        return s
173
174
175# Class describing location of a synchronization point
176class SyncPoint:
177
178    def __init__(self, file, index, cl=None, method=None, source=None):
179        self._file = file
180        self._index = index
181        # In case the key is defined but with empty value, we set 'None' constant to encode that the key is not provided
182        self._cl = Util.blank_string_to_none(cl)
183        self._method = Util.blank_string_to_none(method)
184        self._source = Util.blank_string_to_none(source)
185
186    def __hash__(self):
187        return hash((self.file, self.index, self.cl, self.method, self.source))
188
189    def __eq__(self, other):
190        if not isinstance(other, SyncPoint):
191            return False
192        return (self.file == other.file) \
193            and (self.index == other.index) \
194            and (self.cl == other.cl) \
195            and (self.method == other.method) \
196            and (self.source == other.source)
197
198    @property
199    def file(self):
200        return self._file
201
202    @property
203    def index(self):
204        return self._index
205
206    @property
207    def cl(self):
208        return self._cl
209
210    @property
211    def method(self):
212        return self._method
213
214    @property
215    def source(self):
216        return self._source
217
218    def has_class(self):
219        return self._cl is not None
220
221    def has_method(self):
222        return self._method is not None
223
224    def has_source(self):
225        return self._source is not None
226
227
228    def to_string(self):
229        s = ["file: %s" % (self.file), "index: %s" % (str(self.index))]
230        if(self.has_class()):
231            s.append("class: %s" % (str(self.cl)))
232        if(self.has_method()):
233            s.append("method: %s" % (str(self.method)))
234        if(self.has_source()):
235            s.append("source: %s" % (str(self.source)))
236        return ", ".join(s)
237
238
239# Class describing a synchronization action (code)
240# of a single test at one synchronization point
241class SyncAction:
242
243    ID = 0
244
245    def __init__(self, code):
246        self._code = code
247        self._id = SyncAction.ID
248        SyncAction.ID += 1
249
250    def __hash__(self):
251        return hash(self._id)
252
253    def __eq__(self, other):
254        if not isinstance(other, SyncAction):
255            return False
256        return self._id == other.id
257
258    @property
259    def code(self):
260        return self._code
261
262    @property
263    def id(self):
264        return self._id
265
266
267# Abstraction for a source code file
268class SourceFile:
269
270    def __init__(self, path):
271        self._path = path
272
273    def __hash__(self):
274        return hash(self._path)
275
276    def __eq__(self, other):
277        if not isinstance(other, SourceFile):
278            return False
279        return self.path == other.path
280
281    @property
282    def path(self):
283        return self._path
284
285
286# Abstraction for a header file, i.e.
287# file included by another source or header file
288class HeaderFile(SourceFile):
289    def __init__(self, path, including_source_file_path):
290        self._including_source_file_path = including_source_file_path
291        super().__init__(path)
292
293    def __hash__(self):
294        return hash((self.path, self._including_source_file_path))
295
296    def __eq__(self, other):
297        if not isinstance(other, HeaderFile):
298            return False
299        return super().__eq__(other) \
300            and (self.including_source_file_path == other.including_source_file_path)
301
302    @property
303    def including_source_file_path(self):
304        return self._including_source_file_path
305
306
307# Parser of bindings.yml files
308class BindingsFileParser:
309
310    # raise: OSError
311    def __init__(self, file):
312        self.__file = file
313        with open(file, "r") as f:
314            self.__obj = yaml.safe_load(f)
315
316    #raise: yamale.YamaleError
317    def validate(self, schema_file):
318        schema = yamale.make_schema(schema_file)
319        data = yamale.make_data(self.__file)
320        yamale.validate(schema, data)
321
322    # Set of header files with declarations referenced in the code
323    # of synchronization actions, e.g. function calls, constants
324    # raise: FatalError
325    def get_declaration_set(self, test_dir):
326        l = self.__obj["declaration"].split(",")
327        i = 0
328        while(i < len(l)):
329            l[i] = l[i].lstrip().rstrip()
330            abs_path = os.path.join(test_dir, l[i])
331            if(not os.path.isfile(abs_path)):
332                err_msg = "[%s] in declaration list from [%s] is not an existing file"\
333                    % (l[i], self.__file)
334                raise FatalError(err_msg)
335            l[i] = abs_path
336            i += 1
337        return set(l)
338
339    # raise: FatalError
340    def get_mapping(self, src_dir):
341        m = {}
342        l = self.__obj["mapping"]
343        for e in l:
344            f = e.get("file")
345            abs_f = os.path.join(src_dir, f)
346            if(not os.path.isfile(abs_f)):
347                err_msg = "file attribute [%s] from [%s] is not an existing file"\
348                    % (f, self.__file)
349                raise FatalError(err_msg)
350
351            s = e.get("source")
352            if(s is None):
353                abs_s = None
354            else:
355                abs_s = os.path.join(src_dir, s)
356                if(not os.path.isfile(abs_s)):
357                    err_msg = "source attribute [%s] from [%s] is not an existing file"\
358                        % (s, self.__file)
359                    raise FatalError(err_msg)
360
361            ref = SyncPoint(
362                file=abs_f,
363                index=e.get("index"),
364                cl=e.get("class"),
365                method=e.get("method"),
366                source=abs_s)
367            m[ref] = SyncAction(e.get("code"))
368        return m
369
370
371# Class implementing all instrumentation logic (algorithm)
372class Instrumentator:
373
374    def __init__(self, args_parser):
375        # arguments parser
376        self.__args_parser = args_parser
377
378        # set of bindings.yml files
379        self.__bindings_file_set = set()
380
381        # dictionary from synchronization action
382        # to set of header files declaring functions,
383        # constants and etc. used in that synchronization
384        # action
385        self.__sync_action_to_declaration_set = {}
386
387        # dictionary from synchronization action
388        # to test identifier
389        self.__sync_action_to_test_id = {}
390
391        # dictionary from synchronization point
392        # to list of synchronization actions for that point
393        self.__sync_point_to_sync_action_list = {}
394
395        # dictionary from synchronization point to
396        # line number in which the synchronization comment
397        # for that synchronization point finishes
398        self.__sync_point_to_line = {}
399
400        # dictionary from file to set of synchronization
401        # points from that file
402        self.__file_to_sync_point_set = {}
403
404        # dictionary from file to list of compile options
405        # used to parse that file
406        self.__file_to_compile_option_list = {}
407
408
409    def run(self):
410        Util.find_files_by_name(
411            self.__args_parser.get_test_suite_dir(),
412            BINDINGS_FILE_NAME,
413            self.__bindings_file_set)
414        # go over all bindings.yml files, parse them and fill data structures
415        self.__bindings_file_set_work()
416        self.__lookup_compile_opts_in_clade_db()
417        framework_header_file_set = set()
418        Util.find_files_by_extension(
419            self.__args_parser.get_test_suite_dir(),
420            HEADER_FILE_EXTENSION,
421            False,
422            framework_header_file_set)
423
424        # Instrumentator includes all headers from runtime/tests/intrusive-tests directory
425        # in all places where code is needed to be synced (according to bindings.yml).
426        # That is why it includes intrusive_test_option.h file, which contains RuntimeOptions,
427        # so framework cannot be used in libpandabase, compiler and etc, but actually
428        # intrusive_test_option.h is only needed to initialize testsuite (see Runtime::Create)
429        for header in framework_header_file_set:
430            if "intrusive_test_option.h" in header:
431                framework_header_file_set.remove(header)
432                break
433
434        # go over map elements (from instrumented file to sync points in that file)
435        for file in self.__file_to_sync_point_set:
436            # find line numbers of synchronization points
437            # in the source code and header files
438            self.__lookup_sync_points_with_libclang(file)
439            lines = Util.read_file_lines(file.path)
440            try:
441                sync_point_list = list(self.__file_to_sync_point_set[file])
442            except KeyError:
443                print(f"no key {file}")
444                sys.exit(-1)
445            try:
446                sync_point_list.sort(key=lambda sp_ref : self.__sync_point_to_line[sp_ref])
447            except KeyError:
448                print(f"no keys in {self.__sync_point_to_line}")
449                sys.exit(-1)
450
451            # Header files that should be '#included' in the instrumented file
452            header_file_set = set(framework_header_file_set)
453            self.__add_changes_to_files(header_file_set, sync_point_list, lines)
454            incl_text_list = []
455            for header in header_file_set:
456                incl_text_list.append("#include \"")
457                incl_text_list.append(header)
458                incl_text_list.append("\"\n")
459            lines.insert(0, ''.join(incl_text_list))
460            Util.write_lines_to_file(file.path, lines)
461
462    def __add_changes_to_files(self, header_file_set, sync_point_list, lines):
463        sync_point_idx = 0
464        # go over sync points in a file
465        while(sync_point_idx < len(sync_point_list)):
466            try:
467                sp_ref = sync_point_list[sync_point_idx]
468            except KeyError:
469                print(f"no key {sync_point_idx}")
470                sys.exit(-1)
471            sync_point_code = []
472            # go over synchronization actions for a synchronization point
473            # and insert source code of the synchronization actions into
474            # file lines
475
476            try:
477                sync_action_list = self.__sync_point_to_sync_action_list[sp_ref]
478            except KeyError:
479                print(f"no key {sp_ref}")
480                sys.exit(-1)
481
482            for sync_action in sync_action_list:
483                self.__rep_sync_actions_process(sp_ref, sync_point_code, sync_action, header_file_set)
484            sync_point_code.insert(0, "#if defined(INTRUSIVE_TESTING)\n")
485            sync_point_code.append("#endif\n")
486
487            try:
488                lines.insert(
489                    self.__sync_point_to_line[sp_ref] - 1 + sync_point_idx,
490                    ''.join(sync_point_code))
491            except KeyError:
492                print(f"no key {sp_ref}")
493                sys.exit(-1)
494
495            sync_point_idx += 1
496
497    def __rep_sync_actions_process(self, sp_ref, sync_point_code, sync_action, header_file_set):
498        if(sp_ref.has_method()):
499            if(len(sync_point_code) > 0):
500                sync_point_code.append("\nelse ")
501            sync_point_code.append("if(")
502            sync_point_code.append(CPP_CODE_TO_GET_TEST_ID_IN_PANDA)
503            sync_point_code.append(" == (uint32_t)")
504            try:
505                sync_point_code.append(self.__sync_action_to_test_id[sync_action])
506            except KeyError:
507                print(f"no key {sync_action}")
508                sys.exit(-1)
509            sync_point_code.append("){\n")
510        sync_point_code.append(sync_action.code)
511        if(sp_ref.has_method()):
512            sync_point_code.append("\n}")
513        sync_point_code.append("\n")
514        try:
515            header_file_set.update(self.__sync_action_to_declaration_set[sync_action])
516        except KeyError:
517            print(f"no key {sync_action}")
518            sys.exit(-1)
519
520    def __bindings_file_set_work(self):
521        for bindings_file in self.__bindings_file_set:
522            bindings_parser = BindingsFileParser(bindings_file)
523            bindings_parser.validate(BINDINGS_SCHEMA_FILE)
524            test_dir = self.__get_test_dir(bindings_file)
525            test_id = self.__get_test_id(test_dir)
526            declaration_set = bindings_parser.get_declaration_set(test_dir)
527            mapping = bindings_parser.get_mapping(self.__args_parser.get_src_dir())
528            # go over mapping from sync point to sync action in a single bindings.yml
529            for r in mapping:
530                a = mapping[r]
531                self.__sync_action_to_declaration_set[a] = declaration_set
532                self.__sync_action_to_test_id[a] = test_id
533                if(r not in self.__sync_point_to_sync_action_list):
534                    self.__sync_point_to_sync_action_list[r] = []
535                self.__sync_point_to_sync_action_list[r].append(a)
536                if(r.has_source()):
537                    f = HeaderFile(r.file, r.source)
538                else:
539                    f = SourceFile(r.file)
540                if(f not in self.__file_to_sync_point_set):
541                    self.__file_to_sync_point_set[f] = set()
542                self.__file_to_sync_point_set[f].add(r)
543
544    def __get_test_dir(self, bindings_file):
545        return os.path.dirname(bindings_file)
546
547    def __get_test_id(self, test_dir):
548        return os.path.basename(test_dir).upper()
549
550    # parse clade database file and find compilation options
551    # for source code modules
552    def __lookup_compile_opts_in_clade_db(self):
553        clade_cmds_file = self.__args_parser.get_clade_file()
554        c = Clade(work_dir=os.path.dirname(clade_cmds_file), cmds_file=clade_cmds_file)
555        f_list = list(self.__file_to_sync_point_set.keys())
556        f_list.sort(key=lambda f : f.including_source_file_path if isinstance(f, HeaderFile) else f.path)
557        # go over types of compilation commands
558        # (emitted by C and C++ compilers, 2 items in the list)
559        for cmd_type in ["CXX", "CC"]:
560            for cmd in c.get_all_cmds_by_type(cmd_type):
561                # go over input files of compilation commands
562                # i.e. parsed files (usually 1 item in the list)
563                for compiled_file_path in cmd["in"]:
564                    self.__filter_file(f_list, compiled_file_path, c, cmd)
565        if(len(f_list) > 0):
566            err_msg = ["Failed to find compilation command for source code modules:"]
567            for f in f_list:
568                if isinstance(f, HeaderFile):
569                    src_file_path = f.including_source_file_path
570                else:
571                    src_file_path = f.path
572                err_msg.append("\n")
573                err_msg.append(src_file_path)
574            raise FatalError(''.join(err_msg))
575
576    def __is_kind_of_libclang_function(self, kind):
577        return kind == CursorKind.FUNCTION_DECL \
578            or kind == CursorKind.CXX_METHOD \
579            or kind == CursorKind.FUNCTION_TEMPLATE \
580            or kind == CursorKind.CONSTRUCTOR \
581            or kind == CursorKind.DESTRUCTOR \
582            or kind == CursorKind.CONVERSION_FUNCTION
583
584    def __is_kind_of_libclang_class(self, kind):
585        return kind == CursorKind.CLASS_DECL \
586            or kind == CursorKind.CLASS_TEMPLATE \
587            or kind == CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION \
588            or kind == CursorKind.STRUCT_DECL
589
590    def __filter_file(self, f_list, compiled_file_path, c, cmd):
591        i = 0
592        # go over sorted file List
593        while(i < len(f_list)):
594            f = f_list[i]
595            if isinstance(f, HeaderFile):
596                src_file_path = f.including_source_file_path
597            else:
598                src_file_path = f.path
599            if(compiled_file_path == src_file_path):
600                compile_option_list = c.get_cmd_opts(cmd["id"])
601                compile_option_list.append('%s=%s' % (CLANG_CUR_WORKING_DIR_OPT, cmd["cwd"]))
602                if isinstance(f, HeaderFile):
603                    compile_option_list.extend(CPP_HEADER_COMPILE_OPTION_LIST)
604                self.__file_to_compile_option_list[f] = compile_option_list
605                f_list.pop(i)
606            elif(compiled_file_path < src_file_path):
607                break
608            else:
609                i += 1
610
611    # Find line numbers of synchronization points
612    # in source code modules and header files
613    def __lookup_sync_points_with_libclang(self, file):
614        try:
615            not_found = list(self.__file_to_sync_point_set[file])
616        except KeyError:
617            print(f"no key {file}")
618            sys.exit(-1)
619
620        candidates = []
621
622        clang_index = Index.create()
623        try:
624            tu = clang_index.parse(file.path, args=self.__file_to_compile_option_list[file])
625        except KeyError:
626            print(f"no key {file}")
627            sys.exit(-1)
628        for token in tu.get_tokens(extent=tu.cursor.extent):
629            if(token.kind != TokenKind.COMMENT):
630                continue
631            match = re.match(r'\s*/\*\s*@sync\s+([0-9]+).*', token.spelling)
632            if(not match):
633                continue
634
635            candidates.clear()
636            candidates.extend(not_found)
637
638            self.__clear_candidates_by_match(candidates, match)
639            self.__clear_candidates_by_token(candidates, token)
640
641            for sync_point in candidates:
642                self.__sync_point_to_line[sync_point] = token.extent.end.line + 1
643                not_found.remove(sync_point)
644
645        if(len(not_found) > 0):
646            err_msg = ["Failed to find synchronization points:"]
647            for sync_point in not_found:
648                err_msg.append("\n")
649                err_msg.append(sync_point.to_string())
650            raise FatalError(''.join(err_msg))
651
652    def __clear_candidates_by_match(self, candidates, match):
653        i = 0
654        while(i < len(candidates)):
655            if(int(match.group(1)) != candidates[i].index):
656                candidates.pop(i)
657            else:
658                i += 1
659
660    def __clear_candidates_by_token(self, candidates, token):
661        i = 0
662        while(i < len(candidates)):
663            need_continue = False
664            if(candidates[i].has_method()):
665                need_continue = self.__check_for_method(candidates, token, i)
666
667            elif(candidates[i].has_class()):
668                if(not(self.__is_kind_of_libclang_class(token.cursor.kind)) \
669                    or (token.cursor.spelling != candidates[i].cl)):
670                    candidates.pop(i)
671                    continue
672            if(need_continue):
673                continue
674            i += 1
675
676    def __check_for_method(self, candidates, token, i):
677        if(not(hasattr(token.cursor, "semantic_parent"))):
678            candidates.pop(i)
679            return True
680        sem_parent = token.cursor.semantic_parent
681        if(not(self.__is_kind_of_libclang_function(sem_parent.kind)) \
682            or (sem_parent.spelling != candidates[i].method)):
683            candidates.pop(i)
684            return True
685
686        if(candidates[i].has_class()):
687            if(not(hasattr(sem_parent, "semantic_parent"))):
688                candidates.pop(i)
689                return True
690            sem_grand_parent = sem_parent.semantic_parent
691            if(not(self.__is_kind_of_libclang_class(sem_grand_parent.kind)) \
692                or (sem_grand_parent.spelling != candidates[i].cl)):
693                candidates.pop(i)
694                return True
695        return False
696
697
698if __name__ == '__main__':
699    err_msg_suffix = "Error! Source code instrumentation failed. Reason:"
700    try:
701        exit_code = 0
702        argument_parser = ArgsParser()
703        instrumentator = Instrumentator(argument_parser)
704        instrumentator.run()
705    except yaml.YAMLError as yaml_err:
706        exit_code = 1
707        print(err_msg_suffix)
708        traceback.print_exc(file=sys.stdout, limit=0)
709    except yamale.YamaleError as yamale_err:
710        exit_code = 2
711        print(err_msg_suffix)
712        for res in yamale_err.results:
713            print("Error validating '%s' against schema '%s'\n\t" % (res.data, res.schema))
714            for er in res.errors:
715                print('\t%s' % er)
716    except OSError as os_err:
717        exit_code = 3
718        err_message = [os_err.strerror]
719        if(hasattr(os_err, 'filename') and (os_err.filename is not None)):
720            err_message.append(": ")
721            err_message.append(os_err.filename)
722        if(hasattr(os_err, 'filename2') and (os_err.filename2 is not None)):
723            err_message.append(", ")
724            err_message.append(os_err.filename2)
725        print(''.join(err_message))
726    except FatalError as ferr:
727        exit_code = 4
728        print(err_msg_suffix, ferr.get_err_msg())
729    except Exception as err:
730        exit_code = 5
731        print(err_msg_suffix)
732        traceback.print_exc(file=sys.stdout)
733    sys.exit(exit_code)
734