text: /*
   * Licensed to Elasticsearch B.V. under one or more contributor
   * license agreements. See the NOTICE file distributed with
   * this work for additional information regarding copyright
   * ownership. Elasticsearch B.V. licenses this file to you 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.
   */
  
  #include "ConfigManager.h"
  #ifdef ELASTIC_APM_MOCK_STDLIB
  #   include "mock_stdlib.h"
  #else
  #   include <stdlib.h>
  #endif
  #ifndef ELASTIC_APM_MOCK_PHP_DEPS
  #   include <zend_ini.h>
  #endif
  #include "elastic_apm_assert.h"
  #include "log.h"
  #include "util.h"
  #include "TextOutputStream.h"
  #include "elastic_apm_alloc.h"
  #include "time_util.h"
  
  #define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_CONFIG
  
  enum ParsedOptionValueType
  {
      parsedOptionValueType_undefined = 0,
  
      parsedOptionValueType_bool,
      parsedOptionValueType_string,
      parsedOptionValueType_int,
      parsedOptionValueType_duration,
  
      end_parsedOptionValueType
  };
  typedef enum ParsedOptionValueType ParsedOptionValueType;
  
  #define ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE_TYPE( valueType ) \
      ELASTIC_APM_ASSERT_IN_END_EXCLUDED_RANGE_UINT64( parsedOptionValueType_undefined + 1, valueType, end_parsedOptionValueType )
  /**/
  
  struct ParsedOptionValue
  {
      ParsedOptionValueType type;
      union
      {
          bool boolValue;
          String stringValue;
          int intValue;
          Duration durationValue;
      } u;
  };
  typedef struct ParsedOptionValue ParsedOptionValue;
  
  #define ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedOptionValue ) \
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE_TYPE( (parsedOptionValue).type )
  
  struct EnumOptionAdditionalMetadata
  {
      String* names;
      size_t enumElementsCount;
      bool isUniquePrefixEnough;
  };
  typedef struct EnumOptionAdditionalMetadata EnumOptionAdditionalMetadata;
  
  struct DurationOptionAdditionalMetadata
  {
      DurationUnits defaultUnits;
  };
  typedef struct DurationOptionAdditionalMetadata DurationOptionAdditionalMetadata;
  
  union OptionAdditionalMetadata
  {
      EnumOptionAdditionalMetadata enumData;
      DurationOptionAdditionalMetadata durationData;
  };
  typedef union OptionAdditionalMetadata OptionAdditionalMetadata;
  
  struct OptionMetadata;
  typedef struct OptionMetadata OptionMetadata;
  typedef String (* InterpretIniRawValueFunc )( String rawValue );
  typedef ResultCode (* ParseRawValueFunc )( const OptionMetadata* optMeta, String rawValue, /* out */ ParsedOptionValue* parsedValue );
  typedef String (* StreamParsedValueFunc )( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, TextOutputStream* txtOutStream );
  typedef void (* SetConfigSnapshotFieldFunc )( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, ConfigSnapshot* dst );
  typedef ParsedOptionValue (* GetConfigSnapshotFieldFunc )( const OptionMetadata* optMeta, const ConfigSnapshot* src );
  typedef void (* ParsedOptionValueToZvalFunc )( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, zval* return_value );
  struct OptionMetadata
  {
      bool isSecret;
      String name;
      StringView iniName;
      ParsedOptionValue defaultValue;
      InterpretIniRawValueFunc interpretIniRawValue;
      ParseRawValueFunc parseRawValue;
      StreamParsedValueFunc streamParsedValue;
      SetConfigSnapshotFieldFunc setField;
      GetConfigSnapshotFieldFunc getField;
      ParsedOptionValueToZvalFunc parsedValueToZval;
      OptionAdditionalMetadata additionalData;
  };
  
  struct RawConfigSnapshot
  {
      String original[ numberOfOptions ];
      String interpreted[ numberOfOptions ];
  };
  typedef struct RawConfigSnapshot RawConfigSnapshot;
  
  struct RawConfigSnapshotSource;
  typedef struct RawConfigSnapshotSource RawConfigSnapshotSource;
  typedef ResultCode (* GetRawOptionValueFunc )(
          const ConfigManager* configManager,
          OptionId optId,
          String* originalRawValue,
          String* interpretedRawValue );
  struct RawConfigSnapshotSource
  {
      String description;
      GetRawOptionValueFunc getOptionValue;
  };
  
  struct CombinedRawConfigSnapshot
  {
      String original[ numberOfOptions ];
      String interpreted[ numberOfOptions ];
      String sourceDescriptions[ numberOfOptions ];
  };
  typedef struct CombinedRawConfigSnapshot CombinedRawConfigSnapshot;
  
  struct ConfigRawData
  {
      RawConfigSnapshot fromSources[ numberOfRawConfigSources ];
      CombinedRawConfigSnapshot combined;
  };
  typedef struct ConfigRawData ConfigRawData;
  
  struct ConfigMetadata
  {
      OptionMetadata optionsMeta[ numberOfOptions ];
      String envVarNames[ numberOfOptions ];
      RawConfigSnapshotSource rawCfgSources[ numberOfRawConfigSources ];
  };
  typedef struct ConfigMetadata ConfigMetadata;
  
  struct ConfigManagerCurrentState
  {
      ConfigRawData* rawData;
      ConfigSnapshot snapshot;
  };
  typedef struct ConfigManagerCurrentState ConfigManagerCurrentState;
  
  struct ConfigManager
  {
      ConfigMetadata meta;
      ConfigManagerCurrentState current;
  };
  
  #define ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId ) \
      ELASTIC_APM_ASSERT_IN_END_EXCLUDED_RANGE_UINT64( 0, optId, numberOfOptions )
  
  String interpretStringIniRawValue( String rawValue )
  {
      return rawValue;
  }
  
  String interpretBoolIniRawValue( String rawValue )
  {
      // When PHP engine parses php.ini it automatically converts "true", "on" and "yes" to "1" (meaning true)
      // and "false", "off", "no" and "none" to "" (empty string, meaning false)
      // https://www.php.net/manual/en/function.parse-ini-file.php
      if ( rawValue != NULL && isEmtpyString( rawValue ) ) return "false";
  
      return rawValue;
  }
  
  String interpretEmptyIniRawValueAsOff( String rawValue )
  {
      // When PHP engine parses php.ini it automatically converts "true", "on" and "yes" to "1" (meaning true)
      // and "false", "off", "no" and "none" to "" (empty string, meaning false)
      // https://www.php.net/manual/en/function.parse-ini-file.php
      if ( rawValue != NULL && isEmtpyString( rawValue ) ) return "off";
  
      return rawValue;
  }
  
  static ResultCode parseStringValue( const OptionMetadata* optMeta, String rawValue, /* out */ ParsedOptionValue* parsedValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_string );
      ELASTIC_APM_ASSERT_VALID_PTR( rawValue );
      ELASTIC_APM_ASSERT_VALID_PTR( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue->type, parsedOptionValueType_undefined );
      ELASTIC_APM_ASSERT_PTR_IS_NULL( parsedValue->u.stringValue );
  
      parsedValue->u.stringValue = rawValue;
      parsedValue->type = optMeta->defaultValue.type;
      return resultSuccess;
  }
  
  static String streamParsedString( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, TextOutputStream* txtOutStream )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_string );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
  
      return streamUserString( parsedValue.u.stringValue, txtOutStream );
  }
  
  static void parsedStringValueToZval( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, zval* return_value )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_string );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
      ELASTIC_APM_ASSERT_VALID_PTR( return_value );
  
      if ( parsedValue.u.stringValue == NULL ) RETURN_NULL();
      RETURN_STRING( parsedValue.u.stringValue );
  }
  
  static ResultCode parseBoolValue( const OptionMetadata* optMeta, String rawValue, /* out */ ParsedOptionValue* parsedValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_bool );
      ELASTIC_APM_ASSERT_VALID_PTR( rawValue );
      ELASTIC_APM_ASSERT_VALID_PTR( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue->type, parsedOptionValueType_undefined );
  
      enum { valuesCount = 4 };
      String trueValues[ valuesCount ] = { "true", "1", "yes", "on" };
      String falseValues[ valuesCount ] = { "false", "0", "no", "off" };
      ELASTIC_APM_FOR_EACH_INDEX( i, valuesCount )
      {
          if ( areStringsEqualIgnoringCase( rawValue, trueValues[ i ] ) )
          {
              parsedValue->u.boolValue = true;
              parsedValue->type = parsedOptionValueType_bool;
              return resultSuccess;
          }
          if ( areStringsEqualIgnoringCase( rawValue, falseValues[ i ] ) )
          {
              parsedValue->u.boolValue = false;
              parsedValue->type = parsedOptionValueType_bool;
              return resultSuccess;
          }
      }
  
      return resultFailure;
  }
  
  static String streamParsedBool( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, TextOutputStream* txtOutStream )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_bool );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
  
      return streamBool( parsedValue.u.boolValue, txtOutStream );
  }
  
  static void parsedBoolValueToZval( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, zval* return_value )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_bool );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
      ELASTIC_APM_ASSERT_VALID_PTR( return_value );
  
      RETURN_BOOL( parsedValue.u.boolValue );
  }
  
  static ResultCode parseDurationValue( const OptionMetadata* optMeta, String rawValue, /* out */ ParsedOptionValue* parsedValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_duration );
      ELASTIC_APM_ASSERT_VALID_PTR( rawValue );
      ELASTIC_APM_ASSERT_VALID_PTR( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue->type, parsedOptionValueType_undefined );
  
      ResultCode parseResultCode = parseDuration( stringToView( rawValue )
                                                  , optMeta->additionalData.durationData.defaultUnits
                                                  , /* out */ &parsedValue->u.durationValue );
      if ( parseResultCode == resultSuccess ) parsedValue->type = parsedOptionValueType_duration;
      return parseResultCode;
  }
  
  static String streamParsedDuration( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, TextOutputStream* txtOutStream )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_duration );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
  
      return streamDuration( parsedValue.u.durationValue, txtOutStream );
  }
  
  static void parsedDurationValueToZval( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, zval* return_value )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_duration );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
      ELASTIC_APM_ASSERT_VALID_PTR( return_value );
  
      RETURN_DOUBLE( durationToMilliseconds( parsedValue.u.durationValue ) );
  }
  
  static
  ResultCode parseEnumValue( const OptionMetadata* optMeta, String rawValue, /* out */ ParsedOptionValue* parsedValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_int );
      ELASTIC_APM_ASSERT_VALID_PTR( rawValue );
      ELASTIC_APM_ASSERT_VALID_PTR( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue->type, parsedOptionValueType_undefined );
  
      int foundMatch = -1;
      StringView rawValueStrView = stringToView( rawValue );
  
      ELASTIC_APM_FOR_EACH_INDEX( i, optMeta->additionalData.enumData.enumElementsCount )
      {
          StringView currentEnumName = stringToView( optMeta->additionalData.enumData.names[ i ] );
          if ( ! isStringViewPrefixIgnoringCase( currentEnumName, rawValueStrView ) ) continue;
  
          // If match is exact (i.e., not just prefix) then we return immediately
          if ( currentEnumName.length == rawValueStrView.length )
          {
              foundMatch = (int)i;
              break;
          }
  
          if ( optMeta->additionalData.enumData.isUniquePrefixEnough )
          {
              // If there's more than one enum name that raw value matches as a prefix
              // then it's ambiguous and we return failure
              if ( foundMatch != -1 )
              {
                  ELASTIC_APM_LOG_ERROR(
                          "Failed to parse enum configuration option - raw value matches more than one enum as a prefix."
                          " Option name: `%s'."
                          " Raw value: `%s'."
                          " At least the following enums match: `%s' and `%s'."
                          , optMeta->name
                          , rawValue
                          , optMeta->additionalData.enumData.names[ foundMatch ]
                          , optMeta->additionalData.enumData.names[ i ] );
                  return resultFailure;
              }
  
              foundMatch = (int)i;
          }
      }
  
      if ( foundMatch == -1 ) return resultFailure;
  
      parsedValue->u.intValue = foundMatch;
      parsedValue->type = parsedOptionValueType_int;
      return resultSuccess;
  }
  
  static void parsedEnumValueToZval( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, zval* return_value )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_int );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
      ELASTIC_APM_ASSERT_VALID_PTR( return_value );
  
      RETURN_LONG( (long)( parsedValue.u.intValue ) );
  }
  
  static String streamParsedLogLevel( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, TextOutputStream* txtOutStream )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optMeta );
      ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_int );
      ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue );
      ELASTIC_APM_ASSERT_EQ_UINT64( parsedValue.type, optMeta->defaultValue.type );
  
      return streamLogLevel( (LogLevel) parsedValue.u.intValue, txtOutStream );
  }
  
  static OptionMetadata buildStringOptionMetadata(
          bool isSecret
          , String name
          , StringView iniName
          , String defaultValue
          , SetConfigSnapshotFieldFunc setFieldFunc
          , GetConfigSnapshotFieldFunc getFieldFunc )
  {
      return (OptionMetadata)
      {
          .isSecret = isSecret,
          .name = name,
          .iniName = iniName,
          .defaultValue = { .type = parsedOptionValueType_string, .u.stringValue = defaultValue },
          .interpretIniRawValue = &interpretStringIniRawValue,
          .parseRawValue = &parseStringValue,
          .streamParsedValue = &streamParsedString,
          .setField = setFieldFunc,
          .getField = getFieldFunc,
          .parsedValueToZval = &parsedStringValueToZval
      };
  }
  
  static OptionMetadata buildBoolOptionMetadata(
          bool isSecret
          , String name
          , StringView iniName
          , bool defaultValue
          , SetConfigSnapshotFieldFunc setFieldFunc
          , GetConfigSnapshotFieldFunc getFieldFunc )
  {
      return (OptionMetadata)
      {
          .isSecret = isSecret,
          .name = name,
          .iniName = iniName,
          .defaultValue = { .type = parsedOptionValueType_bool, .u.boolValue = defaultValue },
          .interpretIniRawValue = &interpretBoolIniRawValue,
          .parseRawValue = &parseBoolValue,
          .streamParsedValue = &streamParsedBool,
          .setField = setFieldFunc,
          .getField = getFieldFunc,
          .parsedValueToZval = &parsedBoolValueToZval
      };
  }
  
  static OptionMetadata buildDurationOptionMetadata(
          bool isSecret
          , String name
          , StringView iniName
          , Duration defaultValue
          , SetConfigSnapshotFieldFunc setFieldFunc
          , GetConfigSnapshotFieldFunc getFieldFunc
          , DurationUnits defaultUnits )
  {
      return (OptionMetadata)
      {
          .isSecret = isSecret,
          .name = name,
          .iniName = iniName,
          .defaultValue = { .type = parsedOptionValueType_duration, .u.durationValue = defaultValue },
          .interpretIniRawValue = &interpretStringIniRawValue,
          .parseRawValue = &parseDurationValue,
          .streamParsedValue = &streamParsedDuration,
          .setField = setFieldFunc,
          .getField = getFieldFunc,
          .parsedValueToZval = &parsedDurationValueToZval,
          .additionalData = (OptionAdditionalMetadata){ .durationData = (DurationOptionAdditionalMetadata){ .defaultUnits = defaultUnits } }
      };
  }
  
  static OptionMetadata buildEnumOptionMetadata(
          bool isSecret
          , String name
          , StringView iniName
          , int defaultValue
          , InterpretIniRawValueFunc interpretIniRawValue
          , SetConfigSnapshotFieldFunc setFieldFunc
          , GetConfigSnapshotFieldFunc getFieldFunc
          , StreamParsedValueFunc streamParsedValue
          , EnumOptionAdditionalMetadata additionalMetadata )
  {
      return (OptionMetadata)
      {
          .isSecret = isSecret,
          .name = name,
          .iniName = iniName,
          .defaultValue = { .type = parsedOptionValueType_int, .u.intValue = defaultValue },
          .interpretIniRawValue = interpretIniRawValue,
          .parseRawValue = &parseEnumValue,
          .streamParsedValue = streamParsedValue,
          .setField = setFieldFunc,
          .getField = getFieldFunc,
          .parsedValueToZval = &parsedEnumValueToZval,
          .additionalData = (OptionAdditionalMetadata){ .enumData = additionalMetadata }
      };
  }
  
  static void initOptionMetadataForId( OptionMetadata* optsMeta
                                       , OptionId actualOptId
                                       , OptionId expectedOptId
                                       , OptionMetadata optionMetadata )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optsMeta );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( actualOptId );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( expectedOptId );
      ELASTIC_APM_ASSERT_EQ_UINT64( actualOptId, expectedOptId );
  
      ELASTIC_APM_FOR_EACH_INDEX( i, actualOptId )
          ELASTIC_APM_ASSERT( ! areStringsEqualIgnoringCase( optsMeta[ i ].name, optionMetadata.name )
          , "i: %u, optionMetadata.name: %s", (unsigned int)i, optionMetadata.name );
  
      optsMeta[ actualOptId ] = optionMetadata;
  }
  
  #define ELASTIC_APM_FREE_AND_RESET_FIELD_FUNC_NAME( fieldName ) freeAndReset_ConfigSnapshot_##fieldName##_field
  #define ELASTIC_APM_SET_FIELD_FUNC_NAME( fieldName ) set_ConfigSnapshot_##fieldName##_field
  #define ELASTIC_APM_GET_FIELD_FUNC_NAME( fieldName ) get_ConfigSnapshot_##fieldName##_field
  
  #define ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( unionFieldForType, fieldName ) \
      static void ELASTIC_APM_SET_FIELD_FUNC_NAME( fieldName ) ( const OptionMetadata* optMeta, ParsedOptionValue parsedValue, ConfigSnapshot* dst ) \
      { \
          ELASTIC_APM_ASSERT_VALID_PTR( optMeta ); \
          ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue ); \
          ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedValue.type ); \
          ELASTIC_APM_ASSERT_VALID_PTR( dst ); \
          \
          dst->fieldName = parsedValue.u.unionFieldForType; \
      } \
      \
      static ParsedOptionValue ELASTIC_APM_GET_FIELD_FUNC_NAME( fieldName ) ( const OptionMetadata* optMeta, const ConfigSnapshot* src ) \
      { \
          ELASTIC_APM_ASSERT_VALID_PTR( optMeta ); \
          ELASTIC_APM_ASSERT_VALID_PTR( src ); \
          \
          return (ParsedOptionValue){ .type = optMeta->defaultValue.type, .u.unionFieldForType = src->fieldName }; \
      }
  
  #define ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( EnumType, fieldName ) \
      static void ELASTIC_APM_SET_FIELD_FUNC_NAME( fieldName ) ( const OptionMetadata* optMeta, ParsedOptionValue parsedValue,  ConfigSnapshot* dst ) \
      { \
          ELASTIC_APM_ASSERT_VALID_PTR( optMeta ); \
          ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_int ); \
          ELASTIC_APM_ASSERT_VALID_PARSED_OPTION_VALUE( parsedValue ); \
          ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedValue.type ); \
          ELASTIC_APM_ASSERT_VALID_PTR( dst ); \
          \
          dst->fieldName = (EnumType)( parsedValue.u.intValue ); \
      } \
      \
      static ParsedOptionValue ELASTIC_APM_GET_FIELD_FUNC_NAME( fieldName ) ( const OptionMetadata* optMeta, const ConfigSnapshot* src ) \
      { \
          ELASTIC_APM_ASSERT_VALID_PTR( optMeta ); \
          ELASTIC_APM_ASSERT_EQ_UINT64( optMeta->defaultValue.type, parsedOptionValueType_int ); \
          ELASTIC_APM_ASSERT_VALID_PTR( src ); \
          \
          return (ParsedOptionValue){ .type = optMeta->defaultValue.type, .u.intValue = (int)( src->fieldName ) }; \
      }
  
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( boolValue, abortOnMemoryLeak )
  #   ifdef PHP_WIN32
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( boolValue, allowAbortDialog )
  #   endif
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, apiKey )
  #   if ( ELASTIC_APM_ASSERT_ENABLED_01 != 0 )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( AssertLevel, assertLevel )
  #   endif
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, bootstrapPhpPartFile )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( boolValue, enabled )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, environment )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( InternalChecksLevel, internalChecksLevel )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, logFile )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( LogLevel, logLevel )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( LogLevel, logLevelFile )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( LogLevel, logLevelStderr )
  #   ifndef PHP_WIN32
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( LogLevel, logLevelSyslog )
  #   endif
  #   ifdef PHP_WIN32
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( LogLevel, logLevelWinSysDebug )
  #   endif
  #   if ( ELASTIC_APM_MEMORY_TRACKING_ENABLED_01 != 0 )
  ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS( MemoryTrackingLevel, memoryTrackingLevel )
  #   endif
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, secretToken )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, serverTimeout )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, serverUrl )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, serviceName )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, serviceVersion )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, transactionMaxSpans )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, transactionSampleRate )
  ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( boolValue, verifyServerCert )
  
  #undef ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS
  #undef ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS
  
  #define ELASTIC_APM_INIT_METADATA_EX( buildFunc, fieldName, isSecret, optName, defaultValue, ... ) \
      initOptionMetadataForId \
      ( \
          optsMeta \
          , (OptionId)(i++) \
          , optionId_##fieldName \
          , buildFunc \
          ( \
              isSecret \
              , optName \
              , ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_CFG_CONVERT_OPT_NAME_TO_INI_NAME( optName ) ) \
              , defaultValue \
              , ELASTIC_APM_SET_FIELD_FUNC_NAME( fieldName ) \
              , ELASTIC_APM_GET_FIELD_FUNC_NAME( fieldName ) \
              , ##__VA_ARGS__ \
          ) \
      )
  
  #define ELASTIC_APM_INIT_METADATA( buildFunc, fieldName, optName, defaultValue ) \
      ELASTIC_APM_INIT_METADATA_EX( buildFunc, fieldName, /* isSecret */ false, optName, defaultValue )
  
  #define ELASTIC_APM_INIT_DURATION_METADATA( fieldName, optName, defaultValue, defaultUnits ) \
      ELASTIC_APM_INIT_METADATA_EX( buildDurationOptionMetadata, fieldName, /* isSecret */ false, optName, defaultValue, defaultUnits )
  
  #define ELASTIC_APM_INIT_SECRET_METADATA( buildFunc, fieldName, optName, defaultValue ) \
      ELASTIC_APM_INIT_METADATA_EX( buildFunc, fieldName, /* isSecret */ true, optName, defaultValue )
  
  #define ELASTIC_APM_ENUM_INIT_METADATA( fieldName, optName, defaultValue, interpretIniRawValue, enumNamesArray, isUniquePrefixEnoughArg ) \
      initOptionMetadataForId \
      ( \
          optsMeta \
          , (OptionId)(i++) \
          , optionId_##fieldName \
          , buildEnumOptionMetadata \
          ( \
              /* isSecret */ false \
              , optName \
              , ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_CFG_CONVERT_OPT_NAME_TO_INI_NAME( optName ) ) \
              , defaultValue \
              , interpretIniRawValue \
              , ELASTIC_APM_SET_FIELD_FUNC_NAME( fieldName ) \
              , ELASTIC_APM_GET_FIELD_FUNC_NAME( fieldName ) \
              , &streamParsedLogLevel \
              , (EnumOptionAdditionalMetadata) \
              { \
                  .names = (enumNamesArray), \
                  .enumElementsCount = ELASTIC_APM_STATIC_ARRAY_SIZE( (enumNamesArray) ), \
                  .isUniquePrefixEnough = (isUniquePrefixEnoughArg) \
              } \
          ) \
      )
  
  #define ELASTIC_APM_INIT_LOG_LEVEL_METADATA( fieldName, optName ) \
      ELASTIC_APM_ENUM_INIT_METADATA( fieldName, optName, logLevel_not_set, &interpretEmptyIniRawValueAsOff, logLevelNames, /* isUniquePrefixEnough: */ true )
  
  static void initOptionsMetadata( OptionMetadata* optsMeta )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optsMeta );
  
      size_t i = 0;
  
      //
      // The order of calls to ELASTIC_APM_INIT_METADATA below should be the same as in OptionId
      //
  
      ELASTIC_APM_INIT_METADATA(
              buildBoolOptionMetadata,
              abortOnMemoryLeak,
              ELASTIC_APM_CFG_OPT_NAME_ABORT_ON_MEMORY_LEAK,
              /* defaultValue: */ ELASTIC_APM_MEMORY_TRACKING_DEFAULT_ABORT_ON_MEMORY_LEAK );
  
      #ifdef PHP_WIN32
      ELASTIC_APM_INIT_METADATA(
              buildBoolOptionMetadata,
              allowAbortDialog,
              ELASTIC_APM_CFG_OPT_NAME_ALLOW_ABORT_DIALOG,
              /* defaultValue: */ false );
      #endif
  
      ELASTIC_APM_INIT_SECRET_METADATA(
              buildStringOptionMetadata,
              apiKey,
              ELASTIC_APM_CFG_OPT_NAME_API_KEY,
              /* defaultValue: */ NULL );
  
      #if ( ELASTIC_APM_ASSERT_ENABLED_01 != 0 )
      ELASTIC_APM_ENUM_INIT_METADATA(
              /* fieldName: */ assertLevel,
              /* optName: */ ELASTIC_APM_CFG_OPT_NAME_ASSERT_LEVEL,
              /* defaultValue: */ assertLevel_not_set,
              &interpretEmptyIniRawValueAsOff,
              assertLevelNames,
              /* isUniquePrefixEnough: */ true );
      #endif
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              bootstrapPhpPartFile,
              ELASTIC_APM_CFG_OPT_NAME_BOOTSTRAP_PHP_PART_FILE,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildBoolOptionMetadata,
              enabled,
              ELASTIC_APM_CFG_OPT_NAME_ENABLED,
              /* defaultValue: */ true );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              environment,
              ELASTIC_APM_CFG_OPT_NAME_ENVIRONMENT,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_ENUM_INIT_METADATA(
              /* fieldName: */ internalChecksLevel,
              /* optName: */ ELASTIC_APM_CFG_OPT_NAME_INTERNAL_CHECKS_LEVEL,
              /* defaultValue: */ internalChecksLevel_not_set,
              &interpretEmptyIniRawValueAsOff,
              internalChecksLevelNames,
              /* isUniquePrefixEnough: */ true );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              logFile,
              ELASTIC_APM_CFG_OPT_NAME_LOG_FILE,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_LOG_LEVEL_METADATA(
              logLevel,
              ELASTIC_APM_CFG_OPT_NAME_LOG_LEVEL );
      ELASTIC_APM_INIT_LOG_LEVEL_METADATA(
              logLevelFile,
              ELASTIC_APM_CFG_OPT_NAME_LOG_LEVEL_FILE );
      ELASTIC_APM_INIT_LOG_LEVEL_METADATA(
              logLevelStderr,
              ELASTIC_APM_CFG_OPT_NAME_LOG_LEVEL_STDERR );
      #ifndef PHP_WIN32
      ELASTIC_APM_INIT_LOG_LEVEL_METADATA(
              logLevelSyslog,
              ELASTIC_APM_CFG_OPT_NAME_LOG_LEVEL_SYSLOG );
      #endif
      #ifdef PHP_WIN32
      ELASTIC_APM_INIT_LOG_LEVEL_METADATA(
              logLevelWinSysDebug,
              ELASTIC_APM_CFG_OPT_NAME_LOG_LEVEL_WIN_SYS_DEBUG );
      #endif
  
      #if ( ELASTIC_APM_MEMORY_TRACKING_ENABLED_01 != 0 )
      ELASTIC_APM_ENUM_INIT_METADATA(
              /* fieldName: */ memoryTrackingLevel,
              /* optName: */ ELASTIC_APM_CFG_OPT_NAME_MEMORY_TRACKING_LEVEL,
              /* defaultValue: */ memoryTrackingLevel_not_set,
              &interpretEmptyIniRawValueAsOff,
              memoryTrackingLevelNames,
              /* isUniquePrefixEnough: */ true );
      #endif
  
      ELASTIC_APM_INIT_SECRET_METADATA(
              buildStringOptionMetadata,
              secretToken,
              ELASTIC_APM_CFG_OPT_NAME_SECRET_TOKEN,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              serverTimeout,
              ELASTIC_APM_CFG_OPT_NAME_SERVER_TIMEOUT,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              serverUrl,
              ELASTIC_APM_CFG_OPT_NAME_SERVER_URL,
              /* defaultValue: */ "http://localhost:8200" );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              serviceName,
              ELASTIC_APM_CFG_OPT_NAME_SERVICE_NAME,
              /* defaultValue: */ "Unknown PHP service" );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              serviceVersion,
              ELASTIC_APM_CFG_OPT_NAME_SERVICE_VERSION,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              transactionMaxSpans,
              ELASTIC_APM_CFG_OPT_NAME_TRANSACTION_MAX_SPANS,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildStringOptionMetadata,
              transactionSampleRate,
              ELASTIC_APM_CFG_OPT_NAME_TRANSACTION_SAMPLE_RATE,
              /* defaultValue: */ NULL );
  
      ELASTIC_APM_INIT_METADATA(
              buildBoolOptionMetadata,
              verifyServerCert,
              ELASTIC_APM_CFG_OPT_NAME_VERIFY_SERVER_CERT,
              /* defaultValue: */ true );
  
      ELASTIC_APM_ASSERT_EQ_UINT64( i, numberOfOptions );
  }
  
  #undef ELASTIC_APM_SET_FIELD_FUNC_NAME
  #undef ELASTIC_APM_GET_FIELD_FUNC_NAME
  #undef ELASTIC_APM_FREE_AND_RESET_FIELD_FUNC_NAME
  
  #undef ELASTIC_APM_INIT_METADATA
  #undef ELASTIC_APM_INIT_LOG_LEVEL_METADATA
  
  static
  void parseCombinedRawConfigSnapshot(
          const OptionMetadata* optsMeta,
          const CombinedRawConfigSnapshot* combinedRawCfgSnapshot,
          ConfigSnapshot* cfgSnapshot )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optsMeta );
      ELASTIC_APM_ASSERT_VALID_PTR( combinedRawCfgSnapshot );
      ELASTIC_APM_ASSERT_VALID_PTR( cfgSnapshot );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
      {
          char txtOutStreamBuf[ ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE ];
          TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
          const OptionMetadata* const optMeta = &( optsMeta[ optId ] );
          const String originalRawValue = combinedRawCfgSnapshot->original[ optId ];
          const String interpretedRawValue = combinedRawCfgSnapshot->interpreted[ optId ];
          const String sourceDescription = combinedRawCfgSnapshot->sourceDescriptions[ optId ];
          ParsedOptionValue parsedOptValue;
          ELASTIC_APM_ZERO_STRUCT( &parsedOptValue );
  
          if ( interpretedRawValue == NULL )
          {
              parsedOptValue = optMeta->defaultValue;
              ELASTIC_APM_LOG_DEBUG(
                      "Configuration option `%s' is not set - using default value (%s)",
                      optMeta->name,
                      optMeta->streamParsedValue( optMeta, parsedOptValue, &txtOutStream ) );
          }
          else if ( optMeta->parseRawValue( optMeta, interpretedRawValue, &parsedOptValue ) == resultSuccess )
          {
              ELASTIC_APM_LOG_DEBUG(
                      "Successfully parsed configuration option `%s' - "
                      "parsed value: %s (raw value: `%s', interpreted as: `%s', source: %s)",
                      optMeta->name,
                      optMeta->streamParsedValue( optMeta, parsedOptValue, &txtOutStream ),
                      originalRawValue,
                      interpretedRawValue,
                      sourceDescription );
          }
          else
          {
              parsedOptValue = optMeta->defaultValue;
              ELASTIC_APM_LOG_ERROR(
                      "Failed to parse configuration option `%s' - "
                      "using default value (%s). Failed to parse raw value: `%s', interpreted as: `%s', source: %s.",
                      optMeta->name,
                      optMeta->streamParsedValue( optMeta, parsedOptValue, &txtOutStream ),
                      originalRawValue,
                      interpretedRawValue,
                      sourceDescription );
          }
  
          optMeta->setField( optMeta, parsedOptValue, cfgSnapshot );
      }
  }
  
  static ResultCode constructEnvVarNameForOption( String optName, String* envVarName )
  {
      ELASTIC_APM_ASSERT_VALID_STRING( optName );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( envVarName );
  
      ResultCode resultCode;
      StringView envVarNamePrefix = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "ELASTIC_APM_" );
      MutableString envVarNameBuffer = NULL;
      const size_t envVarNameBufferSize = envVarNamePrefix.length + strlen( optName ) + 1;
  
      ELASTIC_APM_PEMALLOC_STRING_IF_FAILED_GOTO( envVarNameBufferSize, envVarNameBuffer );
      strcpy( envVarNameBuffer, envVarNamePrefix.begin );
      copyStringAsUpperCase( optName, /* out */ envVarNameBuffer + envVarNamePrefix.length );
  
      resultCode = resultSuccess;
      *envVarName = envVarNameBuffer;
  
      finally:
      return resultCode;
  
      failure:
      ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( envVarNameBufferSize, envVarNameBuffer );
      goto finally;
  }
  
  static void destructEnvVarNames( /* in,out */ String envVarNames[] )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( envVarNames );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
          ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( strlen( envVarNames[ optId ] ) + 1, envVarNames[ optId ] );
  }
  
  static ResultCode constructEnvVarNames( OptionMetadata* optsMeta, /* out */ String envVarNames[] )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( optsMeta );
      ELASTIC_APM_ASSERT_VALID_PTR( envVarNames );
  
      ResultCode resultCode;
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
          ELASTIC_APM_CALL_IF_FAILED_GOTO( constructEnvVarNameForOption( optsMeta[ optId ].name, &envVarNames[ optId ] ) );
  
      resultCode = resultSuccess;
  
      finally:
      return resultCode;
  
      failure:
      destructEnvVarNames( envVarNames );
      goto finally;
  }
  
  #ifdef ELASTIC_APM_GETENV_FUNC
  // Declare to avoid warnings
  char* ELASTIC_APM_MOCK_GETENV_FUNC( const char* name );
  #else
  #define ELASTIC_APM_GETENV_FUNC getenv
  #endif
  
  String readRawOptionValueFromEnvVars( const ConfigManager* cfgManager, OptionId optId )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
  
      return ELASTIC_APM_GETENV_FUNC( cfgManager->meta.envVarNames[ optId ] );
  }
  
  static
  ResultCode getRawOptionValueFromEnvVars(
          const ConfigManager* cfgManager,
          OptionId optId,
          String* originalRawValue,
          String* interpretedRawValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( originalRawValue );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( interpretedRawValue );
  
      ResultCode resultCode;
      String returnedRawValue;
      String rawValue = NULL;
  
      returnedRawValue = readRawOptionValueFromEnvVars( cfgManager, optId );
      if ( returnedRawValue != NULL )
          ELASTIC_APM_PEMALLOC_DUP_STRING_IF_FAILED_GOTO( returnedRawValue, rawValue );
  
      resultCode = resultSuccess;
      *originalRawValue = rawValue;
      *interpretedRawValue = *originalRawValue;
  
      finally:
      return resultCode;
  
      failure:
      ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( strlen( rawValue ) + 1, rawValue );
      goto finally;
  }
  
  String readRawOptionValueFromIni(
          const ConfigManager* cfgManager,
          OptionId optId,
          bool* exists )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
  
      const OptionMetadata* const optMeta = &( cfgManager->meta.optionsMeta[ optId ] );
      zend_bool existsZendBool = 0;
      String returnedRawValue = zend_ini_string_ex(
              (char*)( optMeta->iniName.begin ),
              optMeta->iniName.length,
              /* orig: */ 0,
              &existsZendBool );
      *exists = ( existsZendBool != 0 );
      return returnedRawValue;
  }
  
  static
  ResultCode getRawOptionValueFromIni(
          const ConfigManager* cfgManager,
          OptionId optId,
          String* originalRawValue,
          String* interpretedRawValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( originalRawValue );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( interpretedRawValue );
  
      ResultCode resultCode;
      bool exists = 0;
      String returnedRawValue = NULL;
      String rawValue = NULL;
      const OptionMetadata* const optMeta = &( cfgManager->meta.optionsMeta[ optId ] );
  
      returnedRawValue = readRawOptionValueFromIni( cfgManager,optId, &exists );
  
      if ( exists && ( returnedRawValue != NULL ) )
          ELASTIC_APM_PEMALLOC_DUP_STRING_IF_FAILED_GOTO( returnedRawValue, rawValue );
  
      resultCode = resultSuccess;
      *originalRawValue = rawValue;
      *interpretedRawValue = optMeta->interpretIniRawValue( rawValue );
  
      finally:
      return resultCode;
  
      failure:
      ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( strlen( rawValue ) + 1, rawValue );
      goto finally;
  }
  
  static void initRawConfigSources( RawConfigSnapshotSource rawCfgSources[ numberOfRawConfigSources ] )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( rawCfgSources );
  
      size_t i = 0;
  
      ELASTIC_APM_ASSERT_EQ_UINT64( i, rawConfigSourceId_iniFile );
      rawCfgSources[ i++ ] = (RawConfigSnapshotSource)
      {
          .description = "INI file",
          .getOptionValue = &getRawOptionValueFromIni
      };
  
      ELASTIC_APM_ASSERT_EQ_UINT64( i, rawConfigSourceId_envVars );
      rawCfgSources[ i++ ] = (RawConfigSnapshotSource)
      {
          .description = "Environment variables",
          .getOptionValue = &getRawOptionValueFromEnvVars
      };
  
      ELASTIC_APM_ASSERT_EQ_UINT64( i, numberOfRawConfigSources );
  }
  
  static
  void deleteConfigRawDataAndSetToNull( /* in,out */ ConfigRawData** pRawData )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( pRawData );
  
      ConfigRawData* rawData = *pRawData;
      if ( rawData == NULL ) return;
      ELASTIC_APM_ASSERT_VALID_PTR( rawData );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
          ELASTIC_APM_FOR_EACH_INDEX( rawSourceIndex, numberOfRawConfigSources )
          {
              const char** pOriginalRawValue = &( rawData->fromSources[ rawSourceIndex ].original[ optId ] );
              ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL(strlen( *pOriginalRawValue ) + 1, *pOriginalRawValue );
          }
  
      ELASTIC_APM_PEFREE_INSTANCE_AND_SET_TO_NULL( ConfigRawData, *pRawData );
  }
  
  static
  ResultCode fetchConfigRawDataFromAllSources( const ConfigManager* cfgManager, /* out */ ConfigRawData* newRawData )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_PTR( newRawData );
  
      ResultCode resultCode;
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
      {
          ELASTIC_APM_FOR_EACH_INDEX( rawCfgSourceIndex, numberOfRawConfigSources )
          {
              ELASTIC_APM_CALL_IF_FAILED_GOTO( cfgManager->meta.rawCfgSources[ rawCfgSourceIndex ].getOptionValue(
                      cfgManager,
                      optId,
                      &newRawData->fromSources[ rawCfgSourceIndex ].original[ optId ],
                      &newRawData->fromSources[ rawCfgSourceIndex ].interpreted[ optId ] ) );
          }
      }
  
      resultCode = resultSuccess;
  
      finally:
      return resultCode;
  
      failure:
      goto finally;
  }
  
  static
  void combineConfigRawData( const ConfigManager* cfgManager, /* in,out */ ConfigRawData* newRawData )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_PTR( newRawData );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
      {
          ELASTIC_APM_FOR_EACH_INDEX( rawCfgSourceIndex, numberOfRawConfigSources )
          {
              if ( newRawData->fromSources[ rawCfgSourceIndex ].interpreted[ optId ] == NULL ) continue;
  
              newRawData->combined.original[ optId ] = newRawData->fromSources[ rawCfgSourceIndex ].original[ optId ];
              newRawData->combined.interpreted[ optId ] = newRawData->fromSources[ rawCfgSourceIndex ].interpreted[ optId ];
              newRawData->combined.sourceDescriptions[ optId ] = cfgManager->meta.rawCfgSources[ rawCfgSourceIndex ].description;
              break;
          }
      }
  }
  
  static
  ResultCode fetchConfigRawData( const ConfigManager* cfgManager, /* out */ ConfigRawData** pNewRawData )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pNewRawData );
  
      ResultCode resultCode;
      ConfigRawData* newRawData = NULL;
  
      ELASTIC_APM_PEMALLOC_INSTANCE_IF_FAILED_GOTO( ConfigRawData, newRawData );
      ELASTIC_APM_ZERO_STRUCT( newRawData );
  
      ELASTIC_APM_CALL_IF_FAILED_GOTO( fetchConfigRawDataFromAllSources( cfgManager, /* out */ newRawData ) );
      combineConfigRawData( cfgManager, /* in,out */ newRawData );
  
      resultCode = resultSuccess;
      *pNewRawData = newRawData;
  
      finally:
      return resultCode;
  
      failure:
      deleteConfigRawDataAndSetToNull( &newRawData );
      goto finally;
  }
  
  static
  bool areEqualCombinedRawConfigSnapshots( const CombinedRawConfigSnapshot* snapshot1, const CombinedRawConfigSnapshot* snapshot2 )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( snapshot1 );
      ELASTIC_APM_ASSERT_VALID_PTR( snapshot2 );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
          if ( ( ! areEqualNullableStrings( snapshot1->original[ optId ], snapshot2->original[ optId ] ) ) ||
                  ( ! areEqualNullableStrings( snapshot1->interpreted[ optId ], snapshot2->interpreted[ optId ] ) ) ||
                  ( ! areEqualNullableStrings( snapshot1->sourceDescriptions[ optId ], snapshot2->sourceDescriptions[ optId ] ) ) )
              return false;
  
      return true;
  }
  
  static
  void logConfigChange( const ConfigManager* cfgManager, const ConfigRawData* newRawData )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_PTR( newRawData );
  
  }
  
  const ConfigSnapshot* getConfigManagerCurrentSnapshot( const ConfigManager* cfgManager )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      return &cfgManager->current.snapshot;
  }
  
  ResultCode ensureConfigManagerHasLatestConfig( ConfigManager* cfgManager, bool* didConfigChange )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_PTR( didConfigChange );
  
      ResultCode resultCode;
      ConfigRawData* newRawData = NULL;
      ConfigSnapshot newCfgSnapshot = { 0 };
  
      ELASTIC_APM_CALL_IF_FAILED_GOTO( fetchConfigRawData( cfgManager, &newRawData ) );
  
      if ( cfgManager->current.rawData != NULL &&
              areEqualCombinedRawConfigSnapshots( &cfgManager->current.rawData->combined, &newRawData->combined ) )
      {
          ELASTIC_APM_LOG_DEBUG( "Current configuration is already the latest" );
          resultCode = resultSuccess;
          *didConfigChange = false;
          goto finally;
      }
  
      parseCombinedRawConfigSnapshot( cfgManager->meta.optionsMeta, &newRawData->combined, &newCfgSnapshot );
      logConfigChange( cfgManager, newRawData );
      deleteConfigRawDataAndSetToNull( /* in,out */ &cfgManager->current.rawData );
      cfgManager->current.rawData = newRawData;
      cfgManager->current.snapshot = newCfgSnapshot;
      newRawData = NULL;
  
      resultCode = resultSuccess;
      *didConfigChange = true;
  
      finally:
      deleteConfigRawDataAndSetToNull( /* in,out */ &newRawData );
      return resultCode;
  
      failure:
      goto finally;
  }
  
  static
  void destructConfigManagerCurrentState( /* in,out */ ConfigManagerCurrentState* cfgManagerCurrent )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManagerCurrent );
  
      deleteConfigRawDataAndSetToNull( /* in,out */ &cfgManagerCurrent->rawData );
  
      ELASTIC_APM_ZERO_STRUCT( cfgManagerCurrent );
  }
  
  static
  void initConfigManagerCurrentState(
          const ConfigMetadata* cfgManagerMeta,
          /* out */ ConfigManagerCurrentState* cfgManagerCurrent )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManagerMeta );
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManagerCurrent );
  
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
      {
          const OptionMetadata* const optMeta = &( cfgManagerMeta->optionsMeta[ optId ] );
          optMeta->setField( optMeta, optMeta->defaultValue, &cfgManagerCurrent->snapshot );
      }
  }
  
  static
  void destructConfigManagerMetadata( ConfigMetadata* cfgManagerMeta )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManagerMeta );
  
      destructEnvVarNames( /* in,out */ cfgManagerMeta->envVarNames );
  
      ELASTIC_APM_ZERO_STRUCT( cfgManagerMeta );
  }
  
  static
  ResultCode constructConfigManagerMetadata( ConfigMetadata* cfgManagerMeta )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManagerMeta );
  
      ResultCode resultCode;
  
      initOptionsMetadata( cfgManagerMeta->optionsMeta );
      ELASTIC_APM_CALL_IF_FAILED_GOTO( constructEnvVarNames( cfgManagerMeta->optionsMeta, /* out */ cfgManagerMeta->envVarNames ) );
      initRawConfigSources( cfgManagerMeta->rawCfgSources );
  
      resultCode = resultSuccess;
  
      finally:
      return resultCode;
  
      failure:
      destructConfigManagerMetadata( cfgManagerMeta );
      goto finally;
  }
  
  ResultCode getConfigManagerOptionValueByName(
          const ConfigManager* cfgManager
          , String optionName
          , GetConfigManagerOptionValueByNameResult* result
  )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( result );
      ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( &result->txtOutStream );
  
      const OptionMetadata* optMeta = NULL;
      ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
      {
          if ( areStringsEqualIgnoringCase( cfgManager->meta.optionsMeta[ optId ].name, optionName ) )
          {
              optMeta = &( cfgManager->meta.optionsMeta[ optId ] );
              break;
          }
      }
      if ( optMeta == NULL ) return resultFailure;
  
      optMeta->parsedValueToZval( optMeta, optMeta->getField( optMeta, &cfgManager->current.snapshot ), &result->parsedValueAsZval );
      result->streamedParsedValue = optMeta->streamParsedValue(
              optMeta, optMeta->getField( optMeta, &( cfgManager->current.snapshot ) ), &result->txtOutStream );
      return resultSuccess;
  }
  
  void getConfigManagerOptionMetadata(
          const ConfigManager* cfgManager
          , OptionId optId
          , GetConfigManagerOptionMetadataResult* result
  )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_PTR( result );
  
      const OptionMetadata* const optMeta = &( cfgManager->meta.optionsMeta[ optId ] );
      result->isSecret = optMeta->isSecret;
      result->optName = optMeta->name;
      result->envVarName = cfgManager->meta.envVarNames[ optId ];
      result->iniName = optMeta->iniName;
  }
  
  void getConfigManagerOptionValueById(
          const ConfigManager* cfgManager
          , OptionId optId
          , GetConfigManagerOptionValueByIdResult* result
  )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
      ELASTIC_APM_ASSERT_VALID_PTR( result );
      ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( &result->txtOutStream );
  
      const OptionMetadata* const optMeta = &( cfgManager->meta.optionsMeta[ optId ] );
      const ParsedOptionValue parsedOpVal = optMeta->getField( optMeta, &( cfgManager->current.snapshot ) );
      result->streamedParsedValue =
          ( parsedOpVal.type == parsedOptionValueType_string && parsedOpVal.u.stringValue == NULL )
              ? NULL
              : optMeta->streamParsedValue( optMeta, parsedOpVal, &result->txtOutStream );
  
      if ( cfgManager->current.rawData == NULL )
      {
          result->rawValue = NULL;
          result->rawValueSourceDescription = NULL;
      }
      else
      {
          result->rawValue = cfgManager->current.rawData->combined.original[ optId ];
          result->rawValueSourceDescription = cfgManager->current.rawData->combined.sourceDescriptions[ optId ];
      }
  }
  
  void getConfigManagerRawData(
          const ConfigManager* cfgManager,
          OptionId optId,
          RawConfigSourceId rawCfgSourceId,
          /* out */ String* originalRawValue,
          /* out */ String* interpretedRawValue )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
      ELASTIC_APM_ASSERT_VALID_OPTION_ID( optId );
      ELASTIC_APM_ASSERT_LT_UINT64( rawCfgSourceId, numberOfRawConfigSources );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( originalRawValue );
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( interpretedRawValue );
  
      *originalRawValue = cfgManager->current.rawData->fromSources[ rawCfgSourceId ].original[ optId ];
      *interpretedRawValue = cfgManager->current.rawData->fromSources[ rawCfgSourceId ].interpreted[ optId ];
  }
  
  void deleteConfigManagerAndSetToNull( ConfigManager** pCfgManager )
  {
      ELASTIC_APM_ASSERT_VALID_PTR( pCfgManager );
  
      ConfigManager* const cfgManager = *pCfgManager;
      if ( cfgManager == NULL ) return;
      ELASTIC_APM_ASSERT_VALID_PTR( cfgManager );
  
      destructConfigManagerCurrentState( /* in,out */ &cfgManager->current );
      destructConfigManagerMetadata( /* in,out */ &cfgManager->meta );
  
      ELASTIC_APM_ZERO_STRUCT( cfgManager );
  
      ELASTIC_APM_PEFREE_INSTANCE_AND_SET_TO_NULL( ConfigManager, *pCfgManager );
  }
  
  ResultCode newConfigManager( ConfigManager** pNewCfgManager )
  {
      ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pNewCfgManager );
  
      ResultCode resultCode;
      ConfigManager* cfgManager = NULL;
  
      ELASTIC_APM_PEMALLOC_INSTANCE_IF_FAILED_GOTO( ConfigManager, cfgManager );
      ELASTIC_APM_ZERO_STRUCT( cfgManager );
  
      ELASTIC_APM_CALL_IF_FAILED_GOTO( constructConfigManagerMetadata( /* out */ &cfgManager->meta ) );
      initConfigManagerCurrentState( &cfgManager->meta, /* out */ &cfgManager->current );
  
      resultCode = resultSuccess;
      *pNewCfgManager = cfgManager;
  
      finally:
      return resultCode;
  
      failure:
      deleteConfigManagerAndSetToNull( /* in,out */ &cfgManager );
      goto finally;
  }
  