2019-05-29 17:18:00 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2016-03-23 00:24:24 +03:00
/* -*- mode: c; c-basic-offset: 8; -*-
* vim : noexpandtab sw = 8 ts = 8 sts = 0 :
*
* filecheck . c
*
* Code which implements online file check .
*
* Copyright ( C ) 2016 SuSE . All rights reserved .
*/
# include <linux/list.h>
# include <linux/spinlock.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/kmod.h>
# include <linux/fs.h>
# include <linux/kobject.h>
# include <linux/sysfs.h>
# include <linux/sysctl.h>
# include <cluster/masklog.h>
# include "ocfs2.h"
# include "ocfs2_fs.h"
# include "stackglue.h"
# include "inode.h"
# include "filecheck.h"
/* File check error strings,
* must correspond with error number in header file .
*/
static const char * const ocfs2_filecheck_errs [ ] = {
" SUCCESS " ,
" FAILED " ,
" INPROGRESS " ,
" READONLY " ,
" INJBD " ,
" INVALIDINO " ,
" BLOCKECC " ,
" BLOCKNO " ,
" VALIDFLAG " ,
" GENERATION " ,
" UNSUPPORTED "
} ;
struct ocfs2_filecheck_entry {
struct list_head fe_list ;
unsigned long fe_ino ;
unsigned int fe_type ;
unsigned int fe_done : 1 ;
unsigned int fe_status : 31 ;
} ;
struct ocfs2_filecheck_args {
unsigned int fa_type ;
union {
unsigned long fa_ino ;
unsigned int fa_len ;
} ;
} ;
static const char *
ocfs2_filecheck_error ( int errno )
{
if ( ! errno )
return ocfs2_filecheck_errs [ errno ] ;
BUG_ON ( errno < OCFS2_FILECHECK_ERR_START | |
errno > OCFS2_FILECHECK_ERR_END ) ;
return ocfs2_filecheck_errs [ errno - OCFS2_FILECHECK_ERR_START + 1 ] ;
}
2018-04-06 02:19:29 +03:00
static ssize_t ocfs2_filecheck_attr_show ( struct kobject * kobj ,
struct kobj_attribute * attr ,
char * buf ) ;
static ssize_t ocfs2_filecheck_attr_store ( struct kobject * kobj ,
struct kobj_attribute * attr ,
const char * buf , size_t count ) ;
static struct kobj_attribute ocfs2_filecheck_attr_chk =
2016-03-23 00:24:24 +03:00
__ATTR ( check , S_IRUSR | S_IWUSR ,
2018-04-06 02:19:29 +03:00
ocfs2_filecheck_attr_show ,
ocfs2_filecheck_attr_store ) ;
static struct kobj_attribute ocfs2_filecheck_attr_fix =
2016-03-23 00:24:24 +03:00
__ATTR ( fix , S_IRUSR | S_IWUSR ,
2018-04-06 02:19:29 +03:00
ocfs2_filecheck_attr_show ,
ocfs2_filecheck_attr_store ) ;
static struct kobj_attribute ocfs2_filecheck_attr_set =
2016-03-23 00:24:24 +03:00
__ATTR ( set , S_IRUSR | S_IWUSR ,
2018-04-06 02:19:29 +03:00
ocfs2_filecheck_attr_show ,
ocfs2_filecheck_attr_store ) ;
static struct attribute * ocfs2_filecheck_attrs [ ] = {
& ocfs2_filecheck_attr_chk . attr ,
& ocfs2_filecheck_attr_fix . attr ,
& ocfs2_filecheck_attr_set . attr ,
NULL
} ;
static void ocfs2_filecheck_release ( struct kobject * kobj )
{
struct ocfs2_filecheck_sysfs_entry * entry = container_of ( kobj ,
struct ocfs2_filecheck_sysfs_entry , fs_kobj ) ;
complete ( & entry - > fs_kobj_unregister ) ;
}
static ssize_t
ocfs2_filecheck_show ( struct kobject * kobj , struct attribute * attr , char * buf )
{
ssize_t ret = - EIO ;
struct kobj_attribute * kattr = container_of ( attr ,
struct kobj_attribute , attr ) ;
kobject_get ( kobj ) ;
if ( kattr - > show )
ret = kattr - > show ( kobj , kattr , buf ) ;
kobject_put ( kobj ) ;
return ret ;
}
static ssize_t
ocfs2_filecheck_store ( struct kobject * kobj , struct attribute * attr ,
const char * buf , size_t count )
{
ssize_t ret = - EIO ;
struct kobj_attribute * kattr = container_of ( attr ,
struct kobj_attribute , attr ) ;
kobject_get ( kobj ) ;
if ( kattr - > store )
ret = kattr - > store ( kobj , kattr , buf , count ) ;
kobject_put ( kobj ) ;
return ret ;
}
static const struct sysfs_ops ocfs2_filecheck_ops = {
. show = ocfs2_filecheck_show ,
. store = ocfs2_filecheck_store ,
} ;
static struct kobj_type ocfs2_ktype_filecheck = {
. default_attrs = ocfs2_filecheck_attrs ,
. sysfs_ops = & ocfs2_filecheck_ops ,
. release = ocfs2_filecheck_release ,
} ;
2016-03-23 00:24:24 +03:00
static void
ocfs2_filecheck_sysfs_free ( struct ocfs2_filecheck_sysfs_entry * entry )
{
struct ocfs2_filecheck_entry * p ;
spin_lock ( & entry - > fs_fcheck - > fc_lock ) ;
while ( ! list_empty ( & entry - > fs_fcheck - > fc_head ) ) {
p = list_first_entry ( & entry - > fs_fcheck - > fc_head ,
struct ocfs2_filecheck_entry , fe_list ) ;
list_del ( & p - > fe_list ) ;
BUG_ON ( ! p - > fe_done ) ; /* To free a undone file check entry */
kfree ( p ) ;
}
spin_unlock ( & entry - > fs_fcheck - > fc_lock ) ;
kfree ( entry - > fs_fcheck ) ;
2018-04-06 02:19:29 +03:00
entry - > fs_fcheck = NULL ;
2016-03-23 00:24:24 +03:00
}
2018-04-06 02:19:29 +03:00
int ocfs2_filecheck_create_sysfs ( struct ocfs2_super * osb )
2016-03-23 00:24:24 +03:00
{
2018-04-06 02:19:29 +03:00
int ret ;
struct ocfs2_filecheck * fcheck ;
struct ocfs2_filecheck_sysfs_entry * entry = & osb - > osb_fc_ent ;
2016-03-23 00:24:24 +03:00
fcheck = kmalloc ( sizeof ( struct ocfs2_filecheck ) , GFP_NOFS ) ;
2018-04-06 02:19:29 +03:00
if ( ! fcheck )
return - ENOMEM ;
2016-03-23 00:24:24 +03:00
2018-04-06 02:19:29 +03:00
INIT_LIST_HEAD ( & fcheck - > fc_head ) ;
spin_lock_init ( & fcheck - > fc_lock ) ;
fcheck - > fc_max = OCFS2_FILECHECK_MINSIZE ;
fcheck - > fc_size = 0 ;
fcheck - > fc_done = 0 ;
entry - > fs_kobj . kset = osb - > osb_dev_kset ;
init_completion ( & entry - > fs_kobj_unregister ) ;
ret = kobject_init_and_add ( & entry - > fs_kobj , & ocfs2_ktype_filecheck ,
NULL , " filecheck " ) ;
if ( ret ) {
2019-06-01 08:30:29 +03:00
kobject_put ( & entry - > fs_kobj ) ;
2018-04-06 02:19:29 +03:00
kfree ( fcheck ) ;
return ret ;
2016-03-23 00:24:24 +03:00
}
2018-04-06 02:19:29 +03:00
entry - > fs_fcheck = fcheck ;
2016-03-23 00:24:24 +03:00
return 0 ;
}
2018-04-06 02:19:29 +03:00
void ocfs2_filecheck_remove_sysfs ( struct ocfs2_super * osb )
2016-03-23 00:24:24 +03:00
{
2018-04-06 02:19:29 +03:00
if ( ! osb - > osb_fc_ent . fs_fcheck )
return ;
kobject_del ( & osb - > osb_fc_ent . fs_kobj ) ;
kobject_put ( & osb - > osb_fc_ent . fs_kobj ) ;
wait_for_completion ( & osb - > osb_fc_ent . fs_kobj_unregister ) ;
ocfs2_filecheck_sysfs_free ( & osb - > osb_fc_ent ) ;
2016-03-23 00:24:24 +03:00
}
static int
ocfs2_filecheck_erase_entries ( struct ocfs2_filecheck_sysfs_entry * ent ,
unsigned int count ) ;
static int
ocfs2_filecheck_adjust_max ( struct ocfs2_filecheck_sysfs_entry * ent ,
unsigned int len )
{
int ret ;
if ( ( len < OCFS2_FILECHECK_MINSIZE ) | | ( len > OCFS2_FILECHECK_MAXSIZE ) )
return - EINVAL ;
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
if ( len < ( ent - > fs_fcheck - > fc_size - ent - > fs_fcheck - > fc_done ) ) {
2018-04-06 02:19:25 +03:00
mlog ( ML_NOTICE ,
2016-03-23 00:24:24 +03:00
" Cannot set online file check maximum entry number "
" to %u due to too many pending entries(%u) \n " ,
len , ent - > fs_fcheck - > fc_size - ent - > fs_fcheck - > fc_done ) ;
ret = - EBUSY ;
} else {
if ( len < ent - > fs_fcheck - > fc_size )
BUG_ON ( ! ocfs2_filecheck_erase_entries ( ent ,
ent - > fs_fcheck - > fc_size - len ) ) ;
ent - > fs_fcheck - > fc_max = len ;
ret = 0 ;
}
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
return ret ;
}
# define OCFS2_FILECHECK_ARGS_LEN 24
static int
ocfs2_filecheck_args_get_long ( const char * buf , size_t count ,
unsigned long * val )
{
char buffer [ OCFS2_FILECHECK_ARGS_LEN ] ;
memcpy ( buffer , buf , count ) ;
buffer [ count ] = ' \0 ' ;
if ( kstrtoul ( buffer , 0 , val ) )
return 1 ;
return 0 ;
}
static int
ocfs2_filecheck_type_parse ( const char * name , unsigned int * type )
{
if ( ! strncmp ( name , " fix " , 4 ) )
* type = OCFS2_FILECHECK_TYPE_FIX ;
else if ( ! strncmp ( name , " check " , 6 ) )
* type = OCFS2_FILECHECK_TYPE_CHK ;
else if ( ! strncmp ( name , " set " , 4 ) )
* type = OCFS2_FILECHECK_TYPE_SET ;
else
return 1 ;
return 0 ;
}
static int
ocfs2_filecheck_args_parse ( const char * name , const char * buf , size_t count ,
struct ocfs2_filecheck_args * args )
{
unsigned long val = 0 ;
unsigned int type ;
/* too short/long args length */
if ( ( count < 1 ) | | ( count > = OCFS2_FILECHECK_ARGS_LEN ) )
return 1 ;
if ( ocfs2_filecheck_type_parse ( name , & type ) )
return 1 ;
if ( ocfs2_filecheck_args_get_long ( buf , count , & val ) )
return 1 ;
if ( val < = 0 )
return 1 ;
args - > fa_type = type ;
if ( type = = OCFS2_FILECHECK_TYPE_SET )
args - > fa_len = ( unsigned int ) val ;
else
args - > fa_ino = val ;
return 0 ;
}
2018-04-06 02:19:29 +03:00
static ssize_t ocfs2_filecheck_attr_show ( struct kobject * kobj ,
2016-03-23 00:24:24 +03:00
struct kobj_attribute * attr ,
char * buf )
{
ssize_t ret = 0 , total = 0 , remain = PAGE_SIZE ;
unsigned int type ;
struct ocfs2_filecheck_entry * p ;
2018-04-06 02:19:29 +03:00
struct ocfs2_filecheck_sysfs_entry * ent = container_of ( kobj ,
struct ocfs2_filecheck_sysfs_entry , fs_kobj ) ;
2016-03-23 00:24:24 +03:00
if ( ocfs2_filecheck_type_parse ( attr - > attr . name , & type ) )
return - EINVAL ;
if ( type = = OCFS2_FILECHECK_TYPE_SET ) {
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
total = snprintf ( buf , remain , " %u \n " , ent - > fs_fcheck - > fc_max ) ;
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
goto exit ;
}
ret = snprintf ( buf , remain , " INO \t \t DONE \t ERROR \n " ) ;
total + = ret ;
remain - = ret ;
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
list_for_each_entry ( p , & ent - > fs_fcheck - > fc_head , fe_list ) {
if ( p - > fe_type ! = type )
continue ;
ret = snprintf ( buf + total , remain , " %lu \t \t %u \t %s \n " ,
p - > fe_ino , p - > fe_done ,
ocfs2_filecheck_error ( p - > fe_status ) ) ;
if ( ret < 0 ) {
total = ret ;
break ;
}
if ( ret = = remain ) {
/* snprintf() didn't fit */
total = - E2BIG ;
break ;
}
total + = ret ;
remain - = ret ;
}
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
exit :
return total ;
}
2018-04-06 02:19:32 +03:00
static inline int
ocfs2_filecheck_is_dup_entry ( struct ocfs2_filecheck_sysfs_entry * ent ,
unsigned long ino )
{
struct ocfs2_filecheck_entry * p ;
list_for_each_entry ( p , & ent - > fs_fcheck - > fc_head , fe_list ) {
if ( ! p - > fe_done ) {
if ( p - > fe_ino = = ino )
return 1 ;
}
}
return 0 ;
}
2018-04-06 02:19:29 +03:00
static inline int
2016-03-23 00:24:24 +03:00
ocfs2_filecheck_erase_entry ( struct ocfs2_filecheck_sysfs_entry * ent )
{
struct ocfs2_filecheck_entry * p ;
list_for_each_entry ( p , & ent - > fs_fcheck - > fc_head , fe_list ) {
if ( p - > fe_done ) {
list_del ( & p - > fe_list ) ;
kfree ( p ) ;
ent - > fs_fcheck - > fc_size - - ;
ent - > fs_fcheck - > fc_done - - ;
return 1 ;
}
}
return 0 ;
}
static int
ocfs2_filecheck_erase_entries ( struct ocfs2_filecheck_sysfs_entry * ent ,
unsigned int count )
{
unsigned int i = 0 ;
unsigned int ret = 0 ;
while ( i + + < count ) {
if ( ocfs2_filecheck_erase_entry ( ent ) )
ret + + ;
else
break ;
}
return ( ret = = count ? 1 : 0 ) ;
}
static void
ocfs2_filecheck_done_entry ( struct ocfs2_filecheck_sysfs_entry * ent ,
struct ocfs2_filecheck_entry * entry )
{
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
2018-04-06 02:19:25 +03:00
entry - > fe_done = 1 ;
2016-03-23 00:24:24 +03:00
ent - > fs_fcheck - > fc_done + + ;
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
}
static unsigned int
2018-04-06 02:19:29 +03:00
ocfs2_filecheck_handle ( struct ocfs2_super * osb ,
2016-03-23 00:24:24 +03:00
unsigned long ino , unsigned int flags )
{
unsigned int ret = OCFS2_FILECHECK_ERR_SUCCESS ;
struct inode * inode = NULL ;
int rc ;
2018-04-06 02:19:29 +03:00
inode = ocfs2_iget ( osb , ino , flags , 0 ) ;
2016-03-23 00:24:24 +03:00
if ( IS_ERR ( inode ) ) {
rc = ( int ) ( - ( long ) inode ) ;
if ( rc > = OCFS2_FILECHECK_ERR_START & &
rc < OCFS2_FILECHECK_ERR_END )
ret = rc ;
else
ret = OCFS2_FILECHECK_ERR_FAILED ;
} else
iput ( inode ) ;
return ret ;
}
static void
ocfs2_filecheck_handle_entry ( struct ocfs2_filecheck_sysfs_entry * ent ,
struct ocfs2_filecheck_entry * entry )
{
2018-04-06 02:19:29 +03:00
struct ocfs2_super * osb = container_of ( ent , struct ocfs2_super ,
osb_fc_ent ) ;
2016-03-23 00:24:24 +03:00
if ( entry - > fe_type = = OCFS2_FILECHECK_TYPE_CHK )
2018-04-06 02:19:29 +03:00
entry - > fe_status = ocfs2_filecheck_handle ( osb ,
2016-03-23 00:24:24 +03:00
entry - > fe_ino , OCFS2_FI_FLAG_FILECHECK_CHK ) ;
else if ( entry - > fe_type = = OCFS2_FILECHECK_TYPE_FIX )
2018-04-06 02:19:29 +03:00
entry - > fe_status = ocfs2_filecheck_handle ( osb ,
2016-03-23 00:24:24 +03:00
entry - > fe_ino , OCFS2_FI_FLAG_FILECHECK_FIX ) ;
else
entry - > fe_status = OCFS2_FILECHECK_ERR_UNSUPPORTED ;
ocfs2_filecheck_done_entry ( ent , entry ) ;
}
2018-04-06 02:19:29 +03:00
static ssize_t ocfs2_filecheck_attr_store ( struct kobject * kobj ,
2016-03-23 00:24:24 +03:00
struct kobj_attribute * attr ,
const char * buf , size_t count )
{
2018-04-06 02:19:29 +03:00
ssize_t ret = 0 ;
2016-03-23 00:24:24 +03:00
struct ocfs2_filecheck_args args ;
struct ocfs2_filecheck_entry * entry ;
2018-04-06 02:19:29 +03:00
struct ocfs2_filecheck_sysfs_entry * ent = container_of ( kobj ,
struct ocfs2_filecheck_sysfs_entry , fs_kobj ) ;
2016-03-23 00:24:24 +03:00
if ( count = = 0 )
return count ;
2018-04-06 02:19:29 +03:00
if ( ocfs2_filecheck_args_parse ( attr - > attr . name , buf , count , & args ) )
2016-03-23 00:24:24 +03:00
return - EINVAL ;
if ( args . fa_type = = OCFS2_FILECHECK_TYPE_SET ) {
ret = ocfs2_filecheck_adjust_max ( ent , args . fa_len ) ;
goto exit ;
}
entry = kmalloc ( sizeof ( struct ocfs2_filecheck_entry ) , GFP_NOFS ) ;
if ( ! entry ) {
ret = - ENOMEM ;
goto exit ;
}
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
2018-04-06 02:19:32 +03:00
if ( ocfs2_filecheck_is_dup_entry ( ent , args . fa_ino ) ) {
ret = - EEXIST ;
kfree ( entry ) ;
} else if ( ( ent - > fs_fcheck - > fc_size > = ent - > fs_fcheck - > fc_max ) & &
2018-04-06 02:19:29 +03:00
( ent - > fs_fcheck - > fc_done = = 0 ) ) {
2018-04-06 02:19:25 +03:00
mlog ( ML_NOTICE ,
2016-03-23 00:24:24 +03:00
" Cannot do more file check "
" since file check queue(%u) is full now \n " ,
ent - > fs_fcheck - > fc_max ) ;
2018-04-06 02:19:25 +03:00
ret = - EAGAIN ;
2016-03-23 00:24:24 +03:00
kfree ( entry ) ;
} else {
if ( ( ent - > fs_fcheck - > fc_size > = ent - > fs_fcheck - > fc_max ) & &
( ent - > fs_fcheck - > fc_done > 0 ) ) {
/* Delete the oldest entry which was done,
* make sure the entry size in list does
* not exceed maximum value
*/
BUG_ON ( ! ocfs2_filecheck_erase_entry ( ent ) ) ;
}
entry - > fe_ino = args . fa_ino ;
entry - > fe_type = args . fa_type ;
entry - > fe_done = 0 ;
entry - > fe_status = OCFS2_FILECHECK_ERR_INPROGRESS ;
list_add_tail ( & entry - > fe_list , & ent - > fs_fcheck - > fc_head ) ;
ent - > fs_fcheck - > fc_size + + ;
}
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
if ( ! ret )
ocfs2_filecheck_handle_entry ( ent , entry ) ;
exit :
return ( ! ret ? count : ret ) ;
}