2019-09-24 23:28:02 +03:00
/*
* Copyright ( C ) 2014 - 2015 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/metadata/metadata.h"
# include "lib/locking/locking.h"
# include "lib/misc/lvm-string.h"
# include "lib/commands/toolcontext.h"
# include "lib/display/display.h"
# include "lib/metadata/segtype.h"
# include "lib/activate/activate.h"
# include "lib/config/defaults.h"
2020-02-26 23:47:29 +03:00
# include "lib/datastruct/str_list.h"
2019-09-24 23:28:02 +03:00
int lv_is_writecache_origin ( const struct logical_volume * lv )
{
2020-02-19 22:30:02 +03:00
struct lv_segment * seg ;
2019-09-24 23:28:02 +03:00
2020-02-24 22:52:12 +03:00
/*
* This flag is needed when removing writecache from an origin
* in which case the lv connections have been destroyed and
* identifying a writecache origin by these connections doesn ' t
* work .
*/
if ( lv - > status & WRITECACHE_ORIGIN )
return 1 ;
2020-02-19 22:30:02 +03:00
/* Make sure there's exactly one segment in segs_using_this_lv! */
if ( dm_list_empty ( & lv - > segs_using_this_lv ) | |
( dm_list_size ( & lv - > segs_using_this_lv ) > 1 ) )
return 0 ;
seg = get_only_segment_using_this_lv ( lv ) ;
return seg & & lv_is_writecache ( seg - > lv ) & & ! lv_is_pending_delete ( seg - > lv ) & & ( seg_lv ( seg , 0 ) = = lv ) ;
2019-09-24 23:28:02 +03:00
}
int lv_is_writecache_cachevol ( const struct logical_volume * lv )
{
struct seg_list * sl ;
dm_list_iterate_items ( sl , & lv - > segs_using_this_lv ) {
if ( ! sl - > seg | | ! sl - > seg - > lv | | ! sl - > seg - > writecache )
continue ;
if ( lv_is_writecache ( sl - > seg - > lv ) & & ( sl - > seg - > writecache = = lv ) )
return 1 ;
}
return 0 ;
}
static int _get_writecache_kernel_error ( struct cmd_context * cmd ,
2020-08-28 19:26:40 +03:00
const struct logical_volume * lv ,
2019-09-24 23:28:02 +03:00
uint32_t * kernel_error )
{
struct lv_with_info_and_seg_status status ;
memset ( & status , 0 , sizeof ( status ) ) ;
status . seg_status . type = SEG_STATUS_NONE ;
status . seg_status . seg = first_seg ( lv ) ;
/* FIXME: why reporter_pool? */
if ( ! ( status . seg_status . mem = dm_pool_create ( " reporter_pool " , 1024 ) ) ) {
log_error ( " Failed to get mem for LV status. " ) ;
return 0 ;
}
if ( ! lv_info_with_seg_status ( cmd , first_seg ( lv ) , & status , 1 , 1 ) ) {
log_error ( " Failed to get device mapper status for %s " , display_lvname ( lv ) ) ;
goto fail ;
}
if ( ! status . info . exists ) {
log_error ( " No device mapper info exists for %s " , display_lvname ( lv ) ) ;
goto fail ;
}
if ( status . seg_status . type ! = SEG_STATUS_WRITECACHE ) {
log_error ( " Invalid device mapper status type (%d) for %s " ,
( uint32_t ) status . seg_status . type , display_lvname ( lv ) ) ;
goto fail ;
}
* kernel_error = status . seg_status . writecache - > error ;
dm_pool_destroy ( status . seg_status . mem ) ;
return 1 ;
fail :
dm_pool_destroy ( status . seg_status . mem ) ;
return 0 ;
}
2020-02-24 22:52:12 +03:00
static void _rename_detached_cvol ( struct cmd_context * cmd , struct logical_volume * lv_fast )
{
struct volume_group * vg = lv_fast - > vg ;
char cvol_name [ NAME_LEN ] ;
char * suffix , * cvol_name_dup ;
/*
* Rename lv_fast back to its original name , without the _cvol
* suffix that was added when lv_fast was attached for caching .
* If the name is in use , generate new lvol % d .
* Failing to rename is not really a problem , so we intentionally
* do not consider some things here as errors .
*/
if ( ! dm_strncpy ( cvol_name , lv_fast - > name , sizeof ( cvol_name ) ) | |
! ( suffix = strstr ( cvol_name , " _cvol " ) ) ) {
log_debug ( " LV %s has no suffix for cachevol (skipping rename). " ,
display_lvname ( lv_fast ) ) ;
return ;
}
* suffix = 0 ;
if ( lv_name_is_used_in_vg ( vg , cvol_name , NULL ) & &
! generate_lv_name ( vg , " lvol%d " , cvol_name , sizeof ( cvol_name ) ) ) {
log_warn ( " Failed to generate new unique name for unused LV %s " , lv_fast - > name ) ;
return ;
}
if ( ! ( cvol_name_dup = dm_pool_strdup ( vg - > vgmem , cvol_name ) ) ) {
stack ;
return ;
}
lv_fast - > name = cvol_name_dup ;
}
static int _lv_detach_writecache_cachevol_inactive ( struct logical_volume * lv , int noflush )
2019-09-24 23:28:02 +03:00
{
struct cmd_context * cmd = lv - > vg - > cmd ;
2020-02-24 22:52:12 +03:00
struct volume_group * vg = lv - > vg ;
2019-09-24 23:28:02 +03:00
struct logical_volume * lv_fast ;
2020-02-24 22:52:12 +03:00
struct logical_volume * lv_wcorig ;
struct lv_segment * seg = first_seg ( lv ) ;
2019-09-24 23:28:02 +03:00
uint32_t kernel_error = 0 ;
2020-02-24 22:52:12 +03:00
if ( ! seg_is_writecache ( seg ) ) {
log_error ( " LV %s segment is not writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! ( lv_fast = seg - > writecache ) ) {
log_error ( " LV %s writecache segment has no writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! ( lv_wcorig = seg_lv ( seg , 0 ) ) ) {
log_error ( " LV %s writecache segment has no origin " , display_lvname ( lv ) ) ;
return 0 ;
}
2019-09-24 23:28:02 +03:00
if ( noflush )
goto detach ;
/*
* Activate LV internally since the LV needs to be active to flush .
* LV_TEMPORARY should keep the LV from being exposed to the user
* and being accessed .
*/
lv - > status | = LV_TEMPORARY ;
if ( ! activate_lv ( cmd , lv ) ) {
log_error ( " Failed to activate LV %s for flushing writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! sync_local_dev_names ( cmd ) ) {
log_error ( " Failed to sync local devices before detaching writecache. " ) ;
2020-02-24 22:52:12 +03:00
if ( ! deactivate_lv ( cmd , lv ) )
log_error ( " Failed to deactivate %s. " , display_lvname ( lv ) ) ;
2019-09-24 23:28:02 +03:00
return 0 ;
}
if ( ! lv_writecache_message ( lv , " flush " ) ) {
log_error ( " Failed to flush writecache for %s. " , display_lvname ( lv ) ) ;
if ( ! deactivate_lv ( cmd , lv ) )
log_error ( " Failed to deactivate %s. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! _get_writecache_kernel_error ( cmd , lv , & kernel_error ) ) {
log_error ( " Failed to get writecache error status for %s. " , display_lvname ( lv ) ) ;
if ( ! deactivate_lv ( cmd , lv ) )
log_error ( " Failed to deactivate %s. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( kernel_error ) {
log_error ( " Failed to flush writecache (error %u) for %s. " , kernel_error , display_lvname ( lv ) ) ;
2020-02-24 22:52:12 +03:00
if ( ! deactivate_lv ( cmd , lv ) )
log_error ( " Failed to deactivate %s. " , display_lvname ( lv ) ) ;
2019-09-24 23:28:02 +03:00
return 0 ;
}
if ( ! deactivate_lv ( cmd , lv ) ) {
log_error ( " Failed to deactivate LV %s for detaching writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
lv - > status & = ~ LV_TEMPORARY ;
detach :
2020-02-24 22:52:12 +03:00
if ( ! remove_seg_from_segs_using_this_lv ( lv_fast , seg ) )
return_0 ;
lv - > status & = ~ WRITECACHE ;
seg - > writecache = NULL ;
if ( ! remove_layer_from_lv ( lv , lv_wcorig ) )
return_0 ;
if ( ! lv_remove ( lv_wcorig ) )
return_0 ;
lv_set_visible ( lv_fast ) ;
lv_fast - > status & = ~ LV_CACHE_VOL ;
_rename_detached_cvol ( cmd , lv_fast ) ;
if ( ! vg_write ( vg ) | | ! vg_commit ( vg ) )
return_0 ;
return 1 ;
}
static int _lv_detach_writecache_cachevol_active ( struct logical_volume * lv , int noflush )
{
struct cmd_context * cmd = lv - > vg - > cmd ;
struct volume_group * vg = lv - > vg ;
struct logical_volume * lv_fast ;
struct logical_volume * lv_wcorig ;
2020-08-28 19:26:40 +03:00
const struct logical_volume * lv_old ;
2020-02-24 22:52:12 +03:00
struct lv_segment * seg = first_seg ( lv ) ;
uint32_t kernel_error = 0 ;
if ( ! seg_is_writecache ( seg ) ) {
log_error ( " LV %s segment is not writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! ( lv_fast = seg - > writecache ) ) {
log_error ( " LV %s writecache segment has no writecache. " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! ( lv_wcorig = seg_lv ( seg , 0 ) ) ) {
log_error ( " LV %s writecache segment has no origin " , display_lvname ( lv ) ) ;
return 0 ;
}
if ( noflush )
goto detach ;
if ( ! lv_writecache_message ( lv , " flush_on_suspend " ) ) {
log_error ( " Failed to set flush_on_suspend in writecache detach %s. " , display_lvname ( lv ) ) ;
return 0 ;
}
detach :
if ( ! remove_seg_from_segs_using_this_lv ( lv_fast , seg ) ) {
log_error ( " Failed to remove seg in writecache detach. " ) ;
return 0 ;
}
lv - > status & = ~ WRITECACHE ;
seg - > writecache = NULL ;
if ( ! remove_layer_from_lv ( lv , lv_wcorig ) ) {
log_error ( " Failed to remove lv layer in writecache detach. " ) ;
return 0 ;
}
/*
* vg_write ( ) , suspend_lv ( ) , vg_commit ( ) , resume_lv ( ) .
* usually done by lv_update_and_reload for an active lv ,
* but in this case we need to check for writecache errors
* after suspend .
*/
if ( ! vg_write ( vg ) ) {
log_error ( " Failed to write VG in writecache detach. " ) ;
return 0 ;
}
/*
* The version of LV before removal of writecache . When need to
* check for kernel errors based on the old version of LV which
* is still present in the kernel .
*/
2020-08-28 19:26:40 +03:00
if ( ! ( lv_old = lv_committed ( lv ) ) ) {
2020-02-24 22:52:12 +03:00
log_error ( " Failed to get lv_committed in writecache detach. " ) ;
return 0 ;
}
/*
* suspend does not use ' lv ' as we know it here , but grabs the
* old ( precommitted ) version of ' lv ' using lv_committed ( ) ,
* which is from vg - > vg_comitted .
*/
log_debug ( " Suspending writecache to detach %s " , display_lvname ( lv ) ) ;
if ( ! suspend_lv ( cmd , lv ) ) {
log_error ( " Failed to suspend LV in writecache detach. " ) ;
vg_revert ( vg ) ;
return 0 ;
}
log_debug ( " Checking writecache errors to detach. " ) ;
if ( ! _get_writecache_kernel_error ( cmd , lv_old , & kernel_error ) ) {
log_error ( " Failed to get writecache error status for %s. " , display_lvname ( lv_old ) ) ;
return 0 ;
}
if ( kernel_error ) {
log_error ( " Failed to flush writecache (error %u) for %s. " , kernel_error , display_lvname ( lv ) ) ;
return 0 ;
}
if ( ! vg_commit ( vg ) ) {
log_error ( " Failed to commit VG in writecache detach. " ) ;
return 0 ;
}
/*
* Since vg_commit has happened , vg - > vg_committed is now the
* newest copy of lv , so resume uses the ' lv ' that we know
* here .
*/
log_debug ( " Resuming after writecache detached %s " , display_lvname ( lv ) ) ;
if ( ! resume_lv ( cmd , lv ) ) {
log_error ( " Failed to resume LV in writecache detach. " ) ;
return 0 ;
}
log_debug ( " Deactivating previous cachevol %s " , display_lvname ( lv_fast ) ) ;
if ( ! deactivate_lv ( cmd , lv_fast ) )
log_error ( " Failed to deactivate previous cachevol in writecache detach. " ) ;
/*
* Needed for lv_is_writecache_origin to know lv_wcorig was
* a writecache origin , which is needed so that the - real
* dm uuid suffix is applied , which is needed for deactivate to
* work . This is a hacky roundabout way of setting the - real
* uuid suffix ( it would be nice to have a deactivate command
* that accepts a dm uuid . )
*/
lv_wcorig - > status | = WRITECACHE_ORIGIN ;
log_debug ( " Deactivating previous wcorig %s " , display_lvname ( lv_wcorig ) ) ;
if ( ! lv_deactivate ( cmd , NULL , lv_wcorig ) )
log_error ( " Failed to deactivate previous wcorig LV in writecache detach. " ) ;
log_debug ( " Removing previous wcorig %s " , display_lvname ( lv_wcorig ) ) ;
if ( ! lv_remove ( lv_wcorig ) ) {
log_error ( " Failed to remove previous wcorig LV in writecache detach. " ) ;
return 0 ;
}
lv_set_visible ( lv_fast ) ;
lv_fast - > status & = ~ LV_CACHE_VOL ;
_rename_detached_cvol ( cmd , lv_fast ) ;
if ( ! vg_write ( vg ) | | ! vg_commit ( vg ) ) {
log_error ( " Failed to write and commit VG in writecache detach. " ) ;
2019-09-24 23:28:02 +03:00
return 0 ;
}
return 1 ;
}
2020-02-24 22:52:12 +03:00
int lv_detach_writecache_cachevol ( struct logical_volume * lv , int noflush )
{
if ( lv_is_active ( lv ) )
return _lv_detach_writecache_cachevol_active ( lv , noflush ) ;
else
return _lv_detach_writecache_cachevol_inactive ( lv , noflush ) ;
}
2020-02-26 23:47:29 +03:00
static int _writecache_setting_str_list_add ( const char * field , uint64_t val , char * val_str , struct dm_list * result , struct dm_pool * mem )
{
char buf [ 128 ] ;
char * list_item ;
int len ;
if ( val_str ) {
if ( dm_snprintf ( buf , sizeof ( buf ) , " %s=%s " , field , val_str ) < 0 )
return_0 ;
} else {
if ( dm_snprintf ( buf , sizeof ( buf ) , " %s=%llu " , field , ( unsigned long long ) val ) < 0 )
return_0 ;
}
len = strlen ( buf ) + 1 ;
if ( ! ( list_item = dm_pool_zalloc ( mem , len ) ) )
return_0 ;
memcpy ( list_item , buf , len ) ;
if ( ! str_list_add_no_dup_check ( mem , result , list_item ) )
return_0 ;
return 1 ;
}
int writecache_settings_to_str_list ( struct writecache_settings * settings , struct dm_list * result , struct dm_pool * mem )
{
int errors = 0 ;
if ( settings - > high_watermark_set )
if ( ! _writecache_setting_str_list_add ( " high_watermark " , settings - > high_watermark , NULL , result , mem ) )
errors + + ;
if ( settings - > low_watermark_set )
if ( ! _writecache_setting_str_list_add ( " low_watermark " , settings - > low_watermark , NULL , result , mem ) )
errors + + ;
if ( settings - > writeback_jobs_set )
if ( ! _writecache_setting_str_list_add ( " writeback_jobs " , settings - > writeback_jobs , NULL , result , mem ) )
errors + + ;
if ( settings - > autocommit_blocks_set )
if ( ! _writecache_setting_str_list_add ( " autocommit_blocks " , settings - > autocommit_blocks , NULL , result , mem ) )
errors + + ;
if ( settings - > autocommit_time_set )
if ( ! _writecache_setting_str_list_add ( " autocommit_time " , settings - > autocommit_time , NULL , result , mem ) )
errors + + ;
if ( settings - > fua_set )
if ( ! _writecache_setting_str_list_add ( " fua " , ( uint64_t ) settings - > fua , NULL , result , mem ) )
errors + + ;
if ( settings - > nofua_set )
if ( ! _writecache_setting_str_list_add ( " nofua " , ( uint64_t ) settings - > nofua , NULL , result , mem ) )
errors + + ;
2020-03-31 22:14:50 +03:00
if ( settings - > cleaner_set & & settings - > cleaner )
if ( ! _writecache_setting_str_list_add ( " cleaner " , ( uint64_t ) settings - > cleaner , NULL , result , mem ) )
errors + + ;
if ( settings - > max_age_set )
if ( ! _writecache_setting_str_list_add ( " max_age " , ( uint64_t ) settings - > max_age , NULL , result , mem ) )
errors + + ;
2020-02-26 23:47:29 +03:00
if ( settings - > new_key & & settings - > new_val )
if ( ! _writecache_setting_str_list_add ( settings - > new_key , 0 , settings - > new_val , result , mem ) )
errors + + ;
if ( errors )
log_warn ( " Failed to create list of writecache settings. " ) ;
return 1 ;
}