summaryrefslogtreecommitdiffstats
path: root/fluent-bit/src/flb_config_map.c
diff options
context:
space:
mode:
Diffstat (limited to 'fluent-bit/src/flb_config_map.c')
-rw-r--r--fluent-bit/src/flb_config_map.c817
1 files changed, 0 insertions, 817 deletions
diff --git a/fluent-bit/src/flb_config_map.c b/fluent-bit/src/flb_config_map.c
deleted file mode 100644
index 9a3dd7ac..00000000
--- a/fluent-bit/src/flb_config_map.c
+++ /dev/null
@@ -1,817 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-
-/* Fluent Bit
- * ==========
- * Copyright (C) 2015-2022 The Fluent Bit 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.
- */
-
-#include <fluent-bit/flb_info.h>
-#include <fluent-bit/flb_kv.h>
-#include <fluent-bit/flb_env.h>
-#include <fluent-bit/flb_mem.h>
-#include <fluent-bit/flb_log.h>
-#include <fluent-bit/flb_utils.h>
-#include <fluent-bit/flb_slist.h>
-#include <fluent-bit/flb_macros.h>
-#include <fluent-bit/flb_config_map.h>
-
-static int check_list_size(struct mk_list *list, int type)
-{
- int len;
-
- len = mk_list_size(list);
- if (type == FLB_CONFIG_MAP_SLIST_1 || type == FLB_CONFIG_MAP_CLIST_1) {
- if (len < 1) {
- return -1;
- }
- }
- else if (type == FLB_CONFIG_MAP_SLIST_2 || type == FLB_CONFIG_MAP_CLIST_2) {
- if (len < 2) {
- return -1;
- }
- }
- else if (type == FLB_CONFIG_MAP_SLIST_3 || type == FLB_CONFIG_MAP_CLIST_3) {
- if (len < 3) {
- return -1;
- }
- }
- else if (type == FLB_CONFIG_MAP_SLIST_4 || type == FLB_CONFIG_MAP_CLIST_4) {
- if (len < 4) {
- return -1;
- }
- }
-
- return 0;
-}
-
-/*
- * Given a string, split the content using it proper separator generating a linked
- * list of 'slist'
- */
-static struct mk_list *parse_string_map_to_list(struct flb_config_map *map, char *str)
-{
- int ret = -1;
- int type;
- int max_split = -1;
- struct mk_list *list;
-
- type = map->type;
-
- /* Allocate list head */
- list = flb_malloc(sizeof(struct mk_list));
- if (!list) {
- flb_errno();
- return NULL;
- }
- mk_list_init(list);
-
- /* Determinate the max split value based on it type */
- if (map->type > FLB_CONFIG_MAP_CLIST && map->type < FLB_CONFIG_MAP_SLIST) {
- type = FLB_CONFIG_MAP_CLIST;
- max_split = (map->type - FLB_CONFIG_MAP_CLIST);
- }
- else if (map->type > FLB_CONFIG_MAP_SLIST) {
- type = FLB_CONFIG_MAP_SLIST;
- max_split = (map->type - FLB_CONFIG_MAP_SLIST);
- }
-
- if (type == FLB_CONFIG_MAP_CLIST) {
- ret = flb_slist_split_string(list, str, ',', max_split);
- }
- else if (type == FLB_CONFIG_MAP_SLIST) {
- ret = flb_slist_split_tokens(list, str, max_split);
- }
-
- if (ret == -1) {
- flb_error("[config map] error reading list of options");
- flb_free(list);
- return NULL;
- }
-
- return list;
-}
-
-static int translate_default_value(struct flb_config_map *map, char *val)
-{
- int ret;
- struct flb_config_map_val *entry = NULL;
- struct mk_list *list = NULL;
-
- /* Prepare contexts if the map allows multiple entries */
- if (map->flags & FLB_CONFIG_MAP_MULT) {
- entry = flb_calloc(1, sizeof(struct flb_config_map_val));
- if (!entry) {
- flb_errno();
- /*
- * do not worry about 'list' allocation, it will be destroyed by the caller
- * when it catches this error
- */
- return -1;
- }
- }
- else {
- entry = &map->value;
- }
-
- /* Based on specific data types, populate 'value' */
- if (map->type == FLB_CONFIG_MAP_STR) {
- /* Duplicate string as a flb_sds_t */
- entry->val.str = flb_sds_create(val);
-
- /* Validate new memory allocation */
- if (!entry->val.str) {
- goto error;
- }
- }
- else if (map->type == FLB_CONFIG_MAP_STR_PREFIX) {
- /*
- * For prefixed string types we don't process them, just validate
- * that no default value has been set.
- */
- if (val) {
- flb_error("[config map] invalid default value for prefixed string '%s'",
- map->name);
- goto error;
- }
- }
- else if (map->type == FLB_CONFIG_MAP_BOOL) {
- ret = flb_utils_bool(val);
- if (ret == -1) {
- flb_error("[config map] invalid default value for boolean '%s=%s'",
- map->name, val);
- goto error;
- }
- entry->val.boolean = flb_utils_bool(val);
- }
- else if (map->type == FLB_CONFIG_MAP_INT) {
- entry->val.i_num = atoi(val);
- }
- else if (map->type == FLB_CONFIG_MAP_DOUBLE) {
- entry->val.d_num = atof(val);
- }
- else if (map->type == FLB_CONFIG_MAP_SIZE) {
- entry->val.s_num = flb_utils_size_to_bytes(val);
- }
- else if (map->type == FLB_CONFIG_MAP_TIME) {
- entry->val.i_num = flb_utils_time_to_seconds(val);
- }
- else if (map->type >= FLB_CONFIG_MAP_CLIST &&
- map->type <= FLB_CONFIG_MAP_SLIST_4) {
-
- list = parse_string_map_to_list(map, val);
- if (!list) {
- flb_error("[config map] cannot parse list of values '%s'", val);
- goto error;
- }
-
- entry->val.list = list;
- list = NULL;
- }
-
- if (map->flags & FLB_CONFIG_MAP_MULT) {
- mk_list_add(&entry->_head, map->value.mult);
- }
-
- return 0;
-
- error:
- if (map->flags & FLB_CONFIG_MAP_MULT) {
- flb_free(entry);
- }
- return -1;
-}
-
-static flb_sds_t helper_map_options(struct mk_list *map)
-{
- flb_sds_t buf;
- flb_sds_t tmp;
- struct mk_list *head;
- struct flb_config_map *m;
-
- buf = flb_sds_create_size(256);
- if (!buf) {
- flb_errno();
- return NULL;
- }
-
- tmp = flb_sds_printf(&buf, "The following properties are allowed: ");
- if (!tmp) {
- flb_errno();
- flb_sds_destroy(buf);
- return NULL;
- }
- buf = tmp;
-
- mk_list_foreach(head, map) {
- m = mk_list_entry(head, struct flb_config_map, _head);
- if (head->next != map) {
- tmp = flb_sds_printf(&buf, "%s, ", m->name);
- }
- else {
- if (mk_list_size(map) == 1) {
- tmp = flb_sds_printf(&buf, "%s.", m->name);
- }
- else {
- tmp = flb_sds_printf(&buf, "and %s.", m->name);
- }
- }
-
- if (!tmp) {
- flb_errno();
- flb_sds_destroy(buf);
- return NULL;
- }
- buf = tmp;
- }
-
- return buf;
-}
-
-/*
- * Given a static plugin configuration map, create a linked list representation. We use a
- * linked list using heap memory instead of the stack since a plugin can be loaded multiple
- * times.
- *
- * In addition, for default values, we process them and populate the 'value' field with
- * proper data types.
- */
-struct mk_list *flb_config_map_create(struct flb_config *config,
- struct flb_config_map *map)
-{
- int ret;
- flb_sds_t env;
- struct mk_list *tmp;
- struct mk_list *list;
- struct flb_config_map *new = NULL;
- struct flb_config_map *m;
-
- list = flb_malloc(sizeof(struct mk_list));
- if (!list) {
- flb_errno();
- return NULL;
- }
- mk_list_init(list);
-
- /*
- * Read every property defined in the config map and create a new dynamic list
- * with the same content.
- *
- * As an additional step, it populate the 'value' field using the given default
- * value if any. Note that default values are strings so they are processed
- * to fit into the proper data type of 'value'.
- */
- m = map;
- while (m && m->name) {
- /* Allocate map node */
- new = flb_calloc(1, sizeof(struct flb_config_map));
- if (!new) {
- flb_errno();
- flb_config_map_destroy(list);
- return NULL;
- }
-
- new->type = m->type;
- new->name = flb_sds_create(m->name);
- if (new->name == NULL) {
- flb_free(new);
- flb_config_map_destroy(list);
- return NULL;
- }
-
- /* Translate default value */
- if (m->def_value) {
- /*
- * Before to translate any value, make sure to disable the warning
- * about unused variables. This might happen if a default value is an
- * environment variable and the user is not using it (which is ok for
- * that specific use case).
- */
- flb_env_warn_unused(config->env, FLB_FALSE);
-
- /* Translate the value */
- env = flb_env_var_translate(config->env, m->def_value);
- if (env == NULL) {
- flb_errno();
- flb_sds_destroy(new->name);
- flb_free(new);
- flb_config_map_destroy(list);
- return NULL;
- }
- new->def_value = env;
- flb_env_warn_unused(config->env, FLB_TRUE);
- }
-
- new->flags = m->flags;
- new->set_property = m->set_property;
- new->offset = m->offset;
- new->value.mult = NULL;
- new->desc = m->desc;
- mk_list_add(&new->_head, list);
-
- if (new->set_property == FLB_FALSE) {
- m++;
- continue;
- }
-
- /* If this is a multiple type of entries, initialize the main list */
- if (new->flags & FLB_CONFIG_MAP_MULT) {
- tmp = flb_malloc(sizeof(struct mk_list));
- if (!tmp) {
- flb_errno();
- flb_config_map_destroy(list);
- return NULL;
- }
- mk_list_init(tmp);
- new->value.mult = tmp;
- }
-
- /*
- * If there is no default value or the entry will not be set, just
- * continue with the next map entry
- */
- if (!m->def_value) {
- m++;
- continue;
- }
-
- /* Assign value based on data type and multiple mode if set */
- ret = translate_default_value(new, new->def_value);
- if (ret == -1) {
- flb_config_map_destroy(list);
- return NULL;
- }
- m++;
- }
-
- return list;
-}
-
-static void destroy_map_val(int type, struct flb_config_map_val *value)
-{
- if (type == FLB_CONFIG_MAP_STR && value->val.str) {
- flb_sds_destroy(value->val.str);
- }
- else if ((type >= FLB_CONFIG_MAP_CLIST &&
- type <= FLB_CONFIG_MAP_SLIST_4) &&
- value->val.list) {
- flb_slist_destroy(value->val.list);
- flb_free(value->val.list);
- }
-}
-
-/* Destroy a config map context */
-void flb_config_map_destroy(struct mk_list *list)
-{
- struct mk_list *tmp;
- struct mk_list *head;
- struct mk_list *v_head;
- struct mk_list *v_tmp;
- struct flb_config_map *map;
- struct flb_config_map_val *entry;
-
- mk_list_foreach_safe(head, tmp, list) {
- map = mk_list_entry(head, struct flb_config_map, _head);
- mk_list_del(&map->_head);
-
- if (map->flags & FLB_CONFIG_MAP_MULT && map->value.mult) {
- mk_list_foreach_safe(v_head, v_tmp, map->value.mult) {
- entry = mk_list_entry(v_head, struct flb_config_map_val, _head);
- mk_list_del(&entry->_head);
- destroy_map_val(map->type, entry);
- flb_free(entry);
- }
- flb_free(map->value.mult);
- }
- else {
- destroy_map_val(map->type, &map->value);
- }
- if (map->def_value) {
- flb_sds_destroy(map->def_value);
- }
- flb_sds_destroy(map->name);
- flb_free(map);
- }
- flb_free(list);
-}
-
-/* Count the number of times a property key exists */
-int property_count(char *key, int len, struct mk_list *properties)
-{
- int count = 0;
- struct mk_list *head;
- struct flb_kv *kv;
-
- mk_list_foreach(head, properties) {
- kv = mk_list_entry(head, struct flb_kv, _head);
- if (flb_sds_len(kv->key) != len) {
- continue;
- }
-
- if (strncmp(kv->key, key, len) == 0) {
- count++;
- }
- }
- return count;
-}
-
-/*
- * If the property starts with '_debug.', it's an internal property for
- * some component of Fluent Bit, not the plugin it self.
- */
-static int is_internal_debug_property(char *prop_name)
-{
-#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
- if (strncmp(prop_name, "_debug.http.", 12) == 0) {
- return FLB_TRUE;
- }
-#endif
-
- return FLB_FALSE;
-}
-
-
-/* Validate that the incoming properties set by the caller are allowed by the plugin */
-int flb_config_map_properties_check(char *context_name,
- struct mk_list *in_properties,
- struct mk_list *map)
-{
- int len;
- int found;
- int count = 0;
- int ret;
- flb_sds_t helper;
- struct flb_kv *kv;
- struct mk_list *head;
- struct mk_list *m_head;
- struct flb_config_map *m;
-
- /* Iterate all incoming property list */
- mk_list_foreach(head, in_properties) {
- kv = mk_list_entry(head, struct flb_kv, _head);
- found = FLB_FALSE;
-
-
- ret = is_internal_debug_property(kv->key);
- if (ret == FLB_TRUE) {
- /* Skip the config map */
- continue;
- }
-
- if (strcasecmp(kv->key, "active") == 0) {
- /* Accept 'active' property ... */
- continue;
- }
-
- /* Lookup the key into the provided map */
- mk_list_foreach(m_head, map) {
- m = mk_list_entry(m_head, struct flb_config_map, _head);
-
- len = flb_sds_len(m->name);
- if (m->type != FLB_CONFIG_MAP_STR_PREFIX) {
- if (len != flb_sds_len(kv->key)) {
- continue;
- }
- }
-
- if (strncasecmp(kv->key, m->name, len) == 0) {
- if (m->type == FLB_CONFIG_MAP_STR_PREFIX) {
- if (flb_sds_len(kv->key) <= len) {
- flb_error("[config] incomplete prefixed key '%s'", kv->key);
- found = FLB_FALSE;
- break;
- }
- }
- else if(m->type == FLB_CONFIG_MAP_DEPRECATED) {
- flb_warn("[config] %s: '%s' is deprecated",
- context_name, kv->key);
- }
- found = FLB_TRUE;
- break;
- }
- }
-
- if (found == FLB_FALSE) {
- helper = helper_map_options(map);
- if (!helper) {
- flb_error("[config] %s: unknown configuration property '%s'",
- context_name, kv->key);
- }
- else {
- flb_error("[config] %s: unknown configuration property '%s'. %s",
- context_name, kv->key, helper);
- flb_sds_destroy(helper);
- }
-
- return -1;
- }
-
- /* Validate number of times the property is set */
- count = property_count(kv->key, flb_sds_len(kv->key), in_properties);
- if ((m->flags & FLB_CONFIG_MAP_MULT) == 0) {
- if (count > 1) {
- flb_error("[config] %s: configuration property '%s' is set %i times",
- context_name, kv->key, count);
- return -1;
- }
- }
- }
-
- return 0;
-}
-
-/*
- * Returns FLB_TRUE or FLB_FALSE if a property aims to override the default value
- * assigned to the map key valled 'name'.
- */
-static int properties_override_default(struct mk_list *properties, char *name)
-{
- int len;
- struct mk_list *head;
- struct flb_kv *kv;
-
- len = strlen(name);
-
- mk_list_foreach(head, properties) {
- kv = mk_list_entry(head, struct flb_kv, _head);
- if (flb_sds_len(kv->key) != len) {
- continue;
- }
-
- if (strcasecmp(kv->key, name) == 0) {
- return FLB_TRUE;
- }
- }
-
- return FLB_FALSE;
-}
-
-/*
- * Return the number of expected values if the property type is from CLIST
- * or SLIST family.
- */
-int flb_config_map_expected_values(int type)
-{
- if (type > FLB_CONFIG_MAP_CLIST && type < FLB_CONFIG_MAP_SLIST) {
- return type - FLB_CONFIG_MAP_CLIST;
- }
- if (type > FLB_CONFIG_MAP_SLIST && type <= FLB_CONFIG_MAP_SLIST_4) {
- return type - FLB_CONFIG_MAP_SLIST;
- }
- return -1;
-}
-
-
-/*
- * Function used by plugins that needs to populate their context structure with the
- * configuration properties already mapped.
- */
-int flb_config_map_set(struct mk_list *properties, struct mk_list *map, void *context)
-{
- int ret;
- int len;
- char *base;
- char *m_bool;
- int *m_i_num;
- double *m_d_num;
- size_t *m_s_num;
- flb_sds_t *m_str;
- struct flb_kv *kv;
- struct mk_list *head;
- struct mk_list *m_head;
- struct mk_list **m_list;
- struct mk_list *list;
- struct flb_config_map *m = NULL;
- struct flb_config_map_val *entry = NULL;
-
- base = context;
-
- /* Link 'already processed default values' into the caller context */
- mk_list_foreach(m_head, map) {
- m = mk_list_entry(m_head, struct flb_config_map, _head);
-
- /*
- * If the map type allows multiple entries, the user context is a pointer
- * for a linked list. We just point their structure to our pre-processed
- * list of entries.
- */
- if (m->flags & FLB_CONFIG_MAP_MULT && m->set_property == FLB_TRUE) {
- m_list = (struct mk_list **) (base + m->offset);
- *m_list = m->value.mult;
- continue;
- }
-
- /*
- * If no default value exists or the map will not write to the user
- * context.. skip it.
- */
- if (!m->def_value || m->set_property == FLB_FALSE) {
- continue;
- }
-
- /*
- * If a property set by the user will override the default value, just
- * do not put the default value into the context since it will be replaced
- * later.
- */
- ret = properties_override_default(properties, m->name);
- if (ret == FLB_TRUE) {
- continue;
- }
-
- /* All the following steps are direct writes to the user context */
- if (m->type == FLB_CONFIG_MAP_STR) {
- m_str = (char **) (base + m->offset);
- *m_str = m->value.val.str;
- }
- else if (m->type == FLB_CONFIG_MAP_INT) {
- m_i_num = (int *) (base + m->offset);
- *m_i_num = m->value.val.i_num;
- }
- else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
- m_d_num = (double *) (base + m->offset);
- *m_d_num = m->value.val.d_num;
- }
- else if (m->type == FLB_CONFIG_MAP_SIZE) {
- m_s_num = (size_t *) (base + m->offset);
- *m_s_num = m->value.val.s_num;
- }
- else if (m->type == FLB_CONFIG_MAP_TIME) {
- m_i_num = (int *) (base + m->offset);
- *m_i_num = m->value.val.s_num;
- }
- else if (m->type == FLB_CONFIG_MAP_BOOL) {
- m_bool = (char *) (base + m->offset);
- *m_bool = m->value.val.boolean;
- }
- else if (m->type >= FLB_CONFIG_MAP_CLIST ||
- m->type <= FLB_CONFIG_MAP_SLIST_4) {
- m_list = (struct mk_list **) (base + m->offset);
- *m_list = m->value.val.list;
- }
- }
-
- /*
- * Iterate all properties coming from the configuration reader. If a property overrides
- * a default value already set in the previous step, just link to the new value.
- */
- mk_list_foreach(head, properties) {
- kv = mk_list_entry(head, struct flb_kv, _head);
- if (kv->val == NULL) {
- continue;
- }
-
- mk_list_foreach(m_head, map) {
- m = mk_list_entry(m_head, struct flb_config_map, _head);
- if (flb_sds_len(kv->key) != flb_sds_len(m->name)) {
- m = NULL;
- continue;
- }
-
- if (strncasecmp(kv->key, m->name, flb_sds_len(m->name)) == 0) {
- break;
- }
- m = NULL;
- continue;
-
- }
-
- if (!m || m->set_property == FLB_FALSE) {
- continue;
- }
-
- /* Check if the map allows multiple entries */
- if (m->flags & FLB_CONFIG_MAP_MULT) {
- /* Create node */
- entry = flb_calloc(1, sizeof(struct flb_config_map_val));
- if (!entry) {
- flb_errno();
- return -1;
- }
-
- /* Populate value */
- if (m->type == FLB_CONFIG_MAP_STR) {
- entry->val.str = flb_sds_create(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_INT) {
- entry->val.i_num = atoi(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
- entry->val.d_num = atof(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_SIZE) {
- entry->val.s_num = flb_utils_size_to_bytes(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_TIME) {
- entry->val.i_num = flb_utils_time_to_seconds(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_BOOL) {
- ret = flb_utils_bool(kv->val);
- if (ret == -1) {
- flb_free(entry);
- flb_error("[config map] invalid value for boolean property '%s=%s'",
- m->name, kv->val);
- return -1;
- }
- entry->val.boolean = ret;
- }
- else if (m->type >= FLB_CONFIG_MAP_CLIST ||
- m->type <= FLB_CONFIG_MAP_SLIST_4) {
-
- list = parse_string_map_to_list(m, kv->val);
- if (!list) {
- flb_error("[config map] cannot parse list of values '%s'", kv->val);
- flb_free(entry);
- return -1;
- }
- entry->val.list = list;
-
- /* Validate the number of entries are the minimum expected */
- len = mk_list_size(list);
- ret = check_list_size(list, m->type);
- if (ret == -1) {
- flb_error("[config map] property '%s' expects %i values "
- "(only %i were found)",
- kv->key,
- flb_config_map_expected_values(m->type), len);
- /*
- * Register the entry anyways, so on exit the resources will
- * be released
- */
- mk_list_add(&entry->_head, m->value.mult);
- return -1;
- }
- }
-
- /* Add entry to the map 'mult' list tail */
- mk_list_add(&entry->_head, m->value.mult);
-
- /* Override user context */
- m_list = (struct mk_list **) (base + m->offset);
- *m_list = m->value.mult;
- }
- else if (map != NULL) {
- /* Direct write to user context */
- if (m->type == FLB_CONFIG_MAP_STR) {
- m_str = (char **) (base + m->offset);
- *m_str = kv->val;
- }
- else if (m->type == FLB_CONFIG_MAP_INT) {
- m_i_num = (int *) (base + m->offset);
- *m_i_num = atoi(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
- m_d_num = (double *) (base + m->offset);
- *m_d_num = atof(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_BOOL) {
- m_bool = (char *) (base + m->offset);
- ret = flb_utils_bool(kv->val);
- if (ret == -1) {
- flb_error("[config map] invalid value for boolean property '%s=%s'",
- m->name, kv->val);
- return -1;
- }
- *m_bool = ret;
- }
- else if (m->type == FLB_CONFIG_MAP_SIZE) {
- m_s_num = (size_t *) (base + m->offset);
- *m_s_num = flb_utils_size_to_bytes(kv->val);
- }
- else if (m->type == FLB_CONFIG_MAP_TIME) {
- m_i_num = (int *) (base + m->offset);
- *m_i_num = flb_utils_time_to_seconds(kv->val);
- }
- else if (m->type >= FLB_CONFIG_MAP_CLIST ||
- m->type <= FLB_CONFIG_MAP_SLIST_4) {
- list = parse_string_map_to_list(m, kv->val);
- if (!list) {
- flb_error("[config map] cannot parse list of values '%s'", kv->val);
- flb_free(entry);
- return -1;
- }
-
- if (m->value.val.list) {
- destroy_map_val(m->type, &m->value);
- }
-
- m->value.val.list = list;
- m_list = (struct mk_list **) (base + m->offset);
- *m_list = m->value.val.list;
- }
- }
- }
-
- return 0;
-}