2011-08-24 12:27:49 +04:00
/*
2013-02-21 13:25:44 +04:00
* Copyright ( C ) 2011 - 2013 Red Hat , Inc . All rights reserved .
2011-08-24 12:27:49 +04:00
*
* 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 ,
2016-01-21 13:49:46 +03:00
* Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
2011-08-24 12:27:49 +04:00
*/
2018-06-08 15:40:53 +03:00
# include "base/memory/zalloc.h"
2018-05-14 12:30:20 +03:00
# include "lib/misc/lib.h"
# include "lib/display/display.h"
# include "lib/metadata/metadata.h"
# include "lib/metadata/segtype.h"
# include "lib/format_text/text_export.h"
# include "lib/config/config.h"
# include "lib/activate/activate.h"
# include "lib/datastruct/str_list.h"
2011-08-24 12:27:49 +04:00
/* Dm kernel module name for thin provisiong */
2013-07-18 18:06:26 +04:00
static const char _thin_pool_module [ ] = " thin-pool " ;
static const char _thin_module [ ] = " thin " ;
2011-08-24 12:27:49 +04:00
2011-08-26 17:37:47 +04:00
/*
* Macro used as return argument - returns 0.
* return is left to be written in the function for better readability .
*/
# define SEG_LOG_ERROR(t, p...) \
log_error ( t " segment %s of logical volume %s. " , # # p , \
2011-08-30 18:55:15 +04:00
dm_config_parent_name ( sn ) , seg - > lv - > name ) , 0 ;
2011-08-26 17:37:47 +04:00
2012-12-03 16:03:41 +04:00
/* TODO: using static field here, maybe should be a part of segment_type */
2012-12-03 14:52:04 +04:00
static unsigned _feature_mask ;
2014-04-28 13:56:07 +04:00
static void _thin_pool_display ( const struct lv_segment * seg )
{
log_print ( " Chunk size \t \t %s " ,
display_size ( seg - > lv - > vg - > cmd , seg - > chunk_size ) ) ;
log_print ( " Discards \t \t %s " , get_pool_discards_name ( seg - > discards ) ) ;
log_print ( " Thin count \t \t %u " ,
dm_list_size ( & seg - > lv - > segs_using_this_lv ) ) ;
log_print ( " Transaction ID \t % " PRIu64 , seg - > transaction_id ) ;
log_print ( " Zero new blocks \t %s " ,
2017-03-03 22:46:13 +03:00
( seg - > zero_new_blocks = = THIN_ZERO_YES ) ? " yes " : " no " ) ;
2014-04-28 13:56:07 +04:00
log_print ( " " ) ;
}
2011-10-17 18:17:09 +04:00
static int _thin_pool_add_message ( struct lv_segment * seg ,
const char * key ,
const struct dm_config_node * sn )
{
const char * lv_name = NULL ;
struct logical_volume * lv = NULL ;
2011-11-03 18:37:23 +04:00
uint32_t delete_id = 0 ;
2011-10-17 18:17:09 +04:00
dm_thin_message_t type ;
2012-03-03 01:43:26 +04:00
/* Message must have only one from: create, delete */
2011-10-17 18:17:09 +04:00
if ( dm_config_get_str ( sn , " create " , & lv_name ) ) {
if ( ! ( lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
return SEG_LOG_ERROR ( " Unknown LV %s for create message in " ,
lv_name ) ;
/* FIXME: switch to _SNAP later, if the created LV has an origin */
type = DM_THIN_MESSAGE_CREATE_THIN ;
2014-01-08 13:56:05 +04:00
} else if ( dm_config_get_uint32 ( sn , " delete " , & delete_id ) )
2011-10-17 18:17:09 +04:00
type = DM_THIN_MESSAGE_DELETE ;
2014-01-08 13:56:05 +04:00
else
return SEG_LOG_ERROR ( " Unknown message in " ) ;
2011-10-17 18:17:09 +04:00
2022-08-24 16:02:07 +03:00
if ( ! attach_thin_pool_message ( seg , type , lv , delete_id , 1 ) )
2011-10-17 18:17:09 +04:00
return_0 ;
return 1 ;
}
2011-10-11 12:51:56 +04:00
static int _thin_pool_text_import ( struct lv_segment * seg ,
const struct dm_config_node * sn ,
struct dm_hash_table * pv_hash __attribute__ ( ( unused ) ) )
2011-08-25 14:00:09 +04:00
{
2011-09-01 14:16:32 +04:00
const char * lv_name ;
2011-09-07 02:43:56 +04:00
struct logical_volume * pool_data_lv , * pool_metadata_lv ;
2012-08-08 00:24:41 +04:00
const char * discards_str = NULL ;
2017-03-03 22:46:13 +03:00
uint32_t zero = 0 ;
2021-01-12 19:59:29 +03:00
uint32_t crop = 0 ;
2011-08-26 17:37:47 +04:00
2011-09-01 14:16:32 +04:00
if ( ! dm_config_get_str ( sn , " metadata " , & lv_name ) )
2011-09-07 02:35:44 +04:00
return SEG_LOG_ERROR ( " Metadata must be a string in " ) ;
2011-08-26 17:37:47 +04:00
2011-09-07 02:43:56 +04:00
if ( ! ( pool_metadata_lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
2011-09-07 02:35:44 +04:00
return SEG_LOG_ERROR ( " Unknown metadata %s in " , lv_name ) ;
2011-08-26 17:37:47 +04:00
2011-10-22 20:45:25 +04:00
if ( ! dm_config_get_str ( sn , " pool " , & lv_name ) )
return SEG_LOG_ERROR ( " Pool must be a string in " ) ;
2011-09-07 02:43:56 +04:00
2011-10-22 20:45:25 +04:00
if ( ! ( pool_data_lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
return SEG_LOG_ERROR ( " Unknown pool %s in " , lv_name ) ;
2013-07-31 17:13:54 +04:00
if ( ! attach_pool_data_lv ( seg , pool_data_lv ) )
2011-09-07 02:43:56 +04:00
return_0 ;
2013-07-31 17:13:54 +04:00
if ( ! attach_pool_metadata_lv ( seg , pool_metadata_lv ) )
2011-10-22 20:45:25 +04:00
return_0 ;
2011-08-30 18:55:15 +04:00
if ( ! dm_config_get_uint64 ( sn , " transaction_id " , & seg - > transaction_id ) )
2011-08-26 17:37:47 +04:00
return SEG_LOG_ERROR ( " Could not read transaction_id for " ) ;
2012-01-24 04:55:03 +04:00
if ( ! dm_config_get_uint32 ( sn , " chunk_size " , & seg - > chunk_size ) )
return SEG_LOG_ERROR ( " Could not read chunk_size " ) ;
2011-10-22 20:45:25 +04:00
2012-08-08 00:24:41 +04:00
if ( dm_config_has_node ( sn , " discards " ) & &
2012-08-08 00:59:06 +04:00
! dm_config_get_str ( sn , " discards " , & discards_str ) )
2012-08-08 00:24:41 +04:00
return SEG_LOG_ERROR ( " Could not read discards for " ) ;
2012-06-28 16:47:34 +04:00
2012-08-08 00:24:41 +04:00
if ( ! discards_str )
2012-08-09 13:24:37 +04:00
seg - > discards = THIN_DISCARDS_IGNORE ;
2014-11-08 03:28:38 +03:00
else if ( ! set_pool_discards ( & seg - > discards , discards_str ) )
2012-08-08 00:24:41 +04:00
return SEG_LOG_ERROR ( " Discards option unsupported for " ) ;
2012-06-28 16:47:34 +04:00
2012-01-24 04:55:03 +04:00
if ( ( seg - > chunk_size < DM_THIN_MIN_DATA_BLOCK_SIZE ) | |
( seg - > chunk_size > DM_THIN_MAX_DATA_BLOCK_SIZE ) )
return SEG_LOG_ERROR ( " Unsupported value %u for chunk_size " ,
2011-10-06 15:06:36 +04:00
seg - > device_id ) ;
2011-09-01 14:16:32 +04:00
if ( dm_config_has_node ( sn , " zero_new_blocks " ) & &
2017-03-03 22:46:13 +03:00
! dm_config_get_uint32 ( sn , " zero_new_blocks " , & zero ) )
2011-08-26 17:37:47 +04:00
return SEG_LOG_ERROR ( " Could not read zero_new_blocks for " ) ;
2017-03-03 22:46:13 +03:00
seg - > zero_new_blocks = ( zero ) ? THIN_ZERO_YES : THIN_ZERO_NO ;
2021-01-12 19:59:29 +03:00
if ( dm_config_has_node ( sn , " crop_metadata " ) ) {
if ( ! dm_config_get_uint32 ( sn , " crop_metadata " , & crop ) )
return SEG_LOG_ERROR ( " Could not read crop_metadata for " ) ;
seg - > crop_metadata = ( crop ) ? THIN_CROP_METADATA_YES : THIN_CROP_METADATA_NO ;
seg - > lv - > status | = LV_CROP_METADATA ;
}
2011-10-17 18:17:09 +04:00
/* Read messages */
for ( ; sn ; sn = sn - > sib )
if ( ! ( sn - > v ) & & ! _thin_pool_add_message ( seg , sn - > key , sn - > child ) )
return_0 ;
2011-09-08 20:41:18 +04:00
return 1 ;
}
static int _thin_pool_text_import_area_count ( const struct dm_config_node * sn ,
uint32_t * area_count )
{
* area_count = 1 ;
2011-08-25 14:00:09 +04:00
return 1 ;
}
static int _thin_pool_text_export ( const struct lv_segment * seg , struct formatter * f )
{
2011-10-17 18:17:09 +04:00
unsigned cnt = 0 ;
2011-10-20 14:31:27 +04:00
const struct lv_thin_message * tmsg ;
2011-10-17 18:17:09 +04:00
2012-01-19 19:23:50 +04:00
outf ( f , " metadata = \" %s \" " , seg - > metadata_lv - > name ) ;
2011-10-22 20:45:25 +04:00
outf ( f , " pool = \" %s \" " , seg_lv ( seg , 0 ) - > name ) ;
2011-08-26 17:37:47 +04:00
outf ( f , " transaction_id = % " PRIu64 , seg - > transaction_id ) ;
2012-01-24 04:55:03 +04:00
outsize ( f , ( uint64_t ) seg - > chunk_size ,
" chunk_size = %u " , seg - > chunk_size ) ;
2011-10-17 18:17:09 +04:00
2012-08-08 00:24:41 +04:00
switch ( seg - > discards ) {
case THIN_DISCARDS_PASSDOWN :
case THIN_DISCARDS_NO_PASSDOWN :
case THIN_DISCARDS_IGNORE :
outf ( f , " discards = \" %s \" " , get_pool_discards_name ( seg - > discards ) ) ;
2012-06-28 16:47:34 +04:00
break ;
default :
2012-08-08 00:24:41 +04:00
log_error ( INTERNAL_ERROR " Invalid discards value %d. " , seg - > discards ) ;
2012-06-28 16:47:34 +04:00
return 0 ;
}
2017-03-03 22:46:13 +03:00
if ( seg - > zero_new_blocks = = THIN_ZERO_YES )
2011-08-26 17:37:47 +04:00
outf ( f , " zero_new_blocks = 1 " ) ;
2017-03-03 22:46:13 +03:00
else if ( seg - > zero_new_blocks ! = THIN_ZERO_NO ) {
log_error ( INTERNAL_ERROR " Invalid zero new blocks value %d. " ,
seg - > zero_new_blocks ) ;
return 0 ;
}
2011-08-26 17:37:47 +04:00
2021-01-12 19:59:29 +03:00
if ( seg - > crop_metadata ! = THIN_CROP_METADATA_UNSELECTED )
outf ( f , " crop_metadata = %u " , ( seg - > crop_metadata = = THIN_CROP_METADATA_YES ) ? 1 : 0 ) ;
2011-10-17 18:17:09 +04:00
dm_list_iterate_items ( tmsg , & seg - > thin_messages ) {
/* Extra validation */
switch ( tmsg - > type ) {
case DM_THIN_MESSAGE_CREATE_SNAP :
case DM_THIN_MESSAGE_CREATE_THIN :
if ( ! lv_is_thin_volume ( tmsg - > u . lv ) ) {
log_error ( INTERNAL_ERROR
" LV %s is not a thin volume. " ,
tmsg - > u . lv - > name ) ;
return 0 ;
}
break ;
default :
break ;
}
if ( ! cnt )
outnl ( f ) ;
outf ( f , " message%d { " , + + cnt ) ;
out_inc_indent ( f ) ;
switch ( tmsg - > type ) {
case DM_THIN_MESSAGE_CREATE_SNAP :
case DM_THIN_MESSAGE_CREATE_THIN :
outf ( f , " create = \" %s \" " , tmsg - > u . lv - > name ) ;
break ;
case DM_THIN_MESSAGE_DELETE :
outf ( f , " delete = %d " , tmsg - > u . delete_id ) ;
break ;
default :
log_error ( INTERNAL_ERROR " Passed unsupported message. " ) ;
return 0 ;
}
out_dec_indent ( f ) ;
outf ( f , " } " ) ;
}
2011-08-25 14:00:09 +04:00
return 1 ;
}
2011-08-24 12:27:49 +04:00
2011-09-29 12:56:38 +04:00
# ifdef DEVMAPPER_SUPPORT
2013-09-27 15:58:55 +04:00
static int _thin_target_present ( struct cmd_context * cmd ,
2014-02-24 16:13:20 +04:00
const struct lv_segment * seg __attribute__ ( ( unused ) ) ,
2013-09-27 15:58:55 +04:00
unsigned * attributes ) ;
2013-09-30 10:17:56 +04:00
static int _thin_pool_modules_needed ( struct dm_pool * mem ,
const struct lv_segment * seg __attribute__ ( ( unused ) ) ,
struct dm_list * modules )
{
if ( ! str_list_add ( mem , modules , _thin_pool_module ) ) {
log_error ( " String list allocation failed for thin_pool. " ) ;
return 0 ;
}
return 1 ;
}
static int _thin_modules_needed ( struct dm_pool * mem ,
const struct lv_segment * seg ,
struct dm_list * modules )
{
if ( ! _thin_pool_modules_needed ( mem , seg , modules ) )
return_0 ;
if ( ! str_list_add ( mem , modules , _thin_module ) ) {
log_error ( " String list allocation failed for thin. " ) ;
return 0 ;
}
return 1 ;
}
2011-09-29 12:56:38 +04:00
static int _thin_pool_add_target_line ( struct dev_manager * dm ,
2012-01-25 12:51:29 +04:00
struct dm_pool * mem ,
struct cmd_context * cmd ,
2011-10-11 12:51:56 +04:00
void * * target_state __attribute__ ( ( unused ) ) ,
struct lv_segment * seg ,
2012-01-25 12:51:29 +04:00
const struct lv_activate_opts * laopts ,
2011-10-11 12:51:56 +04:00
struct dm_tree_node * node , uint64_t len ,
uint32_t * pvmove_mirror_count __attribute__ ( ( unused ) ) )
2011-09-29 12:56:38 +04:00
{
2012-08-08 00:24:41 +04:00
static int _no_discards = 0 ;
2015-01-13 17:23:03 +03:00
static int _no_error_if_no_space = 0 ;
2011-09-29 12:56:38 +04:00
char * metadata_dlid , * pool_dlid ;
2011-10-20 14:31:27 +04:00
const struct lv_thin_message * lmsg ;
2011-11-10 19:30:59 +04:00
const struct logical_volume * origin ;
2012-06-28 16:47:34 +04:00
unsigned attr ;
2015-10-29 13:58:06 +03:00
uint64_t low_water_mark ;
int threshold ;
2012-06-28 16:47:34 +04:00
2014-02-24 16:13:20 +04:00
if ( ! _thin_target_present ( cmd , NULL , & attr ) )
2012-06-28 16:47:34 +04:00
return_0 ;
2011-09-29 12:56:38 +04:00
2013-07-19 19:28:43 +04:00
if ( ! seg - > metadata_lv ) {
log_error ( INTERNAL_ERROR " Thin pool is missing metadata device. " ) ;
return 0 ;
}
2012-02-02 17:37:51 +04:00
if ( ! ( attr & THIN_FEATURE_BLOCK_SIZE ) & &
2016-06-30 19:59:44 +03:00
! is_power_of_2 ( seg - > chunk_size ) ) {
2015-10-29 13:01:24 +03:00
log_error ( " Thin pool target does not support %s chunk size (needs "
" kernel >= 3.6). " , display_size ( cmd , seg - > chunk_size ) ) ;
2012-02-02 17:37:51 +04:00
return 0 ;
}
2014-03-11 20:13:47 +04:00
if ( ! ( metadata_dlid = build_dm_uuid ( mem , seg - > metadata_lv , NULL ) ) ) {
2011-10-17 18:17:30 +04:00
log_error ( " Failed to build uuid for metadata LV %s. " ,
2015-10-29 13:01:24 +03:00
display_lvname ( seg - > metadata_lv ) ) ;
2011-09-29 12:56:38 +04:00
return 0 ;
}
2014-03-11 20:13:47 +04:00
if ( ! ( pool_dlid = build_dm_uuid ( mem , seg_lv ( seg , 0 ) , NULL ) ) ) {
2011-10-17 18:17:30 +04:00
log_error ( " Failed to build uuid for pool LV %s. " ,
2015-10-29 13:01:24 +03:00
display_lvname ( seg_lv ( seg , 0 ) ) ) ;
2011-09-29 12:56:38 +04:00
return 0 ;
}
2015-10-29 13:58:06 +03:00
threshold = find_config_tree_int ( seg - > lv - > vg - > cmd ,
activation_thin_pool_autoextend_threshold_CFG ,
lv_config_profile ( seg - > lv ) ) ;
if ( threshold < 50 )
threshold = 50 ;
if ( threshold < 100 )
2016-02-11 20:05:36 +03:00
/* Translate to number of free pool blocks to trigger watermark */
low_water_mark = len / seg - > chunk_size * ( 100 - threshold ) / 100 ;
2015-10-29 13:58:06 +03:00
else
2016-02-11 20:05:36 +03:00
low_water_mark = 0 ;
2015-10-29 13:58:06 +03:00
2021-01-12 19:59:29 +03:00
if ( ! dm_tree_node_add_thin_pool_target_v1 ( node , len ,
seg - > transaction_id ,
metadata_dlid , pool_dlid ,
seg - > chunk_size , low_water_mark ,
( seg - > zero_new_blocks = = THIN_ZERO_YES ) ? 0 : 1 ,
( seg - > crop_metadata = = THIN_CROP_METADATA_YES ) ? 1 : 0 ) )
2011-09-29 12:56:38 +04:00
return_0 ;
2012-08-09 13:25:41 +04:00
if ( attr & THIN_FEATURE_DISCARDS ) {
2012-11-26 14:20:13 +04:00
/* Use ignore for discards ignore or non-power-of-2 chunk_size and <1.5 target */
2012-08-08 00:59:06 +04:00
/* FIXME: Check whether underlying dev supports discards */
2012-11-26 14:20:13 +04:00
if ( ( ( ! ( attr & THIN_FEATURE_DISCARDS_NON_POWER_2 ) & &
2016-06-30 19:59:44 +03:00
! is_power_of_2 ( seg - > chunk_size ) ) | |
2012-12-10 13:22:48 +04:00
( seg - > discards = = THIN_DISCARDS_IGNORE ) ) ) {
if ( ! dm_tree_node_set_thin_pool_discard ( node , 1 , 0 ) )
return_0 ;
} else if ( ! dm_tree_node_set_thin_pool_discard ( node , 0 ,
( seg - > discards = = THIN_DISCARDS_NO_PASSDOWN ) ) )
2012-08-08 00:59:06 +04:00
return_0 ;
2012-08-09 13:25:41 +04:00
} else if ( seg - > discards ! = THIN_DISCARDS_IGNORE )
2012-08-08 00:59:06 +04:00
log_warn_suppress ( _no_discards + + , " WARNING: Thin pool target does "
" not support discards (needs kernel >= 3.4). " ) ;
2012-06-28 16:47:34 +04:00
2015-01-13 17:23:03 +03:00
if ( attr & THIN_FEATURE_ERROR_IF_NO_SPACE )
2015-01-15 17:19:00 +03:00
dm_tree_node_set_thin_pool_error_if_no_space ( node , lv_is_error_when_full ( seg - > lv ) ) ;
else if ( lv_is_error_when_full ( seg - > lv ) )
2015-01-13 17:23:03 +03:00
log_warn_suppress ( _no_error_if_no_space + + , " WARNING: Thin pool target does "
" not support error if no space (needs version >= 1.10). " ) ;
2012-01-25 13:06:43 +04:00
/*
* Add messages only for activation tree .
* Otherwise avoid checking for existence of suspended origin .
* Also transation_id is checked only when snapshot origin is active .
* ( This might change later )
*/
2013-01-17 13:35:27 +04:00
if ( ! laopts - > send_messages )
2012-01-25 13:06:43 +04:00
return 1 ;
2011-10-20 14:32:29 +04:00
dm_list_iterate_items ( lmsg , & seg - > thin_messages ) {
switch ( lmsg - > type ) {
case DM_THIN_MESSAGE_CREATE_THIN :
2011-11-10 19:30:59 +04:00
origin = first_seg ( lmsg - > u . lv ) - > origin ;
2012-01-25 13:06:43 +04:00
/* Check if the origin is suspended */
2013-01-08 02:30:29 +04:00
log_debug_activation ( " Thin pool create_%s %s. " , ( ! origin ) ? " thin " : " snap " , lmsg - > u . lv - > name ) ;
2011-11-03 18:45:01 +04:00
if ( ! dm_tree_node_add_thin_pool_message ( node ,
2011-11-10 19:30:59 +04:00
( ! origin ) ? lmsg - > type : DM_THIN_MESSAGE_CREATE_SNAP ,
2011-11-03 18:45:01 +04:00
first_seg ( lmsg - > u . lv ) - > device_id ,
2011-11-10 19:30:59 +04:00
( ! origin ) ? 0 : first_seg ( origin ) - > device_id ) )
2011-10-20 14:32:29 +04:00
return_0 ;
break ;
case DM_THIN_MESSAGE_DELETE :
2013-01-08 02:30:29 +04:00
log_debug_activation ( " Thin pool delete %u. " , lmsg - > u . delete_id ) ;
2011-11-03 18:45:01 +04:00
if ( ! dm_tree_node_add_thin_pool_message ( node ,
lmsg - > type ,
lmsg - > u . delete_id , 0 ) )
2011-10-20 14:32:29 +04:00
return_0 ;
break ;
default :
log_error ( INTERNAL_ERROR " Unsupported message. " ) ;
return 0 ;
2011-10-17 18:17:09 +04:00
}
2011-10-20 14:32:29 +04:00
}
2011-10-17 18:17:09 +04:00
2011-10-20 14:32:29 +04:00
if ( ! dm_list_empty ( & seg - > thin_messages ) ) {
/* Messages were passed, modify transaction_id as the last one */
2015-08-14 18:41:27 +03:00
log_debug_activation ( " Thin pool set transaction id % " PRIu64 " . " , seg - > transaction_id ) ;
2011-11-03 18:45:01 +04:00
if ( ! dm_tree_node_add_thin_pool_message ( node ,
DM_THIN_MESSAGE_SET_TRANSACTION_ID ,
2015-08-14 18:41:27 +03:00
seg - > transaction_id - 1 ,
seg - > transaction_id ) )
2011-10-17 18:17:09 +04:00
return_0 ;
}
2011-09-29 12:56:38 +04:00
return 1 ;
}
2011-12-21 17:08:11 +04:00
static int _thin_pool_target_percent ( void * * target_state __attribute__ ( ( unused ) ) ,
2014-06-09 14:08:27 +04:00
dm_percent_t * percent ,
2011-12-21 17:08:11 +04:00
struct dm_pool * mem ,
struct cmd_context * cmd __attribute__ ( ( unused ) ) ,
2012-01-19 19:21:23 +04:00
struct lv_segment * seg ,
2011-12-21 17:08:11 +04:00
char * params ,
uint64_t * total_numerator ,
uint64_t * total_denominator )
{
struct dm_status_thin_pool * s ;
if ( ! dm_get_status_thin_pool ( mem , params , & s ) )
return_0 ;
2016-02-18 15:18:18 +03:00
if ( s - > fail | | s - > error )
* percent = DM_PERCENT_INVALID ;
2012-01-19 19:42:18 +04:00
/* With 'seg' report metadata percent, otherwice data percent */
2016-02-18 15:18:18 +03:00
else if ( seg ) {
2014-06-09 14:08:27 +04:00
* percent = dm_make_percent ( s - > used_metadata_blocks ,
s - > total_metadata_blocks ) ;
2012-01-19 19:21:23 +04:00
* total_numerator + = s - > used_metadata_blocks ;
* total_denominator + = s - > total_metadata_blocks ;
} else {
2014-06-09 14:08:27 +04:00
* percent = dm_make_percent ( s - > used_data_blocks ,
s - > total_data_blocks ) ;
2012-01-19 19:21:23 +04:00
* total_numerator + = s - > used_data_blocks ;
* total_denominator + = s - > total_data_blocks ;
}
2011-12-21 17:08:11 +04:00
return 1 ;
}
2012-03-20 21:42:19 +04:00
# ifdef DMEVENTD
/* FIXME Cache this */
2018-01-29 18:28:57 +03:00
static int _target_registered ( struct lv_segment * seg , int * pending , int * monitored )
2012-03-20 21:42:19 +04:00
{
return target_registered_with_dmeventd ( seg - > lv - > vg - > cmd ,
2018-02-10 01:40:37 +03:00
seg - > segtype - > dso ,
2018-01-29 18:28:57 +03:00
seg - > lv , pending , monitored ) ;
2012-03-20 21:42:19 +04:00
}
/* FIXME This gets run while suspended and performs banned operations. */
static int _target_set_events ( struct lv_segment * seg , int evmask , int set )
{
/* FIXME Make timeout (10) configurable */
return target_register_events ( seg - > lv - > vg - > cmd ,
2018-02-10 01:40:37 +03:00
seg - > segtype - > dso ,
2012-03-20 21:42:19 +04:00
seg - > lv , evmask , set , 10 ) ;
}
static int _target_register_events ( struct lv_segment * seg ,
int events )
{
return _target_set_events ( seg , events , 1 ) ;
}
static int _target_unregister_events ( struct lv_segment * seg ,
int events )
{
return _target_set_events ( seg , events , 0 ) ;
}
2013-09-27 15:58:55 +04:00
2012-03-20 21:42:19 +04:00
# endif /* DMEVENTD */
2011-12-21 17:08:11 +04:00
# endif /* DEVMAPPER_SUPPORT */
2011-09-29 12:56:38 +04:00
2014-04-28 13:56:07 +04:00
static void _thin_display ( const struct lv_segment * seg )
{
log_print ( " Device ID \t \t %u " , seg - > device_id ) ;
log_print ( " " ) ;
}
2011-10-11 12:51:56 +04:00
static int _thin_text_import ( struct lv_segment * seg ,
const struct dm_config_node * sn ,
struct dm_hash_table * pv_hash __attribute__ ( ( unused ) ) )
2011-08-24 12:27:49 +04:00
{
2011-09-01 14:16:32 +04:00
const char * lv_name ;
2013-11-29 18:51:28 +04:00
struct logical_volume * pool_lv , * origin = NULL , * external_lv = NULL , * merge_lv = NULL ;
2016-03-01 17:21:36 +03:00
struct generic_logical_volume * indirect_origin = NULL ;
2011-08-26 17:37:47 +04:00
2011-09-01 14:16:32 +04:00
if ( ! dm_config_get_str ( sn , " thin_pool " , & lv_name ) )
2011-08-26 17:37:47 +04:00
return SEG_LOG_ERROR ( " Thin pool must be a string in " ) ;
2011-09-07 02:43:56 +04:00
if ( ! ( pool_lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
2011-09-01 14:16:32 +04:00
return SEG_LOG_ERROR ( " Unknown thin pool %s in " , lv_name ) ;
2011-08-26 17:37:47 +04:00
2011-10-21 15:38:35 +04:00
if ( ! dm_config_get_uint64 ( sn , " transaction_id " , & seg - > transaction_id ) )
return SEG_LOG_ERROR ( " Could not read transaction_id for " ) ;
2011-09-01 14:16:32 +04:00
if ( dm_config_has_node ( sn , " origin " ) ) {
if ( ! dm_config_get_str ( sn , " origin " , & lv_name ) )
2011-09-07 02:35:44 +04:00
return SEG_LOG_ERROR ( " Origin must be a string in " ) ;
2011-08-26 17:37:47 +04:00
2011-11-07 15:03:47 +04:00
if ( ! ( origin = find_lv ( seg - > lv - > vg , lv_name ) ) )
2011-09-01 14:16:32 +04:00
return SEG_LOG_ERROR ( " Unknown origin %s in " , lv_name ) ;
2011-08-26 17:37:47 +04:00
}
2013-11-29 18:51:28 +04:00
if ( dm_config_has_node ( sn , " merge " ) ) {
if ( ! dm_config_get_str ( sn , " merge " , & lv_name ) )
return SEG_LOG_ERROR ( " Merge lv must be a string in " ) ;
if ( ! ( merge_lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
return SEG_LOG_ERROR ( " Unknown merge lv %s in " , lv_name ) ;
}
2011-09-29 12:56:38 +04:00
if ( ! dm_config_get_uint32 ( sn , " device_id " , & seg - > device_id ) )
2011-08-26 17:37:47 +04:00
return SEG_LOG_ERROR ( " Could not read device_id for " ) ;
2011-10-06 15:06:36 +04:00
if ( seg - > device_id > DM_THIN_MAX_DEVICE_ID )
return SEG_LOG_ERROR ( " Unsupported value %u for device_id " ,
seg - > device_id ) ;
2013-02-21 13:25:44 +04:00
if ( dm_config_has_node ( sn , " external_origin " ) ) {
if ( ! dm_config_get_str ( sn , " external_origin " , & lv_name ) )
return SEG_LOG_ERROR ( " External origin must be a string in " ) ;
if ( ! ( external_lv = find_lv ( seg - > lv - > vg , lv_name ) ) )
return SEG_LOG_ERROR ( " Unknown external origin %s in " , lv_name ) ;
}
2016-03-01 17:21:36 +03:00
if ( ! attach_pool_lv ( seg , pool_lv , origin , indirect_origin , merge_lv ) )
2011-11-07 15:03:47 +04:00
return_0 ;
2013-02-21 13:25:44 +04:00
if ( ! attach_thin_external_origin ( seg , external_lv ) )
return_0 ;
2011-08-24 12:27:49 +04:00
return 1 ;
}
static int _thin_text_export ( const struct lv_segment * seg , struct formatter * f )
{
2011-09-07 02:43:56 +04:00
outf ( f , " thin_pool = \" %s \" " , seg - > pool_lv - > name ) ;
2011-10-21 15:38:35 +04:00
outf ( f , " transaction_id = % " PRIu64 , seg - > transaction_id ) ;
2011-09-29 12:56:38 +04:00
outf ( f , " device_id = %d " , seg - > device_id ) ;
2011-08-26 17:37:47 +04:00
2013-02-21 13:25:44 +04:00
if ( seg - > external_lv )
outf ( f , " external_origin = \" %s \" " , seg - > external_lv - > name ) ;
2011-08-26 21:40:53 +04:00
if ( seg - > origin )
outf ( f , " origin = \" %s \" " , seg - > origin - > name ) ;
2016-03-01 17:20:49 +03:00
2013-11-29 18:51:28 +04:00
if ( seg - > merge_lv )
outf ( f , " merge = \" %s \" " , seg - > merge_lv - > name ) ;
2011-08-26 17:37:47 +04:00
2011-08-24 12:27:49 +04:00
return 1 ;
}
# ifdef DEVMAPPER_SUPPORT
2011-09-29 12:56:38 +04:00
static int _thin_add_target_line ( struct dev_manager * dm ,
2011-10-31 02:00:57 +04:00
struct dm_pool * mem ,
2011-09-29 12:56:38 +04:00
struct cmd_context * cmd __attribute__ ( ( unused ) ) ,
void * * target_state __attribute__ ( ( unused ) ) ,
struct lv_segment * seg ,
2013-11-29 18:51:28 +04:00
const struct lv_activate_opts * laopts ,
2011-09-29 12:56:38 +04:00
struct dm_tree_node * node , uint64_t len ,
uint32_t * pvmove_mirror_count __attribute__ ( ( unused ) ) )
{
2013-02-21 13:25:44 +04:00
char * pool_dlid , * external_dlid ;
2011-11-03 18:52:09 +04:00
uint32_t device_id = seg - > device_id ;
2014-01-23 16:10:29 +04:00
unsigned attr ;
2011-09-29 12:56:38 +04:00
2013-06-14 11:51:09 +04:00
if ( ! seg - > pool_lv ) {
log_error ( INTERNAL_ERROR " Segment %s has no pool. " ,
seg - > lv - > name ) ;
return 0 ;
}
2014-03-11 20:13:47 +04:00
if ( ! ( pool_dlid = build_dm_uuid ( mem , seg - > pool_lv , lv_layer ( seg - > pool_lv ) ) ) ) {
2011-11-03 18:52:09 +04:00
log_error ( " Failed to build uuid for pool LV %s. " ,
seg - > pool_lv - > name ) ;
2011-09-29 12:56:38 +04:00
return 0 ;
}
2013-11-29 18:51:28 +04:00
if ( ! laopts - > no_merging ) {
2014-02-18 00:48:27 +04:00
if ( seg - > merge_lv ) {
log_error ( INTERNAL_ERROR " Failed to add merged segment of %s. " ,
seg - > lv - > name ) ;
return 0 ;
}
2013-11-29 18:51:28 +04:00
/*
* merge support for thinp snapshots is implemented by
* simply swapping the thinp device_id of the snapshot
* and origin .
*/
2014-02-18 00:48:27 +04:00
if ( lv_is_merging_origin ( seg - > lv ) & & seg_is_thin_volume ( find_snapshot ( seg - > lv ) ) )
2013-11-29 18:51:28 +04:00
/* origin, use merging snapshot's device_id */
device_id = find_snapshot ( seg - > lv ) - > device_id ;
}
2011-11-03 18:52:09 +04:00
if ( ! dm_tree_node_add_thin_target ( node , len , pool_dlid , device_id ) )
2011-09-29 12:56:38 +04:00
return_0 ;
2013-02-21 13:25:44 +04:00
/* Add external origin LV */
if ( seg - > external_lv ) {
2022-08-24 16:02:07 +03:00
if ( ! thin_pool_supports_external_origin ( first_seg ( seg - > pool_lv ) , seg - > external_lv ) )
2014-01-29 17:27:13 +04:00
return_0 ;
2014-01-23 16:10:29 +04:00
if ( seg - > external_lv - > size < seg - > lv - > size ) {
/* Validate target supports smaller external origin */
2014-02-24 16:13:20 +04:00
if ( ! _thin_target_present ( cmd , NULL , & attr ) | |
2014-01-23 16:10:29 +04:00
! ( attr & THIN_FEATURE_EXTERNAL_ORIGIN_EXTEND ) ) {
log_error ( " Thin target does not support smaller size of external origin LV %s. " ,
seg - > external_lv - > name ) ;
return 0 ;
}
}
2014-03-11 20:13:47 +04:00
if ( ! ( external_dlid = build_dm_uuid ( mem , seg - > external_lv ,
2013-02-21 13:25:44 +04:00
lv_layer ( seg - > external_lv ) ) ) ) {
log_error ( " Failed to build uuid for external origin LV %s. " ,
seg - > external_lv - > name ) ;
return 0 ;
}
if ( ! dm_tree_node_set_thin_external_origin ( node , external_dlid ) )
return_0 ;
}
2011-09-29 12:56:38 +04:00
return 1 ;
}
2012-01-19 19:27:54 +04:00
static int _thin_target_percent ( void * * target_state __attribute__ ( ( unused ) ) ,
2014-06-09 14:08:27 +04:00
dm_percent_t * percent ,
2012-01-19 19:27:54 +04:00
struct dm_pool * mem ,
2016-04-01 22:09:38 +03:00
struct cmd_context * cmd __attribute__ ( ( unused ) ) ,
2012-01-19 19:27:54 +04:00
struct lv_segment * seg ,
char * params ,
uint64_t * total_numerator ,
uint64_t * total_denominator )
{
struct dm_status_thin * s ;
2015-06-18 15:38:57 +03:00
uint64_t csize ;
2012-01-19 19:27:54 +04:00
/* Status for thin device is in sectors */
if ( ! dm_get_status_thin ( mem , params , & s ) )
return_0 ;
2016-02-18 15:18:18 +03:00
if ( s - > fail )
* percent = DM_PERCENT_INVALID ;
else if ( seg ) {
2015-06-18 15:38:57 +03:00
/* Pool allocates whole chunk so round-up to nearest one */
csize = first_seg ( seg - > pool_lv ) - > chunk_size ;
csize = ( ( seg - > lv - > size + csize - 1 ) / csize ) * csize ;
2015-09-03 23:52:21 +03:00
if ( s - > mapped_sectors > csize ) {
log_warn ( " WARNING: LV %s maps %s while the size is only %s. " ,
display_lvname ( seg - > lv ) ,
display_size ( cmd , s - > mapped_sectors ) ,
display_size ( cmd , csize ) ) ;
/* Don't show nonsense numbers like i.e. 1000% full */
s - > mapped_sectors = csize ;
}
2015-06-18 15:38:57 +03:00
* percent = dm_make_percent ( s - > mapped_sectors , csize ) ;
* total_denominator + = csize ;
2012-01-19 19:27:54 +04:00
} else {
2016-04-01 22:09:38 +03:00
/* No lv_segment info here */
* percent = DM_PERCENT_INVALID ;
/* FIXME: Using denominator to pass the mapped info upward? */
2012-01-19 19:27:54 +04:00
* total_denominator + = s - > highest_mapped_sector ;
}
2016-04-01 22:09:38 +03:00
* total_numerator + = s - > mapped_sectors ;
2012-01-19 19:27:54 +04:00
return 1 ;
}
2011-08-24 12:27:49 +04:00
static int _thin_target_present ( struct cmd_context * cmd ,
2014-02-24 16:13:20 +04:00
const struct lv_segment * seg __attribute__ ( ( unused ) ) ,
2012-05-25 15:38:03 +04:00
unsigned * attributes )
2011-08-24 12:27:49 +04:00
{
2012-12-03 16:03:41 +04:00
/* List of features with their kernel target version */
static const struct feature {
uint32_t maj ;
uint32_t min ;
unsigned thin_feature ;
const char * feature ;
2014-08-29 13:51:53 +04:00
} _features [ ] = {
2012-12-03 16:03:41 +04:00
{ 1 , 1 , THIN_FEATURE_DISCARDS , " discards " } ,
{ 1 , 1 , THIN_FEATURE_EXTERNAL_ORIGIN , " external_origin " } ,
{ 1 , 4 , THIN_FEATURE_BLOCK_SIZE , " block_size " } ,
{ 1 , 5 , THIN_FEATURE_DISCARDS_NON_POWER_2 , " discards_non_power_2 " } ,
2014-01-21 16:48:57 +04:00
{ 1 , 10 , THIN_FEATURE_METADATA_RESIZE , " metadata_resize " } ,
2015-01-13 17:23:03 +03:00
{ 1 , 10 , THIN_FEATURE_ERROR_IF_NO_SPACE , " error_if_no_space " } ,
2015-06-18 15:38:57 +03:00
{ 1 , 13 , THIN_FEATURE_EXTERNAL_ORIGIN_EXTEND , " external_origin_extend " } ,
2012-12-03 16:03:41 +04:00
} ;
static const char _lvmconf [ ] = " global/thin_disabled_features " ;
2011-08-24 12:27:49 +04:00
static int _checked = 0 ;
static int _present = 0 ;
2012-12-03 14:52:04 +04:00
static unsigned _attrs = 0 ;
2012-05-25 15:38:03 +04:00
uint32_t maj , min , patchlevel ;
2012-12-03 14:52:04 +04:00
unsigned i ;
2012-12-03 16:03:41 +04:00
const struct dm_config_node * cn ;
const struct dm_config_value * cv ;
const char * str ;
2011-08-24 12:27:49 +04:00
2015-12-17 14:23:33 +03:00
if ( ! activation ( ) )
return 0 ;
2011-08-24 12:27:49 +04:00
if ( ! _checked ) {
2015-12-17 14:23:33 +03:00
_checked = 1 ;
2012-05-25 15:38:03 +04:00
2021-02-07 23:48:18 +03:00
if ( ! ( _present = target_present_version ( cmd , _thin_pool_module , 1 ,
& maj , & min , & patchlevel ) ) )
2012-05-25 15:38:03 +04:00
return 0 ;
2015-12-17 14:23:33 +03:00
2014-04-04 23:10:30 +04:00
for ( i = 0 ; i < DM_ARRAY_SIZE ( _features ) ; + + i )
2014-01-23 16:47:23 +04:00
if ( ( maj > _features [ i ] . maj ) | |
( maj = = _features [ i ] . maj & & min > = _features [ i ] . min ) )
2012-12-03 14:52:04 +04:00
_attrs | = _features [ i ] . thin_feature ;
else
2013-07-18 18:06:26 +04:00
log_very_verbose ( " Target %s does not support %s. " ,
_thin_pool_module ,
2012-12-03 14:52:04 +04:00
_features [ i ] . feature ) ;
2011-08-24 12:27:49 +04:00
}
2012-12-03 14:52:04 +04:00
if ( attributes ) {
2012-12-03 16:03:41 +04:00
if ( ! _feature_mask ) {
/* Support runtime lvm.conf changes, N.B. avoid 32 feature */
2015-07-08 12:22:24 +03:00
if ( ( cn = find_config_tree_array ( cmd , global_thin_disabled_features_CFG , NULL ) ) ) {
2012-12-03 16:03:41 +04:00
for ( cv = cn - > v ; cv ; cv = cv - > next ) {
if ( cv - > type ! = DM_CFG_STRING ) {
2015-12-17 14:23:33 +03:00
log_warn ( " WARNING: Ignoring invalid string in config file %s. " ,
2012-12-03 16:03:41 +04:00
_lvmconf ) ;
continue ;
}
str = cv - > v . str ;
2014-03-12 18:53:20 +04:00
if ( ! * str )
2012-12-03 16:03:41 +04:00
continue ;
2014-04-04 23:10:30 +04:00
for ( i = 0 ; i < DM_ARRAY_SIZE ( _features ) ; + + i )
2012-12-03 16:03:41 +04:00
if ( strcasecmp ( str , _features [ i ] . feature ) = = 0 )
_feature_mask | = _features [ i ] . thin_feature ;
}
}
_feature_mask = ~ _feature_mask ;
2014-04-04 23:10:30 +04:00
for ( i = 0 ; i < DM_ARRAY_SIZE ( _features ) ; + + i )
2012-12-03 14:52:04 +04:00
if ( ( _attrs & _features [ i ] . thin_feature ) & &
! ( _feature_mask & _features [ i ] . thin_feature ) )
2013-07-18 18:06:26 +04:00
log_very_verbose ( " Target %s %s support disabled by %s " ,
_thin_pool_module ,
2012-12-03 14:52:04 +04:00
_features [ i ] . feature , _lvmconf ) ;
}
* attributes = _attrs & _feature_mask ;
}
2012-05-25 15:38:03 +04:00
2011-08-24 12:27:49 +04:00
return _present ;
}
# endif
static void _thin_destroy ( struct segment_type * segtype )
{
2018-06-08 15:40:53 +03:00
free ( ( void * ) segtype - > dso ) ;
free ( segtype ) ;
2011-08-24 12:27:49 +04:00
}
2024-05-03 12:59:05 +03:00
static const struct segtype_handler _thin_pool_ops = {
2014-04-28 13:56:07 +04:00
. display = _thin_pool_display ,
2011-08-25 14:00:09 +04:00
. text_import = _thin_pool_text_import ,
2011-09-08 20:41:18 +04:00
. text_import_area_count = _thin_pool_text_import_area_count ,
2011-08-25 14:00:09 +04:00
. text_export = _thin_pool_text_export ,
2011-09-29 12:56:38 +04:00
# ifdef DEVMAPPER_SUPPORT
. add_target_line = _thin_pool_add_target_line ,
2011-12-21 17:08:11 +04:00
. target_percent = _thin_pool_target_percent ,
2011-09-29 12:56:38 +04:00
. target_present = _thin_target_present ,
2014-04-30 01:41:17 +04:00
. modules_needed = _thin_pool_modules_needed ,
2012-03-20 21:42:19 +04:00
# ifdef DMEVENTD
. target_monitored = _target_registered ,
. target_monitor_events = _target_register_events ,
. target_unmonitor_events = _target_unregister_events ,
# endif /* DMEVENTD */
2013-09-27 15:58:55 +04:00
# endif
2011-08-25 14:00:09 +04:00
. destroy = _thin_destroy ,
} ;
2024-05-03 12:59:05 +03:00
static const struct segtype_handler _thin_ops = {
2014-04-28 13:56:07 +04:00
. display = _thin_display ,
2011-08-24 12:27:49 +04:00
. text_import = _thin_text_import ,
. text_export = _thin_text_export ,
# ifdef DEVMAPPER_SUPPORT
2011-09-29 12:56:38 +04:00
. add_target_line = _thin_add_target_line ,
2012-01-19 19:27:54 +04:00
. target_percent = _thin_target_percent ,
2011-08-24 12:27:49 +04:00
. target_present = _thin_target_present ,
. modules_needed = _thin_modules_needed ,
2013-09-27 15:58:55 +04:00
# endif
2011-08-24 12:27:49 +04:00
. destroy = _thin_destroy ,
} ;
# ifdef THIN_INTERNAL
2011-08-25 14:00:09 +04:00
int init_thin_segtypes ( struct cmd_context * cmd , struct segtype_library * seglib )
# else /* Shared */
int init_multiple_segtypes ( struct cmd_context * cmd , struct segtype_library * seglib ) ;
int init_multiple_segtypes ( struct cmd_context * cmd , struct segtype_library * seglib )
2011-08-24 12:27:49 +04:00
# endif
{
2011-08-25 14:00:09 +04:00
static const struct {
2024-05-03 12:59:05 +03:00
const struct segtype_handler * ops ;
2011-08-25 14:00:09 +04:00
const char name [ 16 ] ;
uint32_t flags ;
} reg_segtypes [ ] = {
2015-01-13 17:23:03 +03:00
{ & _thin_pool_ops , " thin-pool " , SEG_THIN_POOL | SEG_CANNOT_BE_ZEROED |
SEG_ONLY_EXCLUSIVE | SEG_CAN_ERROR_WHEN_FULL } ,
2011-09-06 04:26:42 +04:00
/* FIXME Maybe use SEG_THIN_VOLUME instead of SEG_VIRTUAL */
2014-11-10 20:44:37 +03:00
{ & _thin_ops , " thin " , SEG_THIN_VOLUME | SEG_VIRTUAL | SEG_ONLY_EXCLUSIVE }
2011-08-25 14:00:09 +04:00
} ;
struct segment_type * segtype ;
unsigned i ;
2014-04-04 23:10:30 +04:00
for ( i = 0 ; i < DM_ARRAY_SIZE ( reg_segtypes ) ; + + i ) {
2018-06-08 15:40:53 +03:00
segtype = zalloc ( sizeof ( * segtype ) ) ;
2011-08-25 14:00:09 +04:00
if ( ! segtype ) {
log_error ( " Failed to allocate memory for %s segtype " ,
reg_segtypes [ i ] . name ) ;
return 0 ;
}
segtype - > ops = reg_segtypes [ i ] . ops ;
segtype - > name = reg_segtypes [ i ] . name ;
segtype - > flags = reg_segtypes [ i ] . flags ;
2011-08-24 12:27:49 +04:00
# ifdef DEVMAPPER_SUPPORT
# ifdef DMEVENTD
2018-02-10 22:22:32 +03:00
segtype - > dso = get_monitor_dso_path ( cmd , dmeventd_thin_library_CFG ) ;
2018-02-10 01:40:37 +03:00
2012-02-15 17:49:51 +04:00
if ( ( reg_segtypes [ i ] . flags & SEG_THIN_POOL ) & &
2018-02-10 01:40:37 +03:00
segtype - > dso )
2011-12-21 17:08:11 +04:00
segtype - > flags | = SEG_MONITORED ;
# endif /* DMEVENTD */
2011-08-24 12:27:49 +04:00
# endif
2012-02-28 18:23:41 +04:00
if ( ! lvm_register_segtype ( seglib , segtype ) )
/* segtype is already destroyed */
2011-08-25 14:00:09 +04:00
return_0 ;
2011-08-24 12:27:49 +04:00
2011-08-25 14:00:09 +04:00
log_very_verbose ( " Initialised segtype: %s " , segtype - > name ) ;
}
2012-12-03 14:52:04 +04:00
2012-12-03 16:03:41 +04:00
/* Reset mask for recalc */
_feature_mask = 0 ;
2012-12-03 14:52:04 +04:00
2011-08-25 14:00:09 +04:00
return 1 ;
2011-08-24 12:27:49 +04:00
}