2017-03-16 22:18:50 -08:00
// SPDX-License-Identifier: GPL-2.0
# include "bcachefs.h"
# include "disk_groups.h"
# include "super-io.h"
# include <linux/sort.h>
static int group_cmp ( const void * _l , const void * _r )
{
const struct bch_disk_group * l = _l ;
const struct bch_disk_group * r = _r ;
return ( ( BCH_GROUP_DELETED ( l ) > BCH_GROUP_DELETED ( r ) ) -
( BCH_GROUP_DELETED ( l ) < BCH_GROUP_DELETED ( r ) ) ) ? :
( ( BCH_GROUP_PARENT ( l ) > BCH_GROUP_PARENT ( r ) ) -
( BCH_GROUP_PARENT ( l ) < BCH_GROUP_PARENT ( r ) ) ) ? :
strncmp ( l - > label , r - > label , sizeof ( l - > label ) ) ;
}
static const char * bch2_sb_disk_groups_validate ( struct bch_sb * sb ,
struct bch_sb_field * f )
{
struct bch_sb_field_disk_groups * groups =
field_to_type ( f , disk_groups ) ;
struct bch_disk_group * g , * sorted = NULL ;
struct bch_sb_field_members * mi ;
struct bch_member * m ;
unsigned i , nr_groups , len ;
const char * err = NULL ;
mi = bch2_sb_get_members ( sb ) ;
groups = bch2_sb_get_disk_groups ( sb ) ;
nr_groups = disk_groups_nr ( groups ) ;
for ( m = mi - > members ;
m < mi - > members + sb - > nr_devices ;
m + + ) {
unsigned g ;
if ( ! BCH_MEMBER_GROUP ( m ) )
continue ;
g = BCH_MEMBER_GROUP ( m ) - 1 ;
if ( g > = nr_groups | |
BCH_GROUP_DELETED ( & groups - > entries [ g ] ) )
return " disk has invalid group " ;
}
if ( ! nr_groups )
return NULL ;
for ( g = groups - > entries ;
g < groups - > entries + nr_groups ;
g + + ) {
if ( BCH_GROUP_DELETED ( g ) )
continue ;
len = strnlen ( g - > label , sizeof ( g - > label ) ) ;
if ( ! len ) {
err = " group with empty label " ;
goto err ;
}
}
sorted = kmalloc_array ( nr_groups , sizeof ( * sorted ) , GFP_KERNEL ) ;
if ( ! sorted )
return " cannot allocate memory " ;
memcpy ( sorted , groups - > entries , nr_groups * sizeof ( * sorted ) ) ;
sort ( sorted , nr_groups , sizeof ( * sorted ) , group_cmp , NULL ) ;
for ( i = 0 ; i + 1 < nr_groups ; i + + )
if ( ! BCH_GROUP_DELETED ( sorted + i ) & &
! group_cmp ( sorted + i , sorted + i + 1 ) ) {
err = " duplicate groups " ;
goto err ;
}
err = NULL ;
err :
kfree ( sorted ) ;
return err ;
}
2018-11-09 01:24:07 -05:00
static void bch2_sb_disk_groups_to_text ( struct printbuf * out ,
2017-03-16 22:18:50 -08:00
struct bch_sb * sb ,
struct bch_sb_field * f )
{
struct bch_sb_field_disk_groups * groups =
field_to_type ( f , disk_groups ) ;
struct bch_disk_group * g ;
unsigned nr_groups = disk_groups_nr ( groups ) ;
for ( g = groups - > entries ;
g < groups - > entries + nr_groups ;
g + + ) {
if ( g ! = groups - > entries )
2018-11-09 01:24:07 -05:00
pr_buf ( out , " " ) ;
2017-03-16 22:18:50 -08:00
if ( BCH_GROUP_DELETED ( g ) )
2018-11-09 01:24:07 -05:00
pr_buf ( out , " [deleted] " ) ;
2017-03-16 22:18:50 -08:00
else
2018-11-09 01:24:07 -05:00
pr_buf ( out , " [parent %llu name %s] " ,
BCH_GROUP_PARENT ( g ) , g - > label ) ;
2017-03-16 22:18:50 -08:00
}
}
const struct bch_sb_field_ops bch_sb_field_ops_disk_groups = {
. validate = bch2_sb_disk_groups_validate ,
. to_text = bch2_sb_disk_groups_to_text
} ;
int bch2_sb_disk_groups_to_cpu ( struct bch_fs * c )
{
struct bch_sb_field_members * mi ;
struct bch_sb_field_disk_groups * groups ;
struct bch_disk_groups_cpu * cpu_g , * old_g ;
unsigned i , g , nr_groups ;
lockdep_assert_held ( & c - > sb_lock ) ;
mi = bch2_sb_get_members ( c - > disk_sb . sb ) ;
groups = bch2_sb_get_disk_groups ( c - > disk_sb . sb ) ;
nr_groups = disk_groups_nr ( groups ) ;
if ( ! groups )
return 0 ;
cpu_g = kzalloc ( sizeof ( * cpu_g ) +
sizeof ( cpu_g - > entries [ 0 ] ) * nr_groups , GFP_KERNEL ) ;
if ( ! cpu_g )
return - ENOMEM ;
cpu_g - > nr = nr_groups ;
for ( i = 0 ; i < nr_groups ; i + + ) {
struct bch_disk_group * src = & groups - > entries [ i ] ;
struct bch_disk_group_cpu * dst = & cpu_g - > entries [ i ] ;
dst - > deleted = BCH_GROUP_DELETED ( src ) ;
dst - > parent = BCH_GROUP_PARENT ( src ) ;
}
for ( i = 0 ; i < c - > disk_sb . sb - > nr_devices ; i + + ) {
struct bch_member * m = mi - > members + i ;
struct bch_disk_group_cpu * dst =
& cpu_g - > entries [ BCH_MEMBER_GROUP ( m ) ] ;
if ( ! bch2_member_exists ( m ) )
continue ;
g = BCH_MEMBER_GROUP ( m ) ;
while ( g ) {
dst = & cpu_g - > entries [ g - 1 ] ;
__set_bit ( i , dst - > devs . d ) ;
g = dst - > parent ;
}
}
old_g = rcu_dereference_protected ( c - > disk_groups ,
lockdep_is_held ( & c - > sb_lock ) ) ;
rcu_assign_pointer ( c - > disk_groups , cpu_g ) ;
if ( old_g )
kfree_rcu ( old_g , rcu ) ;
return 0 ;
}
const struct bch_devs_mask * bch2_target_to_mask ( struct bch_fs * c , unsigned target )
{
struct target t = target_decode ( target ) ;
switch ( t . type ) {
case TARGET_NULL :
return NULL ;
case TARGET_DEV : {
struct bch_dev * ca = t . dev < c - > sb . nr_devices
? rcu_dereference ( c - > devs [ t . dev ] )
: NULL ;
return ca ? & ca - > self : NULL ;
}
case TARGET_GROUP : {
struct bch_disk_groups_cpu * g = rcu_dereference ( c - > disk_groups ) ;
return t . group < g - > nr & & ! g - > entries [ t . group ] . deleted
? & g - > entries [ t . group ] . devs
: NULL ;
}
default :
BUG ( ) ;
}
}
bool bch2_dev_in_target ( struct bch_fs * c , unsigned dev , unsigned target )
{
struct target t = target_decode ( target ) ;
switch ( t . type ) {
case TARGET_NULL :
return false ;
case TARGET_DEV :
return dev = = t . dev ;
case TARGET_GROUP : {
struct bch_disk_groups_cpu * g ;
const struct bch_devs_mask * m ;
bool ret ;
rcu_read_lock ( ) ;
g = rcu_dereference ( c - > disk_groups ) ;
m = t . group < g - > nr & & ! g - > entries [ t . group ] . deleted
? & g - > entries [ t . group ] . devs
: NULL ;
ret = m ? test_bit ( dev , m - > d ) : false ;
rcu_read_unlock ( ) ;
return ret ;
}
default :
BUG ( ) ;
}
}
static int __bch2_disk_group_find ( struct bch_sb_field_disk_groups * groups ,
unsigned parent ,
const char * name , unsigned namelen )
{
unsigned i , nr_groups = disk_groups_nr ( groups ) ;
if ( ! namelen | | namelen > BCH_SB_LABEL_SIZE )
return - EINVAL ;
for ( i = 0 ; i < nr_groups ; i + + ) {
struct bch_disk_group * g = groups - > entries + i ;
if ( BCH_GROUP_DELETED ( g ) )
continue ;
if ( ! BCH_GROUP_DELETED ( g ) & &
BCH_GROUP_PARENT ( g ) = = parent & &
strnlen ( g - > label , sizeof ( g - > label ) ) = = namelen & &
! memcmp ( name , g - > label , namelen ) )
return i ;
}
return - 1 ;
}
static int __bch2_disk_group_add ( struct bch_sb_handle * sb , unsigned parent ,
const char * name , unsigned namelen )
{
struct bch_sb_field_disk_groups * groups =
bch2_sb_get_disk_groups ( sb - > sb ) ;
unsigned i , nr_groups = disk_groups_nr ( groups ) ;
struct bch_disk_group * g ;
if ( ! namelen | | namelen > BCH_SB_LABEL_SIZE )
return - EINVAL ;
for ( i = 0 ;
i < nr_groups & & ! BCH_GROUP_DELETED ( & groups - > entries [ i ] ) ;
i + + )
;
if ( i = = nr_groups ) {
unsigned u64s =
( sizeof ( struct bch_sb_field_disk_groups ) +
sizeof ( struct bch_disk_group ) * ( nr_groups + 1 ) ) /
sizeof ( u64 ) ;
groups = bch2_sb_resize_disk_groups ( sb , u64s ) ;
if ( ! groups )
return - ENOSPC ;
nr_groups = disk_groups_nr ( groups ) ;
}
BUG_ON ( i > = nr_groups ) ;
g = & groups - > entries [ i ] ;
memcpy ( g - > label , name , namelen ) ;
if ( namelen < sizeof ( g - > label ) )
g - > label [ namelen ] = ' \0 ' ;
SET_BCH_GROUP_DELETED ( g , 0 ) ;
SET_BCH_GROUP_PARENT ( g , parent ) ;
SET_BCH_GROUP_DATA_ALLOWED ( g , ~ 0 ) ;
return i ;
}
int bch2_disk_path_find ( struct bch_sb_handle * sb , const char * name )
{
struct bch_sb_field_disk_groups * groups =
bch2_sb_get_disk_groups ( sb - > sb ) ;
int v = - 1 ;
do {
const char * next = strchrnul ( name , ' . ' ) ;
unsigned len = next - name ;
if ( * next = = ' . ' )
next + + ;
v = __bch2_disk_group_find ( groups , v + 1 , name , len ) ;
name = next ;
} while ( * name & & v > = 0 ) ;
return v ;
}
int bch2_disk_path_find_or_create ( struct bch_sb_handle * sb , const char * name )
{
struct bch_sb_field_disk_groups * groups ;
unsigned parent = 0 ;
int v = - 1 ;
do {
const char * next = strchrnul ( name , ' . ' ) ;
unsigned len = next - name ;
if ( * next = = ' . ' )
next + + ;
groups = bch2_sb_get_disk_groups ( sb - > sb ) ;
v = __bch2_disk_group_find ( groups , parent , name , len ) ;
if ( v < 0 )
v = __bch2_disk_group_add ( sb , parent , name , len ) ;
if ( v < 0 )
return v ;
parent = v + 1 ;
name = next ;
} while ( * name & & v > = 0 ) ;
return v ;
}
2018-11-09 01:24:07 -05:00
void bch2_disk_path_to_text ( struct printbuf * out ,
struct bch_sb_handle * sb ,
unsigned v )
2017-03-16 22:18:50 -08:00
{
struct bch_sb_field_disk_groups * groups =
bch2_sb_get_disk_groups ( sb - > sb ) ;
struct bch_disk_group * g ;
unsigned nr = 0 ;
u16 path [ 32 ] ;
while ( 1 ) {
if ( nr = = ARRAY_SIZE ( path ) )
goto inval ;
if ( v > = disk_groups_nr ( groups ) )
goto inval ;
g = groups - > entries + v ;
if ( BCH_GROUP_DELETED ( g ) )
goto inval ;
path [ nr + + ] = v ;
if ( ! BCH_GROUP_PARENT ( g ) )
break ;
v = BCH_GROUP_PARENT ( g ) - 1 ;
}
while ( nr ) {
v = path [ - - nr ] ;
g = groups - > entries + v ;
2018-11-09 01:24:07 -05:00
bch_scnmemcpy ( out , g - > label ,
strnlen ( g - > label , sizeof ( g - > label ) ) ) ;
2017-03-16 22:18:50 -08:00
if ( nr )
2018-11-09 01:24:07 -05:00
pr_buf ( out , " . " ) ;
2017-03-16 22:18:50 -08:00
}
2018-11-09 01:24:07 -05:00
return ;
2017-03-16 22:18:50 -08:00
inval :
2018-11-09 01:24:07 -05:00
pr_buf ( out , " invalid group %u " , v ) ;
2017-03-16 22:18:50 -08:00
}
int bch2_dev_group_set ( struct bch_fs * c , struct bch_dev * ca , const char * name )
{
struct bch_member * mi ;
int v = - 1 ;
mutex_lock ( & c - > sb_lock ) ;
if ( ! strlen ( name ) | | ! strcmp ( name , " none " ) )
goto write_sb ;
v = bch2_disk_path_find_or_create ( & c - > disk_sb , name ) ;
if ( v < 0 ) {
mutex_unlock ( & c - > sb_lock ) ;
return v ;
}
write_sb :
mi = & bch2_sb_get_members ( c - > disk_sb . sb ) - > members [ ca - > dev_idx ] ;
SET_BCH_MEMBER_GROUP ( mi , v + 1 ) ;
bch2_write_super ( c ) ;
mutex_unlock ( & c - > sb_lock ) ;
return 0 ;
}
int bch2_opt_target_parse ( struct bch_fs * c , const char * buf , u64 * v )
{
struct bch_dev * ca ;
int g ;
if ( ! strlen ( buf ) | | ! strcmp ( buf , " none " ) ) {
* v = 0 ;
return 0 ;
}
/* Is it a device? */
ca = bch2_dev_lookup ( c , buf ) ;
if ( ! IS_ERR ( ca ) ) {
* v = dev_to_target ( ca - > dev_idx ) ;
percpu_ref_put ( & ca - > ref ) ;
return 0 ;
}
mutex_lock ( & c - > sb_lock ) ;
g = bch2_disk_path_find ( & c - > disk_sb , buf ) ;
mutex_unlock ( & c - > sb_lock ) ;
if ( g > = 0 ) {
* v = group_to_target ( g ) ;
return 0 ;
}
return - EINVAL ;
}
2018-11-09 01:24:07 -05:00
void bch2_opt_target_to_text ( struct printbuf * out , struct bch_fs * c , u64 v )
2017-03-16 22:18:50 -08:00
{
struct target t = target_decode ( v ) ;
switch ( t . type ) {
case TARGET_NULL :
2018-11-09 01:24:07 -05:00
pr_buf ( out , " none " ) ;
break ;
2017-03-16 22:18:50 -08:00
case TARGET_DEV : {
struct bch_dev * ca ;
rcu_read_lock ( ) ;
ca = t . dev < c - > sb . nr_devices
? rcu_dereference ( c - > devs [ t . dev ] )
: NULL ;
if ( ca & & percpu_ref_tryget ( & ca - > io_ref ) ) {
2018-11-09 01:24:07 -05:00
pr_buf ( out , " /dev/%pg " , ca - > disk_sb . bdev ) ;
2017-03-16 22:18:50 -08:00
percpu_ref_put ( & ca - > io_ref ) ;
} else if ( ca ) {
2018-11-09 01:24:07 -05:00
pr_buf ( out , " offline device %u " , t . dev ) ;
2017-03-16 22:18:50 -08:00
} else {
2018-11-09 01:24:07 -05:00
pr_buf ( out , " invalid device %u " , t . dev ) ;
2017-03-16 22:18:50 -08:00
}
rcu_read_unlock ( ) ;
break ;
}
case TARGET_GROUP :
mutex_lock ( & c - > sb_lock ) ;
2018-11-09 01:24:07 -05:00
bch2_disk_path_to_text ( out , & c - > disk_sb , t . group ) ;
2017-03-16 22:18:50 -08:00
mutex_unlock ( & c - > sb_lock ) ;
break ;
default :
BUG ( ) ;
}
}