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 .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation , version 2.
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*/
# 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 "
} ;
static DEFINE_SPINLOCK ( ocfs2_filecheck_sysfs_lock ) ;
static LIST_HEAD ( ocfs2_filecheck_sysfs_list ) ;
struct ocfs2_filecheck {
struct list_head fc_head ; /* File check entry list head */
spinlock_t fc_lock ;
unsigned int fc_max ; /* Maximum number of entry in list */
unsigned int fc_size ; /* Current entry count in list */
unsigned int fc_done ; /* Finished entry count in list */
} ;
struct ocfs2_filecheck_sysfs_entry { /* sysfs entry per mounting */
struct list_head fs_list ;
atomic_t fs_count ;
struct super_block * fs_sb ;
struct kset * fs_devicekset ;
struct kset * fs_fcheckkset ;
struct ocfs2_filecheck * fs_fcheck ;
} ;
# define OCFS2_FILECHECK_MAXSIZE 100
# define OCFS2_FILECHECK_MINSIZE 10
/* File check operation type */
enum {
OCFS2_FILECHECK_TYPE_CHK = 0 , /* Check a file(inode) */
OCFS2_FILECHECK_TYPE_FIX , /* Fix a file(inode) */
OCFS2_FILECHECK_TYPE_SET = 100 /* Set entry list maximum size */
} ;
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 ] ;
}
static ssize_t ocfs2_filecheck_show ( struct kobject * kobj ,
struct kobj_attribute * attr ,
char * buf ) ;
static ssize_t ocfs2_filecheck_store ( struct kobject * kobj ,
struct kobj_attribute * attr ,
const char * buf , size_t count ) ;
static struct kobj_attribute ocfs2_attr_filecheck_chk =
__ATTR ( check , S_IRUSR | S_IWUSR ,
ocfs2_filecheck_show ,
ocfs2_filecheck_store ) ;
static struct kobj_attribute ocfs2_attr_filecheck_fix =
__ATTR ( fix , S_IRUSR | S_IWUSR ,
ocfs2_filecheck_show ,
ocfs2_filecheck_store ) ;
static struct kobj_attribute ocfs2_attr_filecheck_set =
__ATTR ( set , S_IRUSR | S_IWUSR ,
ocfs2_filecheck_show ,
ocfs2_filecheck_store ) ;
static void
ocfs2_filecheck_sysfs_free ( struct ocfs2_filecheck_sysfs_entry * entry )
{
struct ocfs2_filecheck_entry * p ;
if ( ! atomic_dec_and_test ( & entry - > fs_count ) )
2017-11-02 18:27:44 +03:00
wait_on_atomic_t ( & entry - > fs_count , atomic_t_wait ,
2016-03-23 00:24:24 +03:00
TASK_UNINTERRUPTIBLE ) ;
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 ) ;
kset_unregister ( entry - > fs_fcheckkset ) ;
kset_unregister ( entry - > fs_devicekset ) ;
kfree ( entry - > fs_fcheck ) ;
kfree ( entry ) ;
}
static void
ocfs2_filecheck_sysfs_add ( struct ocfs2_filecheck_sysfs_entry * entry )
{
spin_lock ( & ocfs2_filecheck_sysfs_lock ) ;
list_add_tail ( & entry - > fs_list , & ocfs2_filecheck_sysfs_list ) ;
spin_unlock ( & ocfs2_filecheck_sysfs_lock ) ;
}
static int ocfs2_filecheck_sysfs_del ( const char * devname )
{
struct ocfs2_filecheck_sysfs_entry * p ;
spin_lock ( & ocfs2_filecheck_sysfs_lock ) ;
list_for_each_entry ( p , & ocfs2_filecheck_sysfs_list , fs_list ) {
if ( ! strcmp ( p - > fs_sb - > s_id , devname ) ) {
list_del ( & p - > fs_list ) ;
spin_unlock ( & ocfs2_filecheck_sysfs_lock ) ;
ocfs2_filecheck_sysfs_free ( p ) ;
return 0 ;
}
}
spin_unlock ( & ocfs2_filecheck_sysfs_lock ) ;
return 1 ;
}
static void
ocfs2_filecheck_sysfs_put ( struct ocfs2_filecheck_sysfs_entry * entry )
{
if ( atomic_dec_and_test ( & entry - > fs_count ) )
wake_up_atomic_t ( & entry - > fs_count ) ;
}
static struct ocfs2_filecheck_sysfs_entry *
ocfs2_filecheck_sysfs_get ( const char * devname )
{
struct ocfs2_filecheck_sysfs_entry * p = NULL ;
spin_lock ( & ocfs2_filecheck_sysfs_lock ) ;
list_for_each_entry ( p , & ocfs2_filecheck_sysfs_list , fs_list ) {
if ( ! strcmp ( p - > fs_sb - > s_id , devname ) ) {
atomic_inc ( & p - > fs_count ) ;
spin_unlock ( & ocfs2_filecheck_sysfs_lock ) ;
return p ;
}
}
spin_unlock ( & ocfs2_filecheck_sysfs_lock ) ;
return NULL ;
}
int ocfs2_filecheck_create_sysfs ( struct super_block * sb )
{
int ret = 0 ;
struct kset * device_kset = NULL ;
struct kset * fcheck_kset = NULL ;
struct ocfs2_filecheck * fcheck = NULL ;
struct ocfs2_filecheck_sysfs_entry * entry = NULL ;
struct attribute * * attrs = NULL ;
struct attribute_group attrgp ;
if ( ! ocfs2_kset )
return - ENOMEM ;
attrs = kmalloc ( sizeof ( struct attribute * ) * 4 , GFP_NOFS ) ;
if ( ! attrs ) {
ret = - ENOMEM ;
goto error ;
} else {
attrs [ 0 ] = & ocfs2_attr_filecheck_chk . attr ;
attrs [ 1 ] = & ocfs2_attr_filecheck_fix . attr ;
attrs [ 2 ] = & ocfs2_attr_filecheck_set . attr ;
attrs [ 3 ] = NULL ;
memset ( & attrgp , 0 , sizeof ( attrgp ) ) ;
attrgp . attrs = attrs ;
}
fcheck = kmalloc ( sizeof ( struct ocfs2_filecheck ) , GFP_NOFS ) ;
if ( ! fcheck ) {
ret = - ENOMEM ;
goto error ;
} else {
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 ;
}
if ( strlen ( sb - > s_id ) < = 0 ) {
mlog ( ML_ERROR ,
" Cannot get device basename when create filecheck sysfs \n " ) ;
ret = - ENODEV ;
goto error ;
}
device_kset = kset_create_and_add ( sb - > s_id , NULL , & ocfs2_kset - > kobj ) ;
if ( ! device_kset ) {
ret = - ENOMEM ;
goto error ;
}
fcheck_kset = kset_create_and_add ( " filecheck " , NULL ,
& device_kset - > kobj ) ;
if ( ! fcheck_kset ) {
ret = - ENOMEM ;
goto error ;
}
ret = sysfs_create_group ( & fcheck_kset - > kobj , & attrgp ) ;
if ( ret )
goto error ;
entry = kmalloc ( sizeof ( struct ocfs2_filecheck_sysfs_entry ) , GFP_NOFS ) ;
if ( ! entry ) {
ret = - ENOMEM ;
goto error ;
} else {
atomic_set ( & entry - > fs_count , 1 ) ;
entry - > fs_sb = sb ;
entry - > fs_devicekset = device_kset ;
entry - > fs_fcheckkset = fcheck_kset ;
entry - > fs_fcheck = fcheck ;
ocfs2_filecheck_sysfs_add ( entry ) ;
}
kfree ( attrs ) ;
return 0 ;
error :
kfree ( attrs ) ;
kfree ( entry ) ;
kfree ( fcheck ) ;
kset_unregister ( fcheck_kset ) ;
kset_unregister ( device_kset ) ;
return ret ;
}
int ocfs2_filecheck_remove_sysfs ( struct super_block * sb )
{
return ocfs2_filecheck_sysfs_del ( sb - > s_id ) ;
}
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 ) ) {
mlog ( ML_ERROR ,
" 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 ;
}
static ssize_t ocfs2_filecheck_show ( struct kobject * kobj ,
struct kobj_attribute * attr ,
char * buf )
{
ssize_t ret = 0 , total = 0 , remain = PAGE_SIZE ;
unsigned int type ;
struct ocfs2_filecheck_entry * p ;
struct ocfs2_filecheck_sysfs_entry * ent ;
if ( ocfs2_filecheck_type_parse ( attr - > attr . name , & type ) )
return - EINVAL ;
ent = ocfs2_filecheck_sysfs_get ( kobj - > parent - > name ) ;
if ( ! ent ) {
mlog ( ML_ERROR ,
" Cannot get the corresponding entry via device basename %s \n " ,
kobj - > name ) ;
return - ENODEV ;
}
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 :
ocfs2_filecheck_sysfs_put ( ent ) ;
return total ;
}
static int
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 )
{
entry - > fe_done = 1 ;
spin_lock ( & ent - > fs_fcheck - > fc_lock ) ;
ent - > fs_fcheck - > fc_done + + ;
spin_unlock ( & ent - > fs_fcheck - > fc_lock ) ;
}
static unsigned int
ocfs2_filecheck_handle ( struct super_block * sb ,
unsigned long ino , unsigned int flags )
{
unsigned int ret = OCFS2_FILECHECK_ERR_SUCCESS ;
struct inode * inode = NULL ;
int rc ;
inode = ocfs2_iget ( OCFS2_SB ( sb ) , ino , flags , 0 ) ;
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 )
{
if ( entry - > fe_type = = OCFS2_FILECHECK_TYPE_CHK )
entry - > fe_status = ocfs2_filecheck_handle ( ent - > fs_sb ,
entry - > fe_ino , OCFS2_FI_FLAG_FILECHECK_CHK ) ;
else if ( entry - > fe_type = = OCFS2_FILECHECK_TYPE_FIX )
entry - > fe_status = ocfs2_filecheck_handle ( ent - > fs_sb ,
entry - > fe_ino , OCFS2_FI_FLAG_FILECHECK_FIX ) ;
else
entry - > fe_status = OCFS2_FILECHECK_ERR_UNSUPPORTED ;
ocfs2_filecheck_done_entry ( ent , entry ) ;
}
static ssize_t ocfs2_filecheck_store ( struct kobject * kobj ,
struct kobj_attribute * attr ,
const char * buf , size_t count )
{
struct ocfs2_filecheck_args args ;
struct ocfs2_filecheck_entry * entry ;
struct ocfs2_filecheck_sysfs_entry * ent ;
ssize_t ret = 0 ;
if ( count = = 0 )
return count ;
if ( ocfs2_filecheck_args_parse ( attr - > attr . name , buf , count , & args ) ) {
mlog ( ML_ERROR , " Invalid arguments for online file check \n " ) ;
return - EINVAL ;
}
ent = ocfs2_filecheck_sysfs_get ( kobj - > parent - > name ) ;
if ( ! ent ) {
mlog ( ML_ERROR ,
" Cannot get the corresponding entry via device basename %s \n " ,
kobj - > parent - > name ) ;
return - ENODEV ;
}
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 ) ;
if ( ( ent - > fs_fcheck - > fc_size > = ent - > fs_fcheck - > fc_max ) & &
( ent - > fs_fcheck - > fc_done = = 0 ) ) {
mlog ( ML_ERROR ,
" Cannot do more file check "
" since file check queue(%u) is full now \n " ,
ent - > fs_fcheck - > fc_max ) ;
ret = - EBUSY ;
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 :
ocfs2_filecheck_sysfs_put ( ent ) ;
return ( ! ret ? count : ret ) ;
}