2011-11-11 19:24:48 +04:00
/*
* Copyright ( C ) 2011 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 ,
2016-01-21 13:49:46 +03:00
* Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
2011-11-11 19:24:48 +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/filters/filter.h"
# include "lib/activate/activate.h"
2014-09-03 17:48:37 +04:00
# ifdef UDEV_SYNC_SUPPORT
# include <libudev.h>
2018-05-14 12:30:20 +03:00
# include "lib/device/dev-ext-udev-constants.h"
2014-09-03 17:48:37 +04:00
# endif
2011-11-11 19:24:48 +04:00
2013-11-13 17:56:29 +04:00
# ifdef __linux__
2013-06-12 14:08:56 +04:00
2011-11-11 19:24:48 +04:00
# include <dirent.h>
# define MPATH_PREFIX "mpath-"
2019-03-13 15:02:09 +03:00
struct mpath_priv {
struct dm_pool * mem ;
struct dev_filter f ;
struct dev_types * dt ;
struct dm_hash_table * hash ;
} ;
2014-08-18 19:34:01 +04:00
static const char * _get_sysfs_name ( struct device * dev )
2011-11-11 19:24:48 +04:00
{
const char * name ;
2012-03-01 14:30:48 +04:00
if ( ! ( name = strrchr ( dev_name ( dev ) , ' / ' ) ) ) {
log_error ( " Cannot find '/' in device name. " ) ;
2011-11-11 19:24:48 +04:00
return NULL ;
2012-03-01 14:30:48 +04:00
}
2011-11-11 19:24:48 +04:00
name + + ;
2012-03-01 14:30:48 +04:00
if ( ! * name ) {
log_error ( " Device name is not valid. " ) ;
2011-11-11 19:24:48 +04:00
return NULL ;
2012-03-01 14:30:48 +04:00
}
2011-11-11 19:24:48 +04:00
return name ;
}
2014-08-18 19:34:01 +04:00
static const char * _get_sysfs_name_by_devt ( const char * sysfs_dir , dev_t devno ,
2014-08-08 12:49:19 +04:00
char * buf , size_t buf_size )
{
const char * name ;
char path [ PATH_MAX ] ;
int size ;
if ( dm_snprintf ( path , sizeof ( path ) , " %s/dev/block/%d:%d " , sysfs_dir ,
( int ) MAJOR ( devno ) , ( int ) MINOR ( devno ) ) < 0 ) {
log_error ( " Sysfs path string is too long. " ) ;
return NULL ;
}
if ( ( size = readlink ( path , buf , buf_size - 1 ) ) < 0 ) {
log_sys_error ( " readlink " , path ) ;
return NULL ;
}
buf [ size ] = ' \0 ' ;
if ( ! ( name = strrchr ( buf , ' / ' ) ) ) {
log_error ( " Cannot find device name in sysfs path. " ) ;
return NULL ;
}
name + + ;
return name ;
}
2014-08-18 19:34:01 +04:00
static int _get_sysfs_string ( const char * path , char * buffer , int max_size )
2011-11-11 19:24:48 +04:00
{
FILE * fp ;
int r = 0 ;
2012-03-01 14:30:48 +04:00
if ( ! ( fp = fopen ( path , " r " ) ) ) {
log_sys_error ( " fopen " , path ) ;
return 0 ;
}
2011-11-11 19:24:48 +04:00
if ( ! fgets ( buffer , max_size , fp ) )
2012-03-01 14:30:48 +04:00
log_sys_error ( " fgets " , path ) ;
2011-11-11 19:24:48 +04:00
else
r = 1 ;
if ( fclose ( fp ) )
2012-03-01 14:30:48 +04:00
log_sys_error ( " fclose " , path ) ;
2011-11-11 19:24:48 +04:00
return r ;
}
2014-08-18 19:34:01 +04:00
static int _get_sysfs_get_major_minor ( const char * sysfs_dir , const char * kname , int * major , int * minor )
2011-11-11 19:24:48 +04:00
{
char path [ PATH_MAX ] , buffer [ 64 ] ;
2012-03-01 14:30:48 +04:00
if ( dm_snprintf ( path , sizeof ( path ) , " %s/block/%s/dev " , sysfs_dir , kname ) < 0 ) {
log_error ( " Sysfs path string is too long. " ) ;
return 0 ;
}
2011-11-11 19:24:48 +04:00
2014-08-18 19:34:01 +04:00
if ( ! _get_sysfs_string ( path , buffer , sizeof ( buffer ) ) )
2012-03-01 14:30:48 +04:00
return_0 ;
2011-11-11 19:24:48 +04:00
2012-03-01 14:30:48 +04:00
if ( sscanf ( buffer , " %d:%d " , major , minor ) ! = 2 ) {
log_error ( " Failed to parse major minor from %s " , buffer ) ;
2011-11-11 19:24:48 +04:00
return 0 ;
2012-03-01 14:30:48 +04:00
}
2011-11-11 19:24:48 +04:00
return 1 ;
}
2014-08-18 19:34:01 +04:00
static int _get_parent_mpath ( const char * dir , char * name , int max_size )
2011-11-11 19:24:48 +04:00
{
struct dirent * d ;
DIR * dr ;
int r = 0 ;
2012-03-01 14:30:48 +04:00
if ( ! ( dr = opendir ( dir ) ) ) {
log_sys_error ( " opendir " , dir ) ;
return 0 ;
}
2011-11-11 19:24:48 +04:00
* name = ' \0 ' ;
while ( ( d = readdir ( dr ) ) ) {
if ( ! strcmp ( d - > d_name , " . " ) | | ! strcmp ( d - > d_name , " .. " ) )
continue ;
/* There should be only one holder if it is multipath */
if ( * name ) {
r = 0 ;
break ;
}
strncpy ( name , d - > d_name , max_size ) ;
r = 1 ;
}
if ( closedir ( dr ) )
2012-03-01 14:30:48 +04:00
log_sys_error ( " closedir " , dir ) ;
2011-11-11 19:24:48 +04:00
return r ;
}
2014-09-03 17:48:37 +04:00
# ifdef UDEV_SYNC_SUPPORT
static int _udev_dev_is_mpath ( struct device * dev )
{
const char * value ;
struct dev_ext * ext ;
if ( ! ( ext = dev_ext_get ( dev ) ) )
return_0 ;
2015-01-29 18:44:34 +03:00
value = udev_device_get_property_value ( ( struct udev_device * ) ext - > handle , DEV_EXT_UDEV_BLKID_TYPE ) ;
if ( value & & ! strcmp ( value , DEV_EXT_UDEV_BLKID_TYPE_MPATH ) )
2014-09-03 17:48:37 +04:00
return 1 ;
2015-01-29 18:44:34 +03:00
value = udev_device_get_property_value ( ( struct udev_device * ) ext - > handle , DEV_EXT_UDEV_MPATH_DEVICE_PATH ) ;
2014-09-03 17:48:37 +04:00
if ( value & & ! strcmp ( value , " 1 " ) )
return 1 ;
return 0 ;
}
# else
static int _udev_dev_is_mpath ( struct device * dev )
{
return 0 ;
}
# endif
static int _native_dev_is_mpath ( struct dev_filter * f , struct device * dev )
2011-11-11 19:24:48 +04:00
{
2019-03-13 15:02:09 +03:00
struct mpath_priv * mp = ( struct mpath_priv * ) f - > private ;
struct dev_types * dt = mp - > dt ;
2014-08-08 12:49:19 +04:00
const char * part_name , * name ;
2011-11-11 19:24:48 +04:00
struct stat info ;
2014-08-19 13:35:18 +04:00
char path [ PATH_MAX ] , parent_name [ PATH_MAX ] ;
2013-06-12 13:38:48 +04:00
const char * sysfs_dir = dm_sysfs_dir ( ) ;
2013-06-12 14:08:56 +04:00
int major = MAJOR ( dev - > dev ) ;
int minor = MINOR ( dev - > dev ) ;
2013-06-12 14:20:10 +04:00
dev_t primary_dev ;
2019-03-13 15:02:09 +03:00
long look ;
2011-11-11 19:24:48 +04:00
/* Limit this filter only to SCSI devices */
2013-06-12 14:08:56 +04:00
if ( ! major_is_scsi_device ( dt , MAJOR ( dev - > dev ) ) )
2011-11-11 19:24:48 +04:00
return 0 ;
2013-06-12 14:20:10 +04:00
switch ( dev_get_primary_dev ( dt , dev , & primary_dev ) ) {
2014-08-19 00:04:39 +04:00
case 2 : /* The dev is partition. */
part_name = dev_name ( dev ) ; /* name of original dev for log_debug msg */
2014-08-18 19:34:01 +04:00
if ( ! ( name = _get_sysfs_name_by_devt ( sysfs_dir , primary_dev , parent_name , sizeof ( parent_name ) ) ) )
2014-08-19 00:04:39 +04:00
return_0 ;
log_debug_devs ( " %s: Device is a partition, using primary "
" device %s for mpath component detection " ,
part_name , name ) ;
break ;
case 1 : /* The dev is already a primary dev. Just continue with the dev. */
2014-08-18 19:34:01 +04:00
if ( ! ( name = _get_sysfs_name ( dev ) ) )
2014-08-19 00:04:39 +04:00
return_0 ;
break ;
default : /* 0, error. */
2019-03-13 14:58:13 +03:00
log_warn ( " Failed to get primary device for %d:%d. " , major , minor ) ;
2014-08-19 00:04:39 +04:00
return 0 ;
2013-06-12 14:20:10 +04:00
}
2014-08-19 13:35:18 +04:00
if ( dm_snprintf ( path , sizeof ( path ) , " %s/block/%s/holders " , sysfs_dir , name ) < 0 ) {
2019-03-13 14:58:13 +03:00
log_warn ( " Sysfs path to check mpath is too long. " ) ;
2012-03-01 14:30:48 +04:00
return 0 ;
}
2011-11-11 19:24:48 +04:00
/* also will filter out partitions */
2012-03-01 14:30:48 +04:00
if ( stat ( path , & info ) )
2011-11-11 19:24:48 +04:00
return 0 ;
2012-03-01 14:30:48 +04:00
if ( ! S_ISDIR ( info . st_mode ) ) {
2019-03-13 14:58:13 +03:00
log_warn ( " Path %s is not a directory. " , path ) ;
2012-03-01 14:30:48 +04:00
return 0 ;
}
2014-08-18 19:34:01 +04:00
if ( ! _get_parent_mpath ( path , parent_name , sizeof ( parent_name ) ) )
2011-11-11 19:24:48 +04:00
return 0 ;
2014-08-18 19:34:01 +04:00
if ( ! _get_sysfs_get_major_minor ( sysfs_dir , parent_name , & major , & minor ) )
2012-03-01 14:30:48 +04:00
return_0 ;
2011-11-11 19:24:48 +04:00
2013-08-21 16:07:01 +04:00
if ( major ! = dt - > device_mapper_major )
2011-11-11 19:24:48 +04:00
return 0 ;
2019-03-13 15:02:09 +03:00
/* Avoid repeated detection of multipath device and use first checked result */
look = ( long ) dm_hash_lookup_binary ( mp - > hash , & minor , sizeof ( minor ) ) ;
if ( look > 0 ) {
log_debug_devs ( " %s(%u:%u): already checked as %sbeing mpath. " ,
parent_name , major , minor , ( look > 1 ) ? " " : " not " ) ;
2019-04-03 14:06:00 +03:00
return ( look > 1 ) ? 1 : 0 ;
2019-03-13 15:02:09 +03:00
}
if ( lvm_dm_prefix_check ( major , minor , MPATH_PREFIX ) ) {
( void ) dm_hash_insert_binary ( mp - > hash , & minor , sizeof ( minor ) , ( void * ) 2 ) ;
return 1 ;
}
( void ) dm_hash_insert_binary ( mp - > hash , & minor , sizeof ( minor ) , ( void * ) 1 ) ;
return 0 ;
2011-11-11 19:24:48 +04:00
}
2014-09-03 17:48:37 +04:00
static int _dev_is_mpath ( struct dev_filter * f , struct device * dev )
{
if ( dev - > ext . src = = DEV_EXT_NONE )
return _native_dev_is_mpath ( f , dev ) ;
if ( dev - > ext . src = = DEV_EXT_UDEV )
return _udev_dev_is_mpath ( dev ) ;
log_error ( INTERNAL_ERROR " Missing hook for mpath recognition "
" using external device info source %s " , dev_ext_name ( dev ) ) ;
return 0 ;
}
2015-09-03 15:19:48 +03:00
# define MSG_SKIPPING "%s: Skipping mpath component device"
2018-12-07 23:35:22 +03:00
static int _ignore_mpath ( struct cmd_context * cmd , struct dev_filter * f , struct device * dev , const char * use_filter_name )
2011-11-11 19:24:48 +04:00
{
2014-08-18 19:34:01 +04:00
if ( _dev_is_mpath ( f , dev ) = = 1 ) {
2015-09-03 15:19:48 +03:00
if ( dev - > ext . src = = DEV_EXT_NONE )
log_debug_devs ( MSG_SKIPPING , dev_name ( dev ) ) ;
else
log_debug_devs ( MSG_SKIPPING " [%s:%p] " , dev_name ( dev ) ,
dev_ext_name ( dev ) , dev - > ext . handle ) ;
2011-11-11 19:24:48 +04:00
return 0 ;
}
return 1 ;
}
static void _destroy ( struct dev_filter * f )
{
2019-03-13 15:02:09 +03:00
struct mpath_priv * mp = ( struct mpath_priv * ) f - > private ;
2011-11-11 19:24:48 +04:00
if ( f - > use_count )
log_error ( INTERNAL_ERROR " Destroying mpath filter while in use %u times. " , f - > use_count ) ;
2019-03-13 15:02:09 +03:00
dm_hash_destroy ( mp - > hash ) ;
dm_pool_destroy ( mp - > mem ) ;
2011-11-11 19:24:48 +04:00
}
2013-06-12 14:08:56 +04:00
struct dev_filter * mpath_filter_create ( struct dev_types * dt )
2011-11-11 19:24:48 +04:00
{
2013-06-12 13:38:48 +04:00
const char * sysfs_dir = dm_sysfs_dir ( ) ;
2019-03-13 15:02:09 +03:00
struct dm_pool * mem ;
struct mpath_priv * mp ;
struct dm_hash_table * hash ;
2011-11-11 19:24:48 +04:00
if ( ! * sysfs_dir ) {
log_verbose ( " No proc filesystem found: skipping multipath filter " ) ;
return NULL ;
}
2019-03-13 15:02:09 +03:00
if ( ! ( hash = dm_hash_create ( 128 ) ) ) {
log_error ( " mpath hash table creation failed. " ) ;
2011-11-11 19:24:48 +04:00
return NULL ;
}
2019-03-13 15:02:09 +03:00
if ( ! ( mem = dm_pool_create ( " mpath " , 256 ) ) ) {
log_error ( " mpath pool creation failed. " ) ;
dm_hash_destroy ( hash ) ;
return NULL ;
}
if ( ! ( mp = dm_pool_zalloc ( mem , sizeof ( * mp ) ) ) ) {
log_error ( " mpath filter allocation failed. " ) ;
goto bad ;
}
if ( ! ( mp = dm_pool_zalloc ( mem , sizeof ( * mp ) ) ) ) {
log_error ( " mpath filter allocation failed. " ) ;
goto bad ;
}
mp - > f . passes_filter = _ignore_mpath ;
mp - > f . destroy = _destroy ;
mp - > f . use_count = 0 ;
mp - > f . private = mp ;
mp - > f . name = " mpath " ;
mp - > mem = mem ;
mp - > dt = dt ;
mp - > hash = hash ;
2011-11-11 19:24:48 +04:00
2013-08-14 02:26:58 +04:00
log_debug_devs ( " mpath filter initialised. " ) ;
2019-03-13 15:02:09 +03:00
return & mp - > f ;
bad :
dm_pool_destroy ( mem ) ;
dm_hash_destroy ( hash ) ;
return NULL ;
2011-11-11 19:24:48 +04:00
}
# else
2013-06-17 17:17:15 +04:00
struct dev_filter * mpath_filter_create ( struct dev_types * dt )
2011-11-11 19:24:48 +04:00
{
return NULL ;
}
# endif