diff options
author | Karol Lewandowski <k.lewandowsk@samsung.com> | 2024-01-23 12:58:00 +0100 |
---|---|---|
committer | Karol Lewandowski <k.lewandowsk@samsung.com> | 2024-01-23 12:58:00 +0100 |
commit | cbab226a74fbaaa43220dee80e8435555c6506ce (patch) | |
tree | 1bbd14ec625ea85d0bcc32232d51c1f71e2604d2 /lib/vdo/vdo.c | |
parent | 44a3c2255bc480c82f34db156553a595606d8a0b (diff) | |
download | device-mapper-sandbox/klewandowski/upstream_2.03.22.tar.gz device-mapper-sandbox/klewandowski/upstream_2.03.22.tar.bz2 device-mapper-sandbox/klewandowski/upstream_2.03.22.zip |
Imported Upstream version 2.03.22upstream/libdevmapper-1.02.196upstream/2.03.22upstreamsandbox/klewandowski/upstream_2.03.22
Diffstat (limited to 'lib/vdo/vdo.c')
-rw-r--r-- | lib/vdo/vdo.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/lib/vdo/vdo.c b/lib/vdo/vdo.c new file mode 100644 index 0000000..6d3b674 --- /dev/null +++ b/lib/vdo/vdo.c @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2018-2022 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lib/misc/lib.h" +#include "lib/activate/activate.h" +#include "lib/activate/targets.h" +#include "lib/commands/toolcontext.h" +#include "lib/datastruct/str_list.h" +#include "lib/display/display.h" +#include "lib/format_text/text_export.h" +#include "lib/log/lvm-logging.h" +#include "lib/metadata/metadata.h" +#include "lib/metadata/lv_alloc.h" +#include "lib/metadata/segtype.h" +#include "lib/mm/memlock.h" +#include "base/memory/zalloc.h" + +static const char _vdo_module[] = MODULE_NAME_VDO; +static unsigned _feature_mask; + +static int _bad_field(const char *field) +{ + log_error("Couldn't read '%s' for VDO segment.", field); + return 0; +} + +static int _import_bool(const struct dm_config_node *n, + const char *name, bool *b) +{ + uint32_t t; + + if (dm_config_has_node(n, name)) { + if (!dm_config_get_uint32(n, name, &t)) + return _bad_field(name); + + if (t) { + *b = true; + return 1; + } + } + + *b = false; + + return 1; +} + +static void _print_yes_no(const char *name, bool value) +{ + log_print(" %s\t%s", name, value ? "yes" : "no"); +} + +/* + * VDO linear mapping + */ +static const char *_vdo_name(const struct lv_segment *seg) +{ + return SEG_TYPE_NAME_VDO; +} + +static void _vdo_display(const struct lv_segment *seg) +{ + display_stripe(seg, 0, " "); +} + +static int _vdo_text_import(struct lv_segment *seg, + const struct dm_config_node *n, + struct dm_hash_table *pv_hash __attribute__((unused))) +{ + struct logical_volume *vdo_pool_lv; + const char *str; + uint32_t vdo_offset; + + if (!dm_config_has_node(n, "vdo_pool") || + !(str = dm_config_find_str(n, "vdo_pool", NULL))) + return _bad_field("vdo_pool"); + if (!(vdo_pool_lv = find_lv(seg->lv->vg, str))) { + log_error("Unknown VDO pool logical volume %s.", str); + return 0; + } + + if (!dm_config_get_uint32(n, "vdo_offset", &vdo_offset)) + return _bad_field("vdo_offset"); + + if (!set_lv_segment_area_lv(seg, 0, vdo_pool_lv, vdo_offset, LV_VDO_POOL)) + return_0; + + seg->lv->status |= LV_VDO; + + return 1; +} + +static int _vdo_text_export(const struct lv_segment *seg, struct formatter *f) +{ + + if (!seg_is_vdo(seg)) { + log_error(INTERNAL_ERROR "Passed segment is not VDO type."); + return 0; + } + + outf(f, "vdo_pool = \"%s\"", seg_lv(seg, 0)->name); + outf(f, "vdo_offset = %u", seg_le(seg, 0)); + + return 1; +} + +#ifdef DEVMAPPER_SUPPORT +static int _vdo_target_status_compatible(const char *type) +{ + return (strcmp(type, TARGET_NAME_LINEAR) == 0); +} + +static int _vdo_add_target_line(struct dev_manager *dm, + struct dm_pool *mem __attribute__((unused)), + struct cmd_context *cmd, + void **target_state __attribute__((unused)), + struct lv_segment *seg, + const struct lv_activate_opts *laopts __attribute__((unused)), + struct dm_tree_node *node, uint64_t len, + uint32_t *pvmove_mirror_count __attribute__((unused))) +{ + char *vdo_pool_uuid; + + if (!(vdo_pool_uuid = build_dm_uuid(mem, seg_lv(seg, 0), lv_layer(seg_lv(seg, 0))))) + return_0; + + if (!add_linear_area_to_dtree(node, len, seg->lv->vg->extent_size, + cmd->use_linear_target, + seg->lv->vg->name, seg->lv->name)) + return_0; + + if (!dm_tree_node_add_target_area(node, NULL, vdo_pool_uuid, + first_seg(seg_lv(seg, 0))->vdo_pool_header_size + + seg->lv->vg->extent_size * (uint64_t)seg_le(seg, 0))) + return_0; + + return 1; +} + +#endif + +/* + * VDO pool + */ +static const char *_vdo_pool_name(const struct lv_segment *seg) +{ + return SEG_TYPE_NAME_VDO_POOL; +} + +static void _vdo_pool_display(const struct lv_segment *seg) +{ + struct cmd_context *cmd = seg->lv->vg->cmd; + const struct dm_vdo_target_params *vtp = &seg->vdo_params; + + log_print(" Virtual size\t%s", display_size(cmd, get_vdo_pool_virtual_size(seg))); + log_print(" Header size\t\t%s", display_size(cmd, seg->vdo_pool_header_size)); + + _print_yes_no("Compression\t", vtp->use_compression); + _print_yes_no("Deduplication", vtp->use_deduplication); + _print_yes_no("Metadata hints", vtp->use_metadata_hints); + + log_print(" Minimum IO size\t%s", + display_size(cmd, vtp->minimum_io_size)); + log_print(" Block map cache sz\t%s", + display_size(cmd, vtp->block_map_cache_size_mb * UINT64_C(2 * 1024))); + log_print(" Block map era length %u", vtp->block_map_era_length); + + _print_yes_no("Sparse index", vtp->use_sparse_index); + + log_print(" Index memory size\t%s", + display_size(cmd, vtp->index_memory_size_mb * UINT64_C(2 * 1024))); + + log_print(" Slab size\t\t%s", + display_size(cmd, vtp->slab_size_mb * UINT64_C(2 * 1024))); + + log_print(" # Ack threads\t%u", (unsigned) vtp->ack_threads); + log_print(" # Bio threads\t%u", (unsigned) vtp->bio_threads); + log_print(" Bio rotation\t%u", (unsigned) vtp->bio_rotation); + log_print(" # CPU threads\t%u", (unsigned) vtp->cpu_threads); + log_print(" # Hash zone threads\t%u", (unsigned) vtp->hash_zone_threads); + log_print(" # Logical threads\t%u", (unsigned) vtp->logical_threads); + log_print(" # Physical threads\t%u", (unsigned) vtp->physical_threads); + log_print(" Max discard\t\t%u", (unsigned) vtp->max_discard); + log_print(" Write policy\t%s", get_vdo_write_policy_name(vtp->write_policy)); +} + +/* reused as _vdo_text_import_area_count */ +static int _vdo_pool_text_import_area_count(const struct dm_config_node *sn __attribute__((unused)), + uint32_t *area_count) +{ + *area_count = 1; + + return 1; +} + +static int _vdo_pool_text_import(struct lv_segment *seg, + const struct dm_config_node *n, + struct dm_hash_table *pv_hash __attribute__((unused))) +{ + struct dm_vdo_target_params *vtp = &seg->vdo_params; + struct logical_volume *data_lv; + const char *str; + + if (!dm_config_has_node(n, "data") || + !(str = dm_config_find_str(n, "data", NULL))) + return _bad_field("data"); + if (!(data_lv = find_lv(seg->lv->vg, str))) { + log_error("Unknown logical volume %s.", str); + return 0; + } + + /* + * TODO: we may avoid printing settings with FIXED default values + * so it would generate smaller metadata. + */ + if (!dm_config_get_uint32(n, "header_size", &seg->vdo_pool_header_size)) + return _bad_field("header_size"); + + if (!dm_config_get_uint32(n, "virtual_extents", &seg->vdo_pool_virtual_extents)) + return _bad_field("virtual_extents"); + + memset(vtp, 0, sizeof(*vtp)); + + if (!_import_bool(n, "use_compression", &vtp->use_compression)) + return_0; + + if (!_import_bool(n, "use_deduplication", &vtp->use_deduplication)) + return_0; + + if (!_import_bool(n, "use_metadata_hints", &vtp->use_metadata_hints)) + return_0; + + if (!dm_config_get_uint32(n, "minimum_io_size", &vtp->minimum_io_size)) + return _bad_field("minimum_io_size"); + vtp->minimum_io_size >>= SECTOR_SHIFT; // keep in sectors, while metadata uses bytes + + if (!dm_config_get_uint32(n, "block_map_cache_size_mb", &vtp->block_map_cache_size_mb)) + return _bad_field("block_map_cache_size_mb"); + + if (!dm_config_get_uint32(n, "block_map_era_length", &vtp->block_map_era_length)) + return _bad_field("block_map_era_length"); + + if (!_import_bool(n, "use_sparse_index", &vtp->use_sparse_index)) + return_0; + + if (!dm_config_get_uint32(n, "index_memory_size_mb", &vtp->index_memory_size_mb)) + return _bad_field("index_memory_size_mb"); + + if (!dm_config_get_uint32(n, "max_discard", &vtp->max_discard)) + return _bad_field("max_discard"); + + if (!dm_config_get_uint32(n, "slab_size_mb", &vtp->slab_size_mb)) + return _bad_field("slab_size_mb"); + + if (!dm_config_get_uint32(n, "ack_threads", &vtp->ack_threads)) + return _bad_field("ack_threads"); + + if (!dm_config_get_uint32(n, "bio_threads", &vtp->bio_threads)) + return _bad_field("bio_threads"); + + if (!dm_config_get_uint32(n, "bio_rotation", &vtp->bio_rotation)) + return _bad_field("bio_rotation"); + + if (!dm_config_get_uint32(n, "cpu_threads", &vtp->cpu_threads)) + return _bad_field("cpu_threads"); + + if (!dm_config_get_uint32(n, "hash_zone_threads", &vtp->hash_zone_threads)) + return _bad_field("hash_zone_threads"); + + if (!dm_config_get_uint32(n, "logical_threads", &vtp->logical_threads)) + return _bad_field("logical_threads"); + + if (!dm_config_get_uint32(n, "physical_threads", &vtp->physical_threads)) + return _bad_field("physical_threads"); + + if (dm_config_has_node(n, "write_policy")) { + if (!(str = dm_config_find_str(n, "write_policy", NULL)) || + !set_vdo_write_policy(&vtp->write_policy, str)) + return _bad_field("write_policy"); + } else + vtp->write_policy = DM_VDO_WRITE_POLICY_AUTO; + + if (!set_lv_segment_area_lv(seg, 0, data_lv, 0, LV_VDO_POOL_DATA)) + return_0; + + seg->lv->status |= LV_VDO_POOL; + lv_set_hidden(data_lv); + + return 1; +} + +static int _vdo_pool_text_export(const struct lv_segment *seg, struct formatter *f) +{ + const struct dm_vdo_target_params *vtp = &seg->vdo_params; + + outf(f, "data = \"%s\"", seg_lv(seg, 0)->name); + outsize(f, seg->vdo_pool_header_size, "header_size = %u", + seg->vdo_pool_header_size); + outsize(f, seg->vdo_pool_virtual_extents * (uint64_t) seg->lv->vg->extent_size, + "virtual_extents = %u", seg->vdo_pool_virtual_extents); + + outnl(f); + + if (vtp->use_compression) + outf(f, "use_compression = 1"); + if (vtp->use_deduplication) + outf(f, "use_deduplication = 1"); + if (vtp->use_metadata_hints) + outf(f, "use_metadata_hints = 1"); + + outf(f, "minimum_io_size = %u", (vtp->minimum_io_size << SECTOR_SHIFT)); + + outsize(f, vtp->block_map_cache_size_mb * UINT64_C(2 * 1024), + "block_map_cache_size_mb = %u", vtp->block_map_cache_size_mb); + outf(f, "block_map_era_length = %u", vtp->block_map_era_length); + + if (vtp->use_sparse_index) + outf(f, "use_sparse_index = 1"); + // TODO - conditionally + outsize(f, vtp->index_memory_size_mb * UINT64_C(2 * 1024), + "index_memory_size_mb = %u", vtp->index_memory_size_mb); + + outf(f, "max_discard = %u", vtp->max_discard); + + // TODO - conditionally + outsize(f, vtp->slab_size_mb * UINT64_C(2 * 1024), + "slab_size_mb = %u", vtp->slab_size_mb); + outf(f, "ack_threads = %u", (unsigned) vtp->ack_threads); + outf(f, "bio_threads = %u", (unsigned) vtp->bio_threads); + outf(f, "bio_rotation = %u", (unsigned) vtp->bio_rotation); + outf(f, "cpu_threads = %u", (unsigned) vtp->cpu_threads); + outf(f, "hash_zone_threads = %u", (unsigned) vtp->hash_zone_threads); + outf(f, "logical_threads = %u", (unsigned) vtp->logical_threads); + outf(f, "physical_threads = %u", (unsigned) vtp->physical_threads); + + if (vtp->write_policy != DM_VDO_WRITE_POLICY_AUTO) + outf(f, "write_policy = %s", get_vdo_write_policy_name(vtp->write_policy)); + + return 1; +} + +#ifdef DEVMAPPER_SUPPORT +static int _vdo_pool_target_status_compatible(const char *type) +{ + return (strcmp(type, TARGET_NAME_VDO) == 0); +} + +static int _vdo_check(struct cmd_context *cmd, const struct lv_segment *seg) +{ + + struct vdo_pool_size_config cfg = { 0 }; + + if (!lv_vdo_pool_size_config(seg->lv, &cfg)) + return_0; + + /* Check if we are just adding more size to the already running vdo pool */ + if (seg->lv->size >= cfg.physical_size) + cfg.physical_size = seg->lv->size - cfg.physical_size; + if (get_vdo_pool_virtual_size(seg) >= cfg.virtual_size) + cfg.virtual_size = get_vdo_pool_virtual_size(seg) - cfg.virtual_size; + if (seg->vdo_params.block_map_cache_size_mb >= cfg.block_map_cache_size_mb) + cfg.block_map_cache_size_mb = seg->vdo_params.block_map_cache_size_mb - cfg.block_map_cache_size_mb; + if (seg->vdo_params.index_memory_size_mb >= cfg.index_memory_size_mb) + cfg.index_memory_size_mb = seg->vdo_params.index_memory_size_mb - cfg.index_memory_size_mb; + + return check_vdo_constrains(cmd, &cfg); +} + +static int _vdo_pool_add_target_line(struct dev_manager *dm, + struct dm_pool *mem, + struct cmd_context *cmd, + void **target_state __attribute__((unused)), + struct lv_segment *seg, + const struct lv_activate_opts *laopts __attribute__((unused)), + struct dm_tree_node *node, uint64_t len, + uint32_t *pvmove_mirror_count __attribute__((unused))) +{ + char *vdo_pool_name, *data_uuid; + unsigned attrs = 0; + + if (seg->segtype->ops->target_present) + seg->segtype->ops->target_present(cmd, NULL, &attrs); + + if (!seg_is_vdo_pool(seg)) { + log_error(INTERNAL_ERROR "Passed segment is not VDO pool."); + return 0; + } + + if (!critical_section() && !_vdo_check(cmd, seg)) + return_0; + + if (!(vdo_pool_name = dm_build_dm_name(mem, seg->lv->vg->name, seg->lv->name, lv_layer(seg->lv)))) + return_0; + + if (!(data_uuid = build_dm_uuid(mem, seg_lv(seg, 0), lv_layer(seg_lv(seg, 0))))) + return_0; + + /* VDO uses virtual size instead of its physical size */ + if (!dm_tree_node_add_vdo_target(node, get_vdo_pool_virtual_size(seg), + !(attrs & VDO_FEATURE_VERSION4) ? 2 : 4, + vdo_pool_name, data_uuid, seg_lv(seg, 0)->size, + &seg->vdo_params)) + return_0; + + return 1; +} + +static int _vdo_target_present(struct cmd_context *cmd, + const struct lv_segment *seg __attribute__((unused)), + unsigned *attributes) +{ + /* List of features with their kernel target version */ + static const struct feature { + uint32_t maj; + uint32_t min; + uint32_t patchlevel; + unsigned vdo_feature; + const char *feature; + } _features[] = { + { 6, 2, 3, VDO_FEATURE_ONLINE_RENAME, "online_rename" }, + { 8, 2, 0, VDO_FEATURE_VERSION4, "version4" }, + }; + static const char _lvmconf[] = "global/vdo_disabled_features"; + static int _vdo_checked = 0; + static int _vdo_present = 0; + static unsigned _vdo_attrs = 0; + uint32_t i, maj, min, patchlevel; + const struct segment_type *segtype; + const struct dm_config_node *cn; + const struct dm_config_value *cv; + const char *str; + + if (!activation()) + return 0; + + if (!_vdo_checked) { + _vdo_checked = 1; + + if (!target_present_version(cmd, TARGET_NAME_VDO, 1, + &maj, &min, &patchlevel)) + return 0; + + if (maj < 6 || (maj == 6 && min < 2)) { + log_warn("WARNING: Target %s version %u.%u.%u is too old.", + _vdo_module, maj, min, patchlevel); + return 0; + } + + /* If stripe target was already detected, reuse its result */ + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_STRIPED)) || + !segtype->ops->target_present || !segtype->ops->target_present(cmd, NULL, NULL)) { + /* Linear/Stripe targer is for mapping LVs on top of single VDO volume. */ + if (!target_present(cmd, TARGET_NAME_LINEAR, 0) || + !target_present(cmd, TARGET_NAME_STRIPED, 0)) + return 0; + } + + _vdo_present = 1; + /* Prepare for adding supported features */ + for (i = 0; i < DM_ARRAY_SIZE(_features); ++i) + if ((maj > _features[i].maj) || + ((maj == _features[i].maj) && (min > _features[i].min)) || + ((maj == _features[i].maj) && (min == _features[i].min) && (patchlevel >= _features[i].patchlevel))) + _vdo_attrs |= _features[i].vdo_feature; + else + log_very_verbose("Target %s does not support %s.", + _vdo_module, + _features[i].feature); + } + + if (attributes) { + if (!_feature_mask) { + /* Support runtime lvm.conf changes, N.B. avoid 32 feature */ + if ((cn = find_config_tree_array(cmd, global_vdo_disabled_features_CFG, NULL))) { + for (cv = cn->v; cv; cv = cv->next) { + if (cv->type != DM_CFG_STRING) { + log_warn("WARNING: Ignoring invalid string in config file %s.", + _lvmconf); + continue; + } + str = cv->v.str; + if (!*str) + continue; + for (i = 0; i < DM_ARRAY_SIZE(_features); ++i) + if (strcasecmp(str, _features[i].feature) == 0) + _feature_mask |= _features[i].vdo_feature; + } + } + _feature_mask = ~_feature_mask; + for (i = 0; i < DM_ARRAY_SIZE(_features); ++i) + if ((_vdo_attrs & _features[i].vdo_feature) && + !(_feature_mask & _features[i].vdo_feature)) + log_very_verbose("Target %s %s support disabled by %s.", + _vdo_module, + _features[i].feature, _lvmconf); + } + *attributes = _vdo_attrs & _feature_mask; + } + + return _vdo_present; +} + +static int _vdo_modules_needed(struct dm_pool *mem, + const struct lv_segment *seg __attribute__((unused)), + struct dm_list *modules) +{ + if (!str_list_add(mem, modules, _vdo_module)) { + log_error("String list allocation failed for VDO module."); + return 0; + } + + return 1; +} + +# ifdef DMEVENTD +/* FIXME Cache this */ +static int _vdo_pool_target_registered(struct lv_segment *seg, int *pending, int *monitored) +{ + return target_registered_with_dmeventd(seg->lv->vg->cmd, + seg->segtype->dso, + seg->lv, pending, monitored); +} + +/* FIXME This gets run while suspended and performs banned operations. */ +static int _vdo_pool_target_set_events(struct lv_segment *seg, int evmask, int set) +{ + /* FIXME Make timeout (10) configurable */ + return target_register_events(seg->lv->vg->cmd, + seg->segtype->dso, + seg->lv, evmask, set, 10); +} + +static int _vdo_pool_target_register_events(struct lv_segment *seg, + int events) +{ + return _vdo_pool_target_set_events(seg, events, 1); +} + +static int _vdo_pool_target_unregister_events(struct lv_segment *seg, + int events) +{ + return _vdo_pool_target_set_events(seg, events, 0); +} + +# endif /* DMEVENTD */ +#endif + +/* reused as _vdo_destroy */ +static void _vdo_pool_destroy(struct segment_type *segtype) +{ + free((void *)segtype->dso); + free((void *)segtype); +} + +static struct segtype_handler _vdo_ops = { + .name = _vdo_name, + .display = _vdo_display, + .text_import = _vdo_text_import, + .text_import_area_count = _vdo_pool_text_import_area_count, + .text_export = _vdo_text_export, + +#ifdef DEVMAPPER_SUPPORT + .target_status_compatible = _vdo_target_status_compatible, + .add_target_line = _vdo_add_target_line, + .target_present = _vdo_target_present, + .modules_needed = _vdo_modules_needed, +#endif + .destroy = _vdo_pool_destroy, +}; + +static struct segtype_handler _vdo_pool_ops = { + .name = _vdo_pool_name, + .display = _vdo_pool_display, + .text_import = _vdo_pool_text_import, + .text_import_area_count = _vdo_pool_text_import_area_count, + .text_export = _vdo_pool_text_export, + +#ifdef DEVMAPPER_SUPPORT + .target_status_compatible = _vdo_pool_target_status_compatible, + .add_target_line = _vdo_pool_add_target_line, + .target_present = _vdo_target_present, + .modules_needed = _vdo_modules_needed, + +# ifdef DMEVENTD + .target_monitored = _vdo_pool_target_registered, + .target_monitor_events = _vdo_pool_target_register_events, + .target_unmonitor_events = _vdo_pool_target_unregister_events, +# endif /* DMEVENTD */ +#endif + .destroy = _vdo_pool_destroy, +}; + +int init_vdo_segtypes(struct cmd_context *cmd, + struct segtype_library *seglib) +{ + struct segment_type *segtype, *pool_segtype; + + if (!(segtype = zalloc(sizeof(*segtype))) || + !(pool_segtype = zalloc(sizeof(*segtype)))) { + log_error("Failed to allocate memory for VDO segtypes."); + free(segtype); + return 0; + } + + segtype->name = SEG_TYPE_NAME_VDO; + segtype->flags = SEG_VDO | SEG_VIRTUAL | SEG_ONLY_EXCLUSIVE; + segtype->ops = &_vdo_ops; + + if (!lvm_register_segtype(seglib, segtype)) { + free(pool_segtype); + return_0; + } + + pool_segtype->name = SEG_TYPE_NAME_VDO_POOL; + pool_segtype->flags = SEG_VDO_POOL | SEG_ONLY_EXCLUSIVE; + pool_segtype->ops = &_vdo_pool_ops; +#ifdef DEVMAPPER_SUPPORT +# ifdef DMEVENTD + pool_segtype->dso = get_monitor_dso_path(cmd, dmeventd_vdo_library_CFG); + if (pool_segtype->dso) + pool_segtype->flags |= SEG_MONITORED; +# endif /* DMEVENTD */ +#endif + + if (!lvm_register_segtype(seglib, pool_segtype)) + return_0; + + log_very_verbose("Initialised segtypes: %s, %s.", segtype->name, pool_segtype->name); + + /* Reset mask for recalc */ + _feature_mask = 0; + + return 1; +} |