From fa101790d3e5dc50b473ecfd3df8febb91bf7b69 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 24 Aug 2023 18:11:39 -0400 Subject: [PATCH] Remove `-Wno-error=array-bounds` from several build configurations (#28835) * Remove -Wno-error=array-bounds from nlfaultinjection library * Remove some array-bounds from esp32 as well: nlfaultinjection should be fixed separately * Remove nlfaultinjection as a submodule, add only relevant files with patch for gcc12 support. * Restyle * Update a few more include paths for nlfaultinjection * exclude nlfaultinjection from clangtidy * Restyle * Apply clang-tidy fixes to nlfaultinjection.cpp --- .gitmodules | 4 - .../all-clusters-app/esp32/CMakeLists.txt | 2 - .../esp32/main/CMakeLists.txt | 2 +- .../CHIPTest/app/src/main/cpp/CMakeLists.txt | 2 +- examples/lighting-app/esp32/CMakeLists.txt | 2 - .../tests/expected_test_cmakelists.txt | 2 +- scripts/examples/tests/test_project.json | 2 +- third_party/nlfaultinjection/BUILD.gn | 14 +- third_party/nlfaultinjection/README.md | 214 +++++ .../include/nlfaultinjection.hpp | 343 +++++++ third_party/nlfaultinjection/repo | 1 - .../nlfaultinjection/src/nlfaultinjection.cpp | 872 ++++++++++++++++++ 12 files changed, 1436 insertions(+), 24 deletions(-) create mode 100644 third_party/nlfaultinjection/README.md create mode 100644 third_party/nlfaultinjection/include/nlfaultinjection.hpp delete mode 160000 third_party/nlfaultinjection/repo create mode 100644 third_party/nlfaultinjection/src/nlfaultinjection.cpp diff --git a/.gitmodules b/.gitmodules index 74298af8ebfdad..f9050a0595cd6a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = third_party/nlassert/repo url = https://github.com/nestlabs/nlassert.git branch = master -[submodule "nlfaultinjection"] - path = third_party/nlfaultinjection/repo - url = https://github.com/nestlabs/nlfaultinjection.git - branch = master [submodule "nlio"] path = third_party/nlio/repo url = https://github.com/nestlabs/nlio.git diff --git a/examples/all-clusters-app/esp32/CMakeLists.txt b/examples/all-clusters-app/esp32/CMakeLists.txt index d5508c4197d790..eac3c948a3e0c9 100644 --- a/examples/all-clusters-app/esp32/CMakeLists.txt +++ b/examples/all-clusters-app/esp32/CMakeLists.txt @@ -46,8 +46,6 @@ idf_build_set_property(COMPILE_OPTIONS "-Wno-format-nonliteral;-Wno-format-secur # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80635 idf_build_set_property(COMPILE_OPTIONS "-Wno-error=maybe-uninitialized" APPEND) -idf_build_set_property(COMPILE_OPTIONS "-Wno-error=array-bounds" APPEND) - flashing_script() if (CONFIG_ENABLE_PW_RPC) diff --git a/examples/all-clusters-app/esp32/main/CMakeLists.txt b/examples/all-clusters-app/esp32/main/CMakeLists.txt index caacc1c5b2acfb..356c3729853f4a 100644 --- a/examples/all-clusters-app/esp32/main/CMakeLists.txt +++ b/examples/all-clusters-app/esp32/main/CMakeLists.txt @@ -21,7 +21,7 @@ set(PRIV_INCLUDE_DIRS_LIST "${CMAKE_CURRENT_LIST_DIR}/include" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/providers" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/platform/esp32" - "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/nlfaultinjection/repo/include" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/nlfaultinjection/include" ) set(SRC_DIRS_LIST "${CMAKE_CURRENT_LIST_DIR}" diff --git a/examples/android/CHIPTest/app/src/main/cpp/CMakeLists.txt b/examples/android/CHIPTest/app/src/main/cpp/CMakeLists.txt index 78028c9be4b997..7c86548554fea5 100644 --- a/examples/android/CHIPTest/app/src/main/cpp/CMakeLists.txt +++ b/examples/android/CHIPTest/app/src/main/cpp/CMakeLists.txt @@ -33,7 +33,7 @@ set_property(TARGET CHIPTest APPEND PROPERTY INCLUDE_DIRECTORIES ${MATTER_SDK_ROOT}/config/standalone/ ${MATTER_SDK_ROOT}/third_party/nlassert/repo/include/ ${MATTER_SDK_ROOT}/third_party/nlio/repo/include/ - ${MATTER_SDK_ROOT}/third_party/nlfaultinjection/repo/include/ + ${MATTER_SDK_ROOT}/third_party/nlfaultinjection/include/ ${MATTER_SDK_ROOT}/third_party/nlunit-test/repo/src/) set_target_properties(CHIPTest PROPERTIES COMPILE_DEFINITIONS "CHIP_HAVE_CONFIG_H=1") diff --git a/examples/lighting-app/esp32/CMakeLists.txt b/examples/lighting-app/esp32/CMakeLists.txt index 356a3cd14d6b64..ab349bb2ec4d86 100644 --- a/examples/lighting-app/esp32/CMakeLists.txt +++ b/examples/lighting-app/esp32/CMakeLists.txt @@ -57,8 +57,6 @@ idf_build_set_property(COMPILE_OPTIONS "-Wno-error=maybe-uninitialized" APPEND) #For the H2, -Werror=uninitialized will cause an error in "src/lib/support/LambdaBridge.h" idf_build_set_property(COMPILE_OPTIONS "-Wno-error=uninitialized" APPEND) -#For ESP32-C6, -Werror=array-bounds will cause an error in 'third_party/nlfaultinjection/repo/src/nlfaultinjection.cpp' -idf_build_set_property(COMPILE_OPTIONS "-Wno-error=array-bounds" APPEND) flashing_script() diff --git a/scripts/examples/tests/expected_test_cmakelists.txt b/scripts/examples/tests/expected_test_cmakelists.txt index 78e20592dbbd76..e8e2d3f2737bfb 100644 --- a/scripts/examples/tests/expected_test_cmakelists.txt +++ b/scripts/examples/tests/expected_test_cmakelists.txt @@ -70,7 +70,7 @@ set_property(TARGET "${target}" APPEND PROPERTY INCLUDE_DIRECTORIES "//TEST_ROOT_PATH/config/standalone/" "//TEST_ROOT_PATH/third_party/nlassert/repo/include/" "//TEST_ROOT_PATH/third_party/nlio/repo/include/" - "//TEST_ROOT_PATH/third_party/nlfaultinjection/repo/include/" + "//TEST_ROOT_PATH/third_party/nlfaultinjection/include/" "//TEST_ROOT_PATH/third_party/nlunit-test/repo/src/") set_target_properties("${target}" PROPERTIES COMPILE_DEFINITIONS "CHIP_HAVE_CONFIG_H=1;") set_target_properties("${target}" PROPERTIES COMPILE_FLAGS "-O0 -std=gnu++14 -fno-rtti ") diff --git a/scripts/examples/tests/test_project.json b/scripts/examples/tests/test_project.json index dbaf96a8bb2149..d15f2abd9e2ce8 100644 --- a/scripts/examples/tests/test_project.json +++ b/scripts/examples/tests/test_project.json @@ -88,7 +88,7 @@ "//config/standalone/", "//third_party/nlassert/repo/include/", "//third_party/nlio/repo/include/", - "//third_party/nlfaultinjection/repo/include/", + "//third_party/nlfaultinjection/include/", "//third_party/nlunit-test/repo/src/" ], "ldflags": ["-march=armv8-a", "-O0"], diff --git a/third_party/nlfaultinjection/BUILD.gn b/third_party/nlfaultinjection/BUILD.gn index 740ee69c4d6d28..e436d620309b25 100644 --- a/third_party/nlfaultinjection/BUILD.gn +++ b/third_party/nlfaultinjection/BUILD.gn @@ -15,26 +15,18 @@ import("//build_overrides/nlassert.gni") config("nlfaultinjection_config") { - include_dirs = [ "repo/include" ] -} - -config("nlfaultinjection_disable_warnings_config") { - cflags = [ - # We are intentionally inducing faults with this library so it makes sense to ignore errors. - "-Wno-error=array-bounds", - ] + include_dirs = [ "include" ] } static_library("nlfaultinjection") { sources = [ - "repo/include/nlfaultinjection.hpp", - "repo/src/nlfaultinjection.cpp", + "include/nlfaultinjection.hpp", + "src/nlfaultinjection.cpp", ] deps = [ "${nlassert_root}:nlassert" ] public_configs = [ ":nlfaultinjection_config" ] - configs += [ ":nlfaultinjection_disable_warnings_config" ] output_name = "libnlfaultinjection" output_dir = "${root_out_dir}/lib" diff --git a/third_party/nlfaultinjection/README.md b/third_party/nlfaultinjection/README.md new file mode 100644 index 00000000000000..47396cb1b8f9dc --- /dev/null +++ b/third_party/nlfaultinjection/README.md @@ -0,0 +1,214 @@ +## Content + +The files here originate from https://github.com/nestlabs/nlfaultinjection.git + +The reason for a stand-alone/reduced copy is the requirement to patch the `Die` +function for gcc12+ to avoid a `-Warray-bounds` error. + +## License + +Original repository was licensed under Apache2: + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` diff --git a/third_party/nlfaultinjection/include/nlfaultinjection.hpp b/third_party/nlfaultinjection/include/nlfaultinjection.hpp new file mode 100644 index 00000000000000..99bfec36b4236e --- /dev/null +++ b/third_party/nlfaultinjection/include/nlfaultinjection.hpp @@ -0,0 +1,343 @@ +/* + * + * Copyright 2016-2018 The nlfaultinjection Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file + * Header file for the fault-injection utilities. + * This module provides an object to manager a set of fault IDs, + * and a macro to simplify the insertion of fault code in + * production code. + */ + +#ifndef FAULT_INJECTION_H_ +#define FAULT_INJECTION_H_ + +#include +#include +#include + +namespace nl { + +namespace FaultInjection { + +typedef struct _Record Record; +typedef struct _Callback Callback; + +typedef uint32_t Identifier; + +typedef const char * Name; + +enum +{ + kMaxFaultArgs = 8 /**< The max number of arguments that can be stored in a fault */ +}; + +/** + * A fault-injection callback function. + * A function of this type can be attached to a fault ID, and will be invoked every time + * FaultInjectionMgr::CheckFault is called on the fault ID. + * The main purpose of registering a callback is to be able to turn on lower-level faults from + * higher level events; e.g., "fail in SendMessage for the next WDM ViewRequest." + * The callback can also be used to let the application decide if the fault is supposed to be + * triggered at each invocation. If the callback returns true, the fault is triggered. + * + * @param[in] aFaultID The fault ID + * @param[in] aFaultRecord Pointer to the Record for aFaultID; + * This allows the callback to check how the fault is configured + * before taking action. + * @param[in] aContext The pointer stored by the application in the Callback + * structure. + * @return true if the fault is to be triggered. false if this callback does not want to + * force the fault to be triggered. + */ +typedef bool (*CallbackFn)(Identifier aId, Record * aFaultRecord, void * aContext); + +/** + * A linked-list node to hold a callback function to be attached to a fault ID. + * The application can store a pointer in the mContext member. + */ +struct _Callback +{ + CallbackFn mCallBackFn; /**< Callback function pointer */ + void * mContext; /**< Pointer for the application to store a context for mCallbackFn */ + Callback * mNext; /**< Linked list next pointer */ +}; + +/** + * Structure that stores the configuration of a given fault ID. + * The module defining the fault-injection API needs to provide an array of Record + * and pass it to its Manager instance via the Init method. + */ +struct _Record +{ + uint16_t mNumCallsToSkip; /**< The number of times this fault should not trigger before it + starts failing */ + uint16_t mNumCallsToFail; /**< The number of times this fault should fail, before disabling + itself */ + uint8_t mPercentage; /**< A number between 0 and 100 that indicates the percentage of + times the fault should be triggered */ + uint8_t mReboot; /**< This fault should reboot the system */ + + uint8_t mLengthOfArguments; /**< The length of the array pointed to by mArguments */ + + uint8_t mNumArguments; /**< The number of items currently stored in the array pointed to by mArguments */ + + Callback * mCallbackList; /**< A list of callbacks */ + + uint32_t mNumTimesChecked; /**< The number of times the fault location was executed */ + + int32_t * mArguments; /**< A pointer to an array of integers to store extra arguments; this array is meant to + be populated by either of the following: + - the ParseFaultInjectionStr, so the values are available at the fault injection site + and when the fault is injected. + - the logic around the fault injection site, to save useful values that can then + be logged by a callback installed by the application, and so made available for use + in subsequent test runs as arguments to the injected code. + For example, the values can be exact arguments to be passed in, or ranges to be + iterated on (like the length of a byte array to be fuzzed). */ +}; + +/** + * The module that provides a fault-injection API needs to provide an instance of Manager, + * and initialize it with an array of Record. + */ +class Manager +{ +public: + static const bool kMutexDoNotTake = false; + static const bool kMutexTake = true; + + int32_t Init(size_t inNumFaults, Record * inFaultArray, Name inManagerName, const Name * inFaultNames); + + int32_t FailRandomlyAtFault(Identifier inId, uint8_t inPercentage = 10); + + int32_t FailAtFault(Identifier inId, uint32_t inNumCallsToSkip, uint32_t inNumCallsToFail); + int32_t FailAtFault(Identifier inId, uint32_t inNumCallsToSkip, uint32_t inNumCallsToFail, bool inTakeMutex); + + int32_t RebootAtFault(Identifier inId); + + int32_t StoreArgsAtFault(Identifier inId, uint16_t inNumArgs, int32_t * inArgs); + + int32_t InsertCallbackAtFault(Identifier inId, Callback * inCallBack); + + int32_t RemoveCallbackAtFault(Identifier inId, Callback * inCallBack); + int32_t RemoveCallbackAtFault(Identifier inId, Callback * inCallBack, bool inTakeMutex); + + bool CheckFault(Identifier inId); + bool CheckFault(Identifier inId, bool inTakeMutex); + bool CheckFault(Identifier inId, uint16_t & outNumArgs, int32_t *& outArgs); + bool CheckFault(Identifier inId, uint16_t & outNumArgs, int32_t *& outArgs, bool inTakeMutex); + + /** + * Get the number of fault IDs defined by the Manager. + * + * @return the number of fault IDs defined by the Manager. + */ + size_t GetNumFaults(void) const { return mNumFaults; } + + /** + * Get the name of the Manager. Every Manager object is initialized with a name, + * so that faults can be configured using human-readable strings. + * + * @return The Manager's name, as a pointer to a const null-terminated string. + */ + Name GetName(void) const { return mName; } + + /** + * Get a pointer to the array of fault names. + * + * @return A pointer to a const char pointer. The array length + * is the number of faults defined by the Manager; see GetNumFaults. + */ + const Name * GetFaultNames(void) const { return mFaultNames; } + + const Record * GetFaultRecords(void) const { return mFaultRecords; } + + void ResetFaultCounters(void); + + int32_t ResetFaultConfigurations(void); + int32_t ResetFaultConfigurations(Identifier inId); + + /** + * Pointer to a function to be called when entering or exiting a critical section + */ + typedef void (*LockCbFn)(void * inLockContext); + + /** + * On multithreaded systems, the Manager's data structures need to be protected with + * a mutex; a common example is the case of the system being configured by one thread (calling + * ParseFaultInjectionStr, ResetFaultConfigurations etc) while another thread runs the + * code in which faults are injected. + * The application is supposed to provide two function pointers, one to enter the + * critical section and one to exit it. + * The application can decide to use the same mutex for all Managers, or to protect different + * Managers with different mutexes. + * In case the platform does not support re-entrant mutexes, the application's callbacks installed + * at the fault injection points must use the inTakeMutex argument to the Manager's method to + * avoid taking the same mutes twice. + * + * @param[in] inLock The callback to take the mutex + * @param[in] inUnlock The callback to release the mutex + * @param[in] inLockContext a void pointer to the mutex context, which is passed to the + * callbacks + */ + void SetLockCallbacks(LockCbFn inLock, LockCbFn inUnlock, void * inLockContext) + { + mLock = inLock; + mUnlock = inUnlock; + mLockContext = inLockContext; + } + + void Lock(void); + + void Unlock(void); + +private: + size_t mNumFaults; + Record * mFaultRecords; + Name mName; + const Name * mFaultNames; + LockCbFn mLock; + LockCbFn mUnlock; + void * mLockContext; +}; + +/** + * The type of a function that returns a reference to a Manager + * The module is expected to provide such a function so that + * it can be added to an array of GetManagerFn instances and passed to + * ParseFaultInjectionStr. + */ +typedef Manager & (*GetManagerFn)(void); + +/** + * A callback for the application to implement support for restarting + * the system. + */ +typedef void (*RebootCallbackFn)(void); + +/** + * A callback to inform the application that a Manager has decided to inject a fault. + * The main use of this type of callback is to print a log statement. + */ +typedef void (*PostInjectionCallbackFn)(Manager * aManager, Identifier aId, Record * aFaultRecord); + +/** + * A table of callbacks used by all managers. + */ +typedef struct _GlobalCallbackTable +{ + RebootCallbackFn mRebootCb; /**< See RebootCallbackFn */ + PostInjectionCallbackFn mPostInjectionCb; /**< See PostInjectionCallbackFn */ +} GlobalCallbackTable; + +/** + * A structure to hold global state that is used + * by all Managers. + */ +typedef struct _GlobalContext +{ + GlobalCallbackTable mCbTable; /**< A table of callbacks */ +} GlobalContext; + +void SetGlobalContext(GlobalContext * inGlobalContext); + +bool ParseFaultInjectionStr(char * inStr, const GetManagerFn * inArray, size_t inArraySize); + +/** + * A structure to store an array of GetManagerFn arrays, used by ParseFaultInjectionStr. + * The main purpose of this is to pass a collection of static tables owned of GetManagerFn owned + * by separate modules to ParseFaultInjectionStr. + */ +typedef struct _ManagerTable +{ + const GetManagerFn * mArray; /**< A pointer to an array of GetManagerFn */ + size_t mNumItems; /**< The length of mArray */ +} ManagerTable; +bool ParseFaultInjectionStr(char * inStr, const ManagerTable * inTables, size_t inNumTables); + +} // namespace FaultInjection + +} // namespace nl + +/** + * The macro to inject the fault code. + * Typically the module offering a fault-injection API + * wraps this macro into a macro that: + * 1. translates to a no-op if faults are disabled at compile time. + * 2. hardcodes aManager to the module's own. + * + * @param[in] aManager The Manager + * @param[in] aId The fault ID + * @param[in] aStatements C++ code to be executed if the fault is to be injected. + * For example: + * - a single statement without terminating ";" + * - two or more statements, separated by ";" + * - a whole C++ block, enclosed in "{}" + */ +#define nlFAULT_INJECT(aManager, aId, aStatements) \ + do \ + { \ + if ((aManager).CheckFault(aId)) \ + { \ + aStatements; \ + } \ + } while (0) + +/** + * The macro to inject fault code that depends on extra arguments (see StoreArgsAtFault). + * Typically the module offering a fault-injection API + * wraps this macro into a macro that: + * 1. translates to a no-op if faults are disabled at compile time; + * 2. hardcodes aManager to the module's own. + * + * Note that on multithreaded systems the statements that consume the + * arguments need to be protected by the Manager's mutex. + * Any other statements should be executed outside of the mutex + * (this is a must in particular for statements that trigger the execution + * of a different fault injection site). + * + * @param[in] aManager The Manager + * @param[in] aId The fault ID + * @param[in] aProtectedStatements C++ code to be executed if the fault is to be injected, + * while holding the Manager's mutex. + * These statements usually refer to a local array of int32_t + * args called faultArgs, of length numFaultArgs to access the extra arguments. + * @param[in] aUnprotectedStatements C++ code to be executed if the fault is to be injected, + * outside of the Manager's mutex + */ +#define nlFAULT_INJECT_WITH_ARGS(aManager, aId, aProtectedStatements, aUnprotectedStatements) \ + do \ + { \ + uint16_t numFaultArgs = 0; \ + int32_t * faultArgs = NULL; \ + \ + (aManager).Lock(); \ + if ((aManager).CheckFault(aId, numFaultArgs, faultArgs, nl::FaultInjection::Manager::kMutexDoNotTake)) \ + { \ + aProtectedStatements; \ + (aManager).Unlock(); \ + aUnprotectedStatements; \ + } \ + else \ + { \ + (aManager).Unlock(); \ + } \ + } while (0) + +#endif // FAULT_INJECTION_H_ diff --git a/third_party/nlfaultinjection/repo b/third_party/nlfaultinjection/repo deleted file mode 160000 index e0de0ab4f52c1d..00000000000000 --- a/third_party/nlfaultinjection/repo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0de0ab4f52c1d1cc7f3948557a1abd0fceeb5ef diff --git a/third_party/nlfaultinjection/src/nlfaultinjection.cpp b/third_party/nlfaultinjection/src/nlfaultinjection.cpp new file mode 100644 index 00000000000000..43842644d5050b --- /dev/null +++ b/third_party/nlfaultinjection/src/nlfaultinjection.cpp @@ -0,0 +1,872 @@ +/* + * + * Copyright 2016-2018 The nlfaultinjection Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file + * Implementation of the fault-injection utilities. + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#include +#include +#include + +#include + +#include + +namespace nl { + +namespace FaultInjection { + +static void Die() __attribute__((noreturn)); + +static GlobalContext * sGlobalContext = nullptr; + +/** + * The callback function that implements the deterministic + * injection feature (see FailAtFault). + */ +static bool DeterministicCbFn(Identifier aId, Record * aRecord, void * aContext) +{ + bool retval = false; + + (void) aId; + (void) aContext; + + if (aRecord->mNumCallsToSkip) + { + aRecord->mNumCallsToSkip--; + } + else if (aRecord->mNumCallsToFail) + { + aRecord->mNumCallsToFail--; + retval = true; + } + + return retval; +} + +/** + * Callback list node for DeterministicCbFn. + * This node terminates all callback lists. + */ +static Callback sDeterministicCb = { DeterministicCbFn, nullptr, nullptr }; + +/** + * The callback function that implements the random + * injection feature (see FailRandomlyAtFault). + */ +static bool RandomCbFn(Identifier aId, Record * aRecord, void * aContext) +{ + bool retval = false; + + (void) aId; + (void) aContext; + + if (aRecord->mPercentage > 0) + { + int randValue = (rand() % 100) + 1; + if (randValue <= aRecord->mPercentage) + { + retval = true; + } + } + + return retval; +} + +/** + * Callback list node for RandomCbFn. + * Note that this is initialized to point to sDeterministicCb. + * All Record instances are initialized to point to + * this callback node. + */ +static Callback sRandomCb = { RandomCbFn, nullptr, &sDeterministicCb }; + +/** + * Alias for the address of the first default callback. + */ +static const Callback * sEndOfCustomCallbacks = &sRandomCb; + +/** + * Initialize the Manager instance. + * + * @param[in] inNumFaults The size of inFaultArray, equal to the number of fault IDs. + * @param[in] inFaultArray A pointer to an array of Record, in which this object + * will store the configuration of each fault. + * @param[in] inManagerName A pointer to a C string containing the name of the Manager. + * @param[in] inFaultNames A pointer to an array of inNumFaults C strings that describe + * each fault ID. + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::Init(size_t inNumFaults, Record * inFaultArray, Name inManagerName, const Name * inFaultNames) +{ + int32_t err = 0; + Identifier i; + + nlEXPECT_ACTION((inNumFaults > 0 && inFaultArray && inManagerName && inFaultNames), exit, err = -EINVAL); + + mName = inManagerName; + mNumFaults = inNumFaults; + mFaultRecords = inFaultArray; + mFaultNames = inFaultNames; + mLock = nullptr; + mUnlock = nullptr; + mLockContext = nullptr; + + // Link all callback lists to the two default callbacks. + for (i = 0; i < mNumFaults; i++) + { + mFaultRecords[i].mCallbackList = &sRandomCb; + } + +exit: + return err; +} + +/** + * Configure a fault to be triggered randomly, with a given probability defined as a percentage + * This is meant to be used on live systems to generate a build that will encounter random failures. + * + * @param[in] inId The fault ID + * @param[in] inPercentage An integer between 0 and 100. 100 means "always". 0 means "never". + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::FailRandomlyAtFault(Identifier inId, uint8_t inPercentage) +{ + int32_t err = 0; + + nlEXPECT_ACTION((inId < mNumFaults && inPercentage <= 100), exit, err = -EINVAL); + + Lock(); + + mFaultRecords[inId].mNumCallsToSkip = 0; + mFaultRecords[inId].mNumCallsToFail = 0; + mFaultRecords[inId].mPercentage = inPercentage; + + Unlock(); + +exit: + return err; +} + +/** + * Configure a fault to be triggered deterministically. + * + * @param[in] inId The fault ID + * @param[in] inNumCallsToSkip The number of times this fault is to be skipped before it + * starts to fail. + * @param[in] inNumCallsToFail The number of times the fault should be triggered. + * @param[in] inTakeMutex By default this method takes the Manager's mutex. + * If inTakeMutex is set to kMutexDoNotTake, the mutex is not taken. + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::FailAtFault(Identifier inId, uint32_t inNumCallsToSkip, uint32_t inNumCallsToFail, bool inTakeMutex) +{ + int32_t err = 0; + + nlEXPECT_ACTION(inId < mNumFaults && inNumCallsToSkip <= UINT16_MAX && inNumCallsToFail <= UINT16_MAX, exit, err = -EINVAL); + + if (inTakeMutex) + { + Lock(); + } + + mFaultRecords[inId].mNumCallsToSkip = static_cast(inNumCallsToSkip); + mFaultRecords[inId].mNumCallsToFail = static_cast(inNumCallsToFail); + mFaultRecords[inId].mPercentage = 0; + + if (inTakeMutex) + { + Unlock(); + } + +exit: + return err; +} + +/** + * @overload int32_t FailAtFault(Identifier inId, uint32_t inNumCallsToSkip, uint32_t inNumCallsToFail, bool inTakeMutex) + */ +int32_t Manager::FailAtFault(Identifier inId, uint32_t inNumCallsToSkip, uint32_t inNumCallsToFail) +{ + return FailAtFault(inId, inNumCallsToSkip, inNumCallsToFail, kMutexTake); +} + +/** + * Configure a fault to reboot the system when triggered. + * If the application has installed a RebootCallbackFn, it will + * be invoked when fault inId is triggered. + * If the application has not installed the callback, the system + * will crash. + * + * @param[in] inId The fault ID + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::RebootAtFault(Identifier inId) +{ + int32_t err = 0; + + nlEXPECT_ACTION(inId < mNumFaults, exit, err = -EINVAL); + + Lock(); + + mFaultRecords[inId].mReboot = true; + + Unlock(); + +exit: + return err; +} + +/** + * Store a set of arguments for a given fault ID. + * The array of arguments is made available to the code injected with + * the nlFAULT_INJECT macro. + * For this to work for a given fault ID, the Manager must allocate memory to + * store the arguments and configure the Record's mLengthOfArguments and + * mArguments members accordingly. + * + * @param[in] inId The fault ID + * @param[in] inNumArgs The number of arguments in the array pointed to by inArgs. + * @param[in] inArgs The pointer to the array of integers to be stored in the fault + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::StoreArgsAtFault(Identifier inId, uint16_t inNumArgs, int32_t * inArgs) +{ + int32_t err = 0; + size_t i; + + nlEXPECT_ACTION(inId < mNumFaults && mFaultRecords[inId].mArguments != nullptr && + mFaultRecords[inId].mLengthOfArguments >= inNumArgs && inNumArgs <= UINT8_MAX, + exit, err = -EINVAL); + + Lock(); + + for (i = 0; i < inNumArgs; i++) + { + mFaultRecords[inId].mArguments[i] = inArgs[i]; + } + + mFaultRecords[inId].mNumArguments = static_cast(inNumArgs); + + Unlock(); + +exit: + return err; +} + +/** + * Attach a callback to a fault ID. + * Calling this twice does not attach the callback twice. + * + * @param[in] inId The fault ID + * @param[in] inCallback The callback node to be attached to the fault + * + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::InsertCallbackAtFault(Identifier inId, Callback * inCallBack) +{ + int32_t err = 0; + + // Make sure it's not already there + err = RemoveCallbackAtFault(inId, inCallBack); + + nlEXPECT_SUCCESS(err, exit); + + Lock(); + + // Insert the callback at the beginning of the list. + // Remember that all lists end into the two default (deterministic + // and random) callbacks! + inCallBack->mNext = mFaultRecords[inId].mCallbackList; + mFaultRecords[inId].mCallbackList = inCallBack; + + Unlock(); + +exit: + return err; +} + +/** + * Detaches a callback from a fault. + * + * @param[in] inId The fault + * @param[in] inCallback The callback node to be removed. + * @param[in] inTakeMutex By default this method takes the Manager's mutex. + * If inTakeMutex is set to kMutexDoNotTake, the mutex is not taken. + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::RemoveCallbackAtFault(Identifier inId, Callback * inCallBack, bool inTakeMutex) +{ + int32_t err = 0; + Callback ** cb = nullptr; + + nlEXPECT_ACTION((inId < mNumFaults) && (inCallBack != nullptr), exit, err = -EINVAL); + + if (inTakeMutex) + { + Lock(); + } + + cb = &mFaultRecords[inId].mCallbackList; + + while (*cb != nullptr) + { + if (*cb == inCallBack) + { + *cb = (*cb)->mNext; + break; + } + cb = &((*cb)->mNext); + } + + if (inTakeMutex) + { + Unlock(); + } + +exit: + return err; +} + +/** + * @overload int32_t Manager::RemoveCallbackAtFault(Identifier inId, Callback *inCallBack, bool inTakeMutex) + */ +int32_t Manager::RemoveCallbackAtFault(Identifier inId, Callback * inCallBack) +{ + return RemoveCallbackAtFault(inId, inCallBack, kMutexTake); +} + +/** + * When the program traverses the location at which a fault should be injected, this method is invoked + * on the manager to query the configuration of the fault ID. + * + * A fault can be triggered randomly, deterministically or on a call-by-call basis by a callback. + * All three types of trigger can be installed at the same time, and they all get a chance of + * injecting the fault. + * + * @param[in] inId The fault ID + * @param[in] inTakeMutex By default this method takes the Manager's mutex. + * If inTakeMutex is set to kMutexDoNotTake, the mutex is not taken. + * + * @return true if the fault should be injected; false otherwise. + */ +bool Manager::CheckFault(Identifier inId, bool inTakeMutex) +{ + bool retval = false; + Callback * cb = nullptr; + Callback * next = nullptr; + bool reboot = false; + + nlEXPECT(inId < mNumFaults, exit); + + if (inTakeMutex) + { + Lock(); + } + + cb = mFaultRecords[inId].mCallbackList; + + while (cb != nullptr) + { + // Save mNext now, in case the callback removes itself + // calling RemoveCallbackAtFault + next = cb->mNext; + if (cb->mCallBackFn(inId, &mFaultRecords[inId], cb->mContext)) + { + retval = true; + } + cb = next; + } + + reboot = mFaultRecords[inId].mReboot; + + if (retval && sGlobalContext && sGlobalContext->mCbTable.mPostInjectionCb) + { + sGlobalContext->mCbTable.mPostInjectionCb(this, inId, &mFaultRecords[inId]); + } + + if (retval && reboot) + { + // If the application has not setup a context and/or reboot callback, the system will crash + if (sGlobalContext && sGlobalContext->mCbTable.mRebootCb) + { + sGlobalContext->mCbTable.mRebootCb(); + } + else + { + Die(); + } + } + + mFaultRecords[inId].mNumTimesChecked++; + + if (inTakeMutex) + { + Unlock(); + } + +exit: + return retval; +} + +/** + * @overload bool CheckFault(Identifier inId, bool inTakeMutex) + */ +bool Manager::CheckFault(Identifier inId) +{ + return CheckFault(inId, kMutexTake); +} + +/** + * When the program traverses the location at which a fault should be injected, this method is invoked + * on the manager to query the configuration of the fault ID. + * + * This version of the method retrieves the arguments stored in the Record. + * + * A fault can be triggered randomly, deterministically or on a call-by-call basis by a callback. + * All three types of trigger can be installed at the same time, and they all get a chance of + * injecting the fault. + * + * @param[in] inId The fault ID + * @param[in] outNumArgs The length of the array pointed to by outArgs + * @param[in] outArgs The array of arguments configured for the faultId + * @param[in] inTakeMutex By default this method takes the Manager's mutex. + * If inTakeMutex is set to kMutexDoNotTake, the mutex is not taken. + * + * @return true if the fault should be injected; false otherwise. + */ +bool Manager::CheckFault(Identifier inId, uint16_t & outNumArgs, int32_t *& outArgs, bool inTakeMutex) +{ + bool retval = false; + + if (inTakeMutex) + { + Lock(); + } + + retval = CheckFault(inId, kMutexDoNotTake); + if (retval) + { + outNumArgs = mFaultRecords[inId].mNumArguments; + outArgs = mFaultRecords[inId].mArguments; + } + + if (inTakeMutex) + { + Unlock(); + } + + return retval; +} + +/** + * @overload bool CheckFault(Identifier inId, uint16_t &outNumArgs, int32_t *&outArgs, bool inTakeMutex) + */ +bool Manager::CheckFault(Identifier inId, uint16_t & outNumArgs, int32_t *& outArgs) +{ + return CheckFault(inId, outNumArgs, outArgs, kMutexTake); +} + +/** + * Reset the counters in the fault Records + * Note that calling this method does not impact the current configuration + * in any way (including the number of times a fault is to be skipped + * before it should fail). + */ +void Manager::ResetFaultCounters() +{ + Identifier id = 0; + + Lock(); + + for (id = 0; id < mNumFaults; id++) + { + mFaultRecords[id].mNumTimesChecked = 0; + } + + Unlock(); +} + +/** + * Reset the configuration of a fault Record + * + * @param[in] inId The fault ID + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::ResetFaultConfigurations(Identifier inId) +{ + Callback * cb; + int32_t err = 0; + + nlEXPECT_ACTION((inId < mNumFaults), exit, err = -EINVAL); + + Lock(); + + mFaultRecords[inId].mNumCallsToSkip = 0; + mFaultRecords[inId].mNumCallsToFail = 0; + mFaultRecords[inId].mPercentage = 0; + mFaultRecords[inId].mReboot = 0; + mFaultRecords[inId].mNumArguments = 0; + + cb = mFaultRecords[inId].mCallbackList; + // All callback handling code in this module is based on the assumption + // that custom callbacks are inserted at the beginning of the list + while (cb != sEndOfCustomCallbacks && cb != nullptr) + { + (void) RemoveCallbackAtFault(inId, cb, kMutexDoNotTake); + cb = mFaultRecords[inId].mCallbackList; + } + + Unlock(); + +exit: + return err; +} + +/** + * Reset the configuration of all fault Records + * + * @return -EINVAL if the inputs are not valid. + * 0 otherwise. + */ +int32_t Manager::ResetFaultConfigurations() +{ + int32_t err = 0; + Identifier id = 0; + + for (id = 0; id < mNumFaults; id++) + { + err = ResetFaultConfigurations(id); + nlEXPECT(err == 0, exit); + } + +exit: + return err; +} + +/** + * Take the Manager's mutex. + */ +void Manager::Lock() +{ + if (mLock) + { + mLock(mLockContext); + } +} + +/** + * Release the Manager's mutex. + */ +void Manager::Unlock() +{ + if (mUnlock) + { + mUnlock(mLockContext); + } +} + +/** + * Configure the instance of GlobalContext to use. + * On systems in which faults are configured and injected from different threads, + * this function should be called before threads are started. + * + * @param[in] inGlobalContext Pointer to the GlobalContext provided by the application + */ +void SetGlobalContext(GlobalContext * inGlobalContext) +{ + sGlobalContext = inGlobalContext; +} + +/** + * Parse an integer + * + * This implementation does not check for ERANGE, as it assumes a very simple + * underlying implementation of strtol. + * + * @param[in] str Pointer to a string representing an integer + * + * @param[out] num Pointer to the integer result + * + * @return true in case of success; false if the string does not + * contain an integer. + */ +static bool ParseInt(const char * str, int32_t * num) +{ + char * endptr = nullptr; + long tmp; + bool retval = true; + + tmp = strtol(str, &endptr, 10); + if (!endptr || *endptr != '\0') + { + retval = false; + } + else + { + *num = static_cast(tmp); + } + + return retval; +} + +/** + * Parse an unsigned integer + * + * @param[in] str Pointer to a string representing an insigned int + * + * @param[out] num Pointer to the unsigned integer result + * + * @return true in case of success; false if the string does not + * contain an unsigned integer. + */ +static bool ParseUInt(const char * str, uint32_t * num) +{ + bool retval = true; + int32_t tmpint = 0; + + retval = ParseInt(str, &tmpint); + if (retval) + { + if (tmpint < 0) + { + retval = false; + } + else + { + *num = static_cast(tmpint); + } + } + + return retval; +} + +/** + * Parse a fault-injection configuration string and apply the configuration. + * + * @param[in] aFaultInjectionStr The configuration string. An example of a valid string that + * enables two faults is "system_buffer_f5_s1:inet_send_p33" + * An example of a configuration string that + * also passes three integer arguments to the fault point is + * "system_buffer_f5_s1_a10_a7_a-4" + * The format is + * "__{f[_s],p}[_a]..." + * + * @param[in] inArray An array of GetManagerFn callbacks + * to be used to parse the string. + * + * @param[in] inArraySize Num of elements in inArray + * + * @return true if the string can be parsed completely; false otherwise + */ +bool ParseFaultInjectionStr(char * aFaultInjectionStr, const GetManagerFn * inArray, size_t inArraySize) +{ + ManagerTable table = { inArray, inArraySize }; + size_t numTables = 1; + + return ParseFaultInjectionStr(aFaultInjectionStr, &table, numTables); +} + +/** + * Parse a fault-injection configuration string and apply the configuration. + * + * @param[in] aFaultInjectionStr The configuration string. An example of a valid string that + * enables two faults is "system_buffer_f5_s1:inet_send_p33" + * An example of a configuration string that + * also passes three integer arguments to the fault point is + * "system_buffer_f5_s1_a10_a7_a-4" + * The format is + * "__{f[_s],p}[_a]..." + * + * @param[in] inTables An array of ManagerTable structures + * to be used to parse the string. + * + * @param[in] inNumTables Size of inTables + * + * @return true if the string can be parsed completely; false otherwise + */ +bool ParseFaultInjectionStr(char * aFaultInjectionStr, const ManagerTable * inTables, size_t inNumTables) +{ + char * tok1 = nullptr; + char * savePtr1 = nullptr; + char * tok2 = nullptr; + char * savePtr2 = nullptr; + char * outerString = aFaultInjectionStr; + size_t i = 0; + nl::FaultInjection::Identifier j = 0; + int err = 0; + bool retval = false; + int32_t args[kMaxFaultArgs]; + uint16_t numArgs = 0; + + nl::FaultInjection::Manager * mgr = nullptr; + nl::FaultInjection::Identifier faultId = 0; + + memset(args, 0, sizeof(args)); + + while ((tok1 = strtok_r(outerString, ":", &savePtr1))) + { + uint32_t numTimesToFail = 0; + uint32_t numTimesToSkip = 0; + uint32_t percentage = 0; + bool gotPercentage = false; + bool gotReboot = false; + bool gotArguments = false; + const Name * faultNames = nullptr; + + outerString = nullptr; + + tok2 = strtok_r(tok1, "_", &savePtr2); + nlEXPECT(tok2 != nullptr, exit); + + // this is the module + for (i = 0; i < inNumTables; i++) + { + for (j = 0; j < inTables[i].mNumItems; j++) + { + nl::FaultInjection::Manager & tmpMgr = inTables[i].mArray[j](); + if (!strcmp(tok2, tmpMgr.GetName())) + { + mgr = &tmpMgr; + break; + } + } + } + nlEXPECT(mgr != nullptr, exit); + + tok2 = strtok_r(nullptr, "_", &savePtr2); + nlEXPECT(tok2 != nullptr, exit); + + // this is the fault name + faultNames = mgr->GetFaultNames(); + for (j = 0; j < mgr->GetNumFaults(); j++) + { + if (!strcmp(tok2, faultNames[j])) + { + faultId = j; + break; + } + } + + nlEXPECT(j != mgr->GetNumFaults(), exit); + + while ((tok2 = strtok_r(nullptr, "_", &savePtr2))) + { + switch (tok2[0]) + { + case 'a': { + int32_t tmp = 0; + nlEXPECT(numArgs < kMaxFaultArgs, exit); + + gotArguments = true; + + nlEXPECT(ParseInt(&(tok2[1]), &tmp), exit); + args[numArgs++] = tmp; + } + break; + case 'f': + nlEXPECT(ParseUInt(&(tok2[1]), &numTimesToFail), exit); + break; + case 's': + nlEXPECT(ParseUInt(&(tok2[1]), &numTimesToSkip), exit); + break; + case 'p': + gotPercentage = true; + nlEXPECT(ParseUInt(&(tok2[1]), &percentage), exit); + nlEXPECT(percentage <= 100, exit); + break; + case 'r': + gotReboot = true; + break; + default: + goto exit; + break; + } + } + + if (gotArguments) + { + err = mgr->StoreArgsAtFault(faultId, numArgs, args); + nlEXPECT_SUCCESS(err, exit); + } + + if (gotPercentage) + { + err = mgr->FailRandomlyAtFault(faultId, static_cast(percentage)); + nlEXPECT_SUCCESS(err, exit); + } + else + { + err = mgr->FailAtFault(faultId, numTimesToSkip, numTimesToFail); + nlEXPECT_SUCCESS(err, exit); + } + if (gotReboot) + { + err = mgr->RebootAtFault(faultId); + nlEXPECT_SUCCESS(err, exit); + } + } + + retval = true; + +exit: + return retval; +} + +/** + * Internal function to kill the process if a + * fault is supposed to reboot the process but the application + * has not installed a callback + */ +static void Die() +{ +#if defined(__GNUC__) && (__GNUC__ >= 12) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + while (true) + *((volatile long *) 1) = 0; +#if defined(__GNUC__) && (__GNUC__ >= 12) +#pragma GCC diagnostic pop +#endif +} + +} // namespace FaultInjection + +} // namespace nl