2021-11-04 00:02:38 +03:00
/*
* Copyright ( C ) 2021 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 "base/memory/zalloc.h"
# include "lib/misc/lib.h"
# include "lib/commands/toolcontext.h"
# include "lib/device/device.h"
# include "lib/device/online.h"
2022-09-14 17:13:58 +03:00
# include "lib/metadata/metadata.h"
2021-11-04 00:02:38 +03:00
# include <dirent.h>
2021-11-09 01:02:48 +03:00
/*
* file contains :
* < major > : < minor > \ n
* vg : < vgname > \ n
* dev : < devname > \ n \ 0
*
* It ' s possible that vg and dev may not exist .
*/
static int _copy_pvid_file_field ( const char * field , char * buf , int bufsize , char * out , int outsize )
2021-11-04 00:02:38 +03:00
{
2021-11-09 01:02:48 +03:00
char * p ;
int i = 0 ;
if ( ! ( p = strstr ( buf , field ) ) )
return 0 ;
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
p + = strlen ( field ) ;
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
while ( 1 ) {
if ( * p = = ' \n ' )
break ;
if ( * p = = ' \0 ' )
break ;
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
if ( p > = ( buf + bufsize ) )
return 0 ;
if ( i > = outsize - 1 )
return 0 ;
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
out [ i ] = * p ;
i + + ;
p + + ;
2021-11-04 00:02:38 +03:00
}
2021-11-09 01:02:48 +03:00
return i ? 1 : 0 ;
2021-11-04 00:02:38 +03:00
}
# define MAX_PVID_FILE_SIZE 512
2021-11-09 01:02:48 +03:00
int online_pvid_file_read ( char * path , int * major , int * minor , char * vgname , char * devname )
2021-11-04 00:02:38 +03:00
{
char buf [ MAX_PVID_FILE_SIZE ] = { 0 } ;
int fd , rv ;
fd = open ( path , O_RDONLY ) ;
if ( fd < 0 ) {
log_warn ( " Failed to open %s " , path ) ;
return 0 ;
}
rv = read ( fd , buf , sizeof ( buf ) - 1 ) ;
if ( close ( fd ) )
log_sys_debug ( " close " , path ) ;
if ( ! rv | | rv < 0 ) {
log_warn ( " No info in %s " , path ) ;
return 0 ;
}
buf [ rv ] = 0 ; /* \0 terminated buffer */
if ( sscanf ( buf , " %d:%d " , major , minor ) ! = 2 ) {
log_warn ( " No device numbers in %s " , path ) ;
return 0 ;
}
2021-11-09 01:02:48 +03:00
if ( vgname ) {
if ( ! strstr ( buf , " vg: " ) ) {
log_debug ( " No vgname in %s " , path ) ;
vgname [ 0 ] = ' \0 ' ;
goto copy_dev ;
}
if ( ! _copy_pvid_file_field ( " vg: " , buf , MAX_PVID_FILE_SIZE , vgname , NAME_LEN ) ) {
log_warn ( " Ignoring invalid vg field in %s " , path ) ;
vgname [ 0 ] = ' \0 ' ;
goto copy_dev ;
}
if ( ! validate_name ( vgname ) ) {
log_warn ( " Ignoring invalid vgname in %s (%s) " , path , vgname ) ;
vgname [ 0 ] = ' \0 ' ;
goto copy_dev ;
}
}
copy_dev :
if ( devname ) {
if ( ! strstr ( buf , " dev: " ) ) {
log_debug ( " No devname in %s " , path ) ;
devname [ 0 ] = ' \0 ' ;
goto out ;
}
if ( ! _copy_pvid_file_field ( " dev: " , buf , MAX_PVID_FILE_SIZE , devname , NAME_LEN ) ) {
log_warn ( " Ignoring invalid devname field in %s " , path ) ;
devname [ 0 ] = ' \0 ' ;
goto out ;
}
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
if ( strncmp ( devname , " /dev/ " , 5 ) ) {
log_warn ( " Ignoring invalid devname in %s (%s) " , path , devname ) ;
devname [ 0 ] = ' \0 ' ;
goto out ;
}
}
out :
2021-11-04 00:02:38 +03:00
return 1 ;
}
2021-11-05 20:19:35 +03:00
void free_po_list ( struct dm_list * list )
{
struct pv_online * po , * po2 ;
dm_list_iterate_items_safe ( po , po2 , list ) {
dm_list_del ( & po - > list ) ;
free ( po ) ;
}
}
int get_pvs_online ( struct dm_list * pvs_online , const char * vgname )
{
char path [ PATH_MAX ] ;
char file_vgname [ NAME_LEN ] ;
2021-11-09 01:02:48 +03:00
char file_devname [ NAME_LEN ] ;
2021-11-05 20:19:35 +03:00
DIR * dir ;
struct dirent * de ;
struct pv_online * po ;
int file_major = 0 , file_minor = 0 ;
if ( ! ( dir = opendir ( PVS_ONLINE_DIR ) ) )
return 0 ;
while ( ( de = readdir ( dir ) ) ) {
if ( de - > d_name [ 0 ] = = ' . ' )
continue ;
if ( strlen ( de - > d_name ) ! = ID_LEN )
continue ;
memset ( path , 0 , sizeof ( path ) ) ;
snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_ONLINE_DIR , de - > d_name ) ;
file_major = 0 ;
file_minor = 0 ;
memset ( file_vgname , 0 , sizeof ( file_vgname ) ) ;
2021-11-09 01:02:48 +03:00
memset ( file_devname , 0 , sizeof ( file_devname ) ) ;
2021-11-05 20:19:35 +03:00
2021-11-09 01:02:48 +03:00
if ( ! online_pvid_file_read ( path , & file_major , & file_minor , file_vgname , file_devname ) )
2021-11-05 20:19:35 +03:00
continue ;
if ( vgname & & strcmp ( file_vgname , vgname ) )
continue ;
if ( ! ( po = zalloc ( sizeof ( * po ) ) ) )
continue ;
memcpy ( po - > pvid , de - > d_name , ID_LEN ) ;
if ( file_major | | file_minor )
po - > devno = MKDEV ( file_major , file_minor ) ;
if ( file_vgname [ 0 ] )
2021-11-09 01:02:48 +03:00
strncpy ( po - > vgname , file_vgname , NAME_LEN ) ;
if ( file_devname [ 0 ] )
strncpy ( po - > devname , file_devname , NAME_LEN ) ;
2021-11-05 20:19:35 +03:00
2021-11-09 01:02:48 +03:00
log_debug ( " Found PV online %s for VG %s %s " , path , vgname , file_devname ) ;
2021-11-05 20:19:35 +03:00
dm_list_add ( pvs_online , & po - > list ) ;
}
2021-11-08 20:30:25 +03:00
2021-11-05 20:19:35 +03:00
if ( closedir ( dir ) )
log_sys_debug ( " closedir " , PVS_ONLINE_DIR ) ;
2021-11-08 20:30:25 +03:00
2021-11-09 01:02:48 +03:00
log_debug ( " Found PVs online %d for %s " , dm_list_size ( pvs_online ) , vgname ? : " all " ) ;
2021-11-08 20:30:25 +03:00
2021-11-05 20:19:35 +03:00
return 1 ;
}
2021-11-04 00:02:38 +03:00
/*
* When a PV goes offline , remove the vg online file for that VG
* ( even if other PVs for the VG are still online ) . This means
* that the vg will be activated again when it becomes complete .
*/
void online_vg_file_remove ( const char * vgname )
{
char path [ PATH_MAX ] ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , VGS_ONLINE_DIR , vgname ) < 0 ) {
log_error ( " Path %s/%s is too long. " , VGS_ONLINE_DIR , vgname ) ;
return ;
}
log_debug ( " Unlink vg online: %s " , path ) ;
if ( unlink ( path ) & & ( errno ! = ENOENT ) )
log_sys_debug ( " unlink " , path ) ;
}
int online_vg_file_create ( struct cmd_context * cmd , const char * vgname )
{
char path [ PATH_MAX ] ;
int fd ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , VGS_ONLINE_DIR , vgname ) < 0 ) {
log_error_pvscan ( cmd , " Path %s/%s is too long. " , VGS_ONLINE_DIR , vgname ) ;
return 0 ;
}
log_debug ( " Create vg online: %s " , path ) ;
fd = open ( path , O_CREAT | O_EXCL | O_TRUNC | O_RDWR , S_IRUSR | S_IWUSR ) ;
if ( fd < 0 ) {
log_debug ( " Failed to create %s: %d " , path , errno ) ;
return 0 ;
}
/* We don't care about syncing, these files are not even persistent. */
if ( close ( fd ) )
log_sys_debug ( " close " , path ) ;
return 1 ;
}
int online_pvid_file_create ( struct cmd_context * cmd , struct device * dev , const char * vgname )
{
char path [ PATH_MAX ] ;
char buf [ MAX_PVID_FILE_SIZE ] = { 0 } ;
char file_vgname [ NAME_LEN ] ;
2021-11-09 01:02:48 +03:00
char file_devname [ NAME_LEN ] ;
char devname [ NAME_LEN ] ;
int devnamelen ;
2021-11-04 00:02:38 +03:00
int file_major = 0 , file_minor = 0 ;
int major , minor ;
int fd ;
int rv ;
int len ;
int len1 = 0 ;
int len2 = 0 ;
2021-11-09 01:02:48 +03:00
int len3 = 0 ;
2021-11-04 00:02:38 +03:00
major = ( int ) MAJOR ( dev - > dev ) ;
minor = ( int ) MINOR ( dev - > dev ) ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_ONLINE_DIR , dev - > pvid ) < 0 ) {
log_error_pvscan ( cmd , " Path %s/%s is too long. " , PVS_ONLINE_DIR , dev - > pvid ) ;
return 0 ;
}
if ( ( len1 = dm_snprintf ( buf , sizeof ( buf ) , " %d:%d \n " , major , minor ) ) < 0 ) {
log_error_pvscan ( cmd , " Cannot create online file path for %s %d:%d. " , dev_name ( dev ) , major , minor ) ;
return 0 ;
}
if ( vgname ) {
if ( ( len2 = dm_snprintf ( buf + len1 , sizeof ( buf ) - len1 , " vg:%s \n " , vgname ) ) < 0 ) {
2021-11-09 01:02:48 +03:00
log_print ( " Incomplete online file for %s %d:%d vg %s. " , dev_name ( dev ) , major , minor , vgname ) ;
2021-11-04 00:02:38 +03:00
/* can still continue without vgname */
len2 = 0 ;
}
}
2021-11-09 01:02:48 +03:00
devnamelen = dm_snprintf ( devname , sizeof ( devname ) , " %s " , dev_name ( dev ) ) ;
if ( ( devnamelen > 5 ) & & ( devnamelen < NAME_LEN - 1 ) ) {
if ( ( len3 = dm_snprintf ( buf + len1 + len2 , sizeof ( buf ) - len1 - len2 , " dev:%s \n " , devname ) ) < 0 ) {
log_print ( " Incomplete devname in online file for %s. " , dev_name ( dev ) ) ;
/* can continue without devname */
len3 = 0 ;
}
}
len = len1 + len2 + len3 ;
2021-11-04 00:02:38 +03:00
log_debug ( " Create pv online: %s %d:%d %s " , path , major , minor , dev_name ( dev ) ) ;
fd = open ( path , O_CREAT | O_EXCL | O_RDWR , S_IRUSR | S_IWUSR ) ;
if ( fd < 0 ) {
if ( errno = = EEXIST )
goto check_duplicate ;
log_error_pvscan ( cmd , " Failed to create online file for %s path %s error %d " , dev_name ( dev ) , path , errno ) ;
return 0 ;
}
while ( len > 0 ) {
rv = write ( fd , buf , len ) ;
if ( rv < 0 ) {
/* file exists so it still works in part */
log_warn ( " Cannot write online file for %s to %s error %d " ,
dev_name ( dev ) , path , errno ) ;
if ( close ( fd ) )
log_sys_debug ( " close " , path ) ;
return 1 ;
}
len - = rv ;
}
/* We don't care about syncing, these files are not even persistent. */
if ( close ( fd ) )
log_sys_debug ( " close " , path ) ;
return 1 ;
check_duplicate :
/*
* If a PVID online file already exists for this PVID , check if the
* file contains a different device number , and if so we may have a
* duplicate PV .
*
* FIXME : disable autoactivation of the VG somehow ?
* The VG may or may not already be activated when a dupicate appears .
* Perhaps write a new field in the pv online or vg online file ?
*/
memset ( file_vgname , 0 , sizeof ( file_vgname ) ) ;
2021-11-09 01:02:48 +03:00
memset ( file_devname , 0 , sizeof ( file_devname ) ) ;
2021-11-04 00:02:38 +03:00
2021-11-09 01:02:48 +03:00
online_pvid_file_read ( path , & file_major , & file_minor , file_vgname , file_devname ) ;
2021-11-04 00:02:38 +03:00
if ( ( file_major = = major ) & & ( file_minor = = minor ) ) {
log_debug ( " Existing online file for %d:%d " , major , minor ) ;
return 1 ;
}
/* Don't know how vgname might not match, but it's not good so fail. */
if ( ( file_major ! = major ) | | ( file_minor ! = minor ) )
2021-11-09 01:02:48 +03:00
log_error_pvscan ( cmd , " PV %s %d:%d is duplicate for PVID %s on %d:%d %s. " ,
dev_name ( dev ) , major , minor , dev - > pvid , file_major , file_minor , file_devname ) ;
2021-11-04 00:02:38 +03:00
if ( file_vgname [ 0 ] & & vgname & & strcmp ( file_vgname , vgname ) )
log_error_pvscan ( cmd , " PV %s has unexpected VG %s vs %s. " ,
dev_name ( dev ) , vgname , file_vgname ) ;
return 0 ;
}
int online_pvid_file_exists ( const char * pvid )
{
char path [ PATH_MAX ] = { 0 } ;
struct stat buf ;
int rv ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_ONLINE_DIR , pvid ) < 0 ) {
log_debug ( INTERNAL_ERROR " Path %s/%s is too long. " , PVS_ONLINE_DIR , pvid ) ;
return 0 ;
}
log_debug ( " Check pv online: %s " , path ) ;
rv = stat ( path , & buf ) ;
if ( ! rv ) {
log_debug ( " Check pv online %s: yes " , pvid ) ;
return 1 ;
}
log_debug ( " Check pv online %s: no " , pvid ) ;
return 0 ;
}
2021-11-05 20:19:35 +03:00
int get_pvs_lookup ( struct dm_list * pvs_online , const char * vgname )
{
char lookup_path [ PATH_MAX ] = { 0 } ;
char path [ PATH_MAX ] = { 0 } ;
char line [ 64 ] ;
char pvid [ ID_LEN + 1 ] __attribute__ ( ( aligned ( 8 ) ) ) = { 0 } ;
char file_vgname [ NAME_LEN ] ;
2021-11-09 01:02:48 +03:00
char file_devname [ NAME_LEN ] ;
2021-11-05 20:19:35 +03:00
struct pv_online * po ;
int file_major = 0 , file_minor = 0 ;
FILE * fp ;
if ( dm_snprintf ( lookup_path , sizeof ( path ) , " %s/%s " , PVS_LOOKUP_DIR , vgname ) < 0 )
return_0 ;
if ( ! ( fp = fopen ( lookup_path , " r " ) ) )
return_0 ;
while ( fgets ( line , sizeof ( line ) , fp ) ) {
memcpy ( pvid , line , ID_LEN ) ;
if ( strlen ( pvid ) ! = ID_LEN )
goto_bad ;
memset ( path , 0 , sizeof ( path ) ) ;
snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_ONLINE_DIR , pvid ) ;
file_major = 0 ;
file_minor = 0 ;
memset ( file_vgname , 0 , sizeof ( file_vgname ) ) ;
2021-11-09 01:02:48 +03:00
memset ( file_devname , 0 , sizeof ( file_devname ) ) ;
2021-11-05 20:19:35 +03:00
2021-11-09 01:02:48 +03:00
if ( ! online_pvid_file_read ( path , & file_major , & file_minor , file_vgname , file_devname ) )
2021-11-05 20:19:35 +03:00
goto_bad ;
2021-11-12 20:52:36 +03:00
/*
* PVs without metadata will not have a vgname in their pvid
* file , but the purpose of using the lookup file is that we
* know the PV is for this VG even without the pvid vgname
* field .
*/
if ( vgname & & file_vgname [ 0 ] & & strcmp ( file_vgname , vgname ) ) {
/* Should never happen */
log_error ( " Incorrect VG lookup file %s PVID %s %s. " , vgname , pvid , file_vgname ) ;
2021-11-05 20:19:35 +03:00
goto_bad ;
2021-11-12 20:52:36 +03:00
}
2021-11-05 20:19:35 +03:00
if ( ! ( po = zalloc ( sizeof ( * po ) ) ) )
goto_bad ;
memcpy ( po - > pvid , pvid , ID_LEN ) ;
if ( file_major | | file_minor )
po - > devno = MKDEV ( file_major , file_minor ) ;
if ( file_vgname [ 0 ] )
strncpy ( po - > vgname , file_vgname , NAME_LEN - 1 ) ;
2021-11-09 01:02:48 +03:00
if ( file_devname [ 0 ] )
strncpy ( po - > devname , file_devname , NAME_LEN - 1 ) ;
2021-11-05 20:19:35 +03:00
2021-11-09 01:02:48 +03:00
log_debug ( " Found PV online lookup %s for VG %s on %s " , path , vgname , file_devname ) ;
2021-11-05 20:19:35 +03:00
dm_list_add ( pvs_online , & po - > list ) ;
}
2021-11-09 01:02:48 +03:00
log_debug ( " Found PVs online lookup %d for %s " , dm_list_size ( pvs_online ) , vgname ) ;
2021-11-08 20:30:25 +03:00
2021-11-05 20:19:35 +03:00
fclose ( fp ) ;
return 1 ;
bad :
free_po_list ( pvs_online ) ;
fclose ( fp ) ;
return 0 ;
}
2021-11-04 00:02:38 +03:00
void online_dir_setup ( struct cmd_context * cmd )
{
struct stat st ;
int rv ;
if ( ! stat ( DEFAULT_RUN_DIR , & st ) )
goto do_pvs ;
log_debug ( " Creating run_dir. " ) ;
dm_prepare_selinux_context ( DEFAULT_RUN_DIR , S_IFDIR ) ;
rv = mkdir ( DEFAULT_RUN_DIR , 0755 ) ;
dm_prepare_selinux_context ( NULL , 0 ) ;
if ( ( rv < 0 ) & & stat ( DEFAULT_RUN_DIR , & st ) )
log_error_pvscan ( cmd , " Failed to create %s %d " , DEFAULT_RUN_DIR , errno ) ;
do_pvs :
if ( ! stat ( PVS_ONLINE_DIR , & st ) )
goto do_vgs ;
log_debug ( " Creating pvs_online_dir. " ) ;
dm_prepare_selinux_context ( PVS_ONLINE_DIR , S_IFDIR ) ;
rv = mkdir ( PVS_ONLINE_DIR , 0755 ) ;
dm_prepare_selinux_context ( NULL , 0 ) ;
if ( ( rv < 0 ) & & stat ( PVS_ONLINE_DIR , & st ) )
log_error_pvscan ( cmd , " Failed to create %s %d " , PVS_ONLINE_DIR , errno ) ;
do_vgs :
if ( ! stat ( VGS_ONLINE_DIR , & st ) )
goto do_lookup ;
log_debug ( " Creating vgs_online_dir. " ) ;
dm_prepare_selinux_context ( VGS_ONLINE_DIR , S_IFDIR ) ;
rv = mkdir ( VGS_ONLINE_DIR , 0755 ) ;
dm_prepare_selinux_context ( NULL , 0 ) ;
if ( ( rv < 0 ) & & stat ( VGS_ONLINE_DIR , & st ) )
log_error_pvscan ( cmd , " Failed to create %s %d " , VGS_ONLINE_DIR , errno ) ;
do_lookup :
if ( ! stat ( PVS_LOOKUP_DIR , & st ) )
return ;
log_debug ( " Creating pvs_lookup_dir. " ) ;
dm_prepare_selinux_context ( PVS_LOOKUP_DIR , S_IFDIR ) ;
rv = mkdir ( PVS_LOOKUP_DIR , 0755 ) ;
dm_prepare_selinux_context ( NULL , 0 ) ;
if ( ( rv < 0 ) & & stat ( PVS_LOOKUP_DIR , & st ) )
log_error_pvscan ( cmd , " Failed to create %s %d " , PVS_LOOKUP_DIR , errno ) ;
}
2022-09-14 17:13:58 +03:00
void online_lookup_file_remove ( const char * vgname )
{
char path [ PATH_MAX ] ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_LOOKUP_DIR , vgname ) < 0 ) {
log_error ( " Path %s/%s is too long. " , PVS_LOOKUP_DIR , vgname ) ;
return ;
}
log_debug ( " Unlink pvs_lookup: %s " , path ) ;
if ( unlink ( path ) & & ( errno ! = ENOENT ) )
log_sys_debug ( " unlink " , path ) ;
}
static int _online_pvid_file_remove ( char * pvid )
{
char path [ PATH_MAX ] ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/%s " , PVS_ONLINE_DIR , pvid ) < 0 )
return_0 ;
if ( ! unlink ( path ) )
return 1 ;
return 0 ;
}
/*
* Reboot automatically clearing tmpfs on / run is the main method of removing
* online files . It ' s important to note that removing the online files for a
* VG is not a technical requirement for anything and could easily be skipped
* if it had any downside . It ' s only done to clean up the space used in / run
* by the online files , e . g . if there happens to be an extreme amount of
* vgcreate / pvscan / vgremove between reboots that are leaving a large number of
* useless online files consuming tmpfs space .
*/
void online_vgremove ( struct volume_group * vg )
{
char pvid [ ID_LEN + 1 ] __attribute__ ( ( aligned ( 8 ) ) ) = { 0 } ;
struct pv_list * pvl ;
/*
* online files may not exist for the vg if there has been no
* pvscans or autoactivation .
*/
online_vg_file_remove ( vg - > name ) ;
online_lookup_file_remove ( vg - > name ) ;
dm_list_iterate_items ( pvl , & vg - > pvs ) {
memcpy ( pvid , & pvl - > pv - > id . uuid , ID_LEN ) ;
_online_pvid_file_remove ( pvid ) ;
}
}