Added SIGUSR1 option for cache file integrity test

This commit is contained in:
Takeshi Nakatani
2020-06-28 08:00:41 +00:00
committed by Andrew Gaul
parent e0a38adaf6
commit ad1961417d
9 changed files with 864 additions and 7 deletions

View File

@ -340,6 +340,12 @@ Put the debug message from libcurl when this option is specified.
Specify "normal" or "body" for the parameter.
If the parameter is omitted, it is the same as "normal".
If "body" is specified, some API communication body data will be output in addition to the debug message output as "normal".
.TP
\fB\-o\fR set_check_cache_sigusr1 (default is stdout)
If the cache is enabled, you can check the integrity of the cache file and the cache file's stats info file.
This option is specified and when sending the SIGUSR1 signal to the s3fs process checks the cache status at that time.
This option can take a file path as parameter to output the check result to that file.
The file path parameter can be omitted. If omitted, the result will be output to stdout or syslog.
.SS "utility mode options"
.TP
\fB\-u\fR or \fB\-\-incomplete\-mpu\-list\fR

View File

@ -32,7 +32,8 @@ s3fs_SOURCES = \
s3fs_util.cpp \
fdcache.cpp \
common_auth.cpp \
addhead.cpp
addhead.cpp \
sighandlers.cpp
if USE_SSL_OPENSSL
s3fs_SOURCES += openssl_auth.cpp
endif

View File

@ -126,6 +126,16 @@ enum s3fs_log_level{
} \
}while(0)
// Special macro for checking cache files
#define S3FS_LOW_CACHE(fp, fmt, ...) \
do{ \
if(foreground){ \
fprintf(fp, fmt "%s\n", __VA_ARGS__); \
}else{ \
syslog(S3FS_LOG_LEVEL_TO_SYSLOG(S3FS_LOG_INFO), "%s: " fmt "%s", instance_name.c_str(), __VA_ARGS__); \
} \
}while(0)
// [NOTE]
// small trick for VA_ARGS
//
@ -140,6 +150,7 @@ enum s3fs_log_level{
#define S3FS_PRN_INFO2(fmt, ...) S3FS_LOW_LOGPRN2(S3FS_LOG_INFO, 2, fmt, ##__VA_ARGS__, "")
#define S3FS_PRN_INFO3(fmt, ...) S3FS_LOW_LOGPRN2(S3FS_LOG_INFO, 3, fmt, ##__VA_ARGS__, "")
#define S3FS_PRN_CURL(fmt, ...) S3FS_LOW_CURLDBG(fmt, ##__VA_ARGS__, "")
#define S3FS_PRN_CACHE(fp, ...) S3FS_LOW_CACHE(fp, ##__VA_ARGS__, "")
//
// Typedef

View File

@ -31,6 +31,7 @@
#include <cerrno>
#include <cstring>
#include <dirent.h>
#include <stddef.h>
#include <curl/curl.h>
#include <string>
#include <iostream>
@ -51,7 +52,8 @@ using namespace std;
//------------------------------------------------
// Symbols
//------------------------------------------------
static const int MAX_MULTIPART_CNT = 10 * 1000; // S3 multipart max count
static const int MAX_MULTIPART_CNT = 10 * 1000; // S3 multipart max count
static const int CHECK_CACHEFILE_PART_SIZE = 1024 * 16; // Buffer size in PageList::CheckZeroAreaInFile()
//
// For cache directory top path
@ -62,6 +64,28 @@ static const int MAX_MULTIPART_CNT = 10 * 1000; // S3 multipart max count
#define TMPFILE_DIR_0PATH "/tmp"
#endif
//
// The following symbols are used by FdManager::RawCheckAllCache().
//
#define CACHEDBG_FMT_DIR_PROB "Directory: %s"
#define CACHEDBG_FMT_HEAD "------------------------------------------------------------\n" \
"Check cache file and its stats file consistency\n" \
"------------------------------------------------------------"
#define CACHEDBG_FMT_FOOT "------------------------------------------------------------\n" \
"Summary - Total files: %d\n" \
" Detected error files: %d\n" \
" Detected error directories: %d\n" \
"------------------------------------------------------------"
#define CACHEDBG_FMT_FILE_OK "File: %s%s -> [OK] no problem"
#define CACHEDBG_FMT_FILE_PROB "File: %s%s"
#define CACHEDBG_FMT_DIR_PROB "Directory: %s"
#define CACHEDBG_FMT_ERR_HEAD " -> [E] there is a mark that data exists in stats, but there is no data in the cache file."
#define CACHEDBG_FMT_WARN_HEAD " -> [W] These show no data in stats, but there is evidence of data in the cache file(no problem)."
#define CACHEDBG_FMT_WARN_OPEN "\n -> [W] This file is currently open and may not provide accurate analysis results."
#define CACHEDBG_FMT_CRIT_HEAD " -> [C] %s"
#define CACHEDBG_FMT_CRIT_HEAD2 " -> [C] "
#define CACHEDBG_FMT_PROB_BLOCK " 0x%016jx(0x%016jx bytes)"
//------------------------------------------------
// CacheFileStat class methods
//------------------------------------------------
@ -223,7 +247,7 @@ bool CacheFileStat::SetPath(const char* tpath, bool is_open)
return Open();
}
bool CacheFileStat::Open()
bool CacheFileStat::RawOpen(bool readonly)
{
if(path.empty()){
return false;
@ -239,9 +263,16 @@ bool CacheFileStat::Open()
return false;
}
// open
if(-1 == (fd = open(sfile_path.c_str(), O_CREAT|O_RDWR, 0600))){
S3FS_PRN_ERR("failed to open cache stat file path(%s) - errno(%d)", path.c_str(), errno);
return false;
if(readonly){
if(-1 == (fd = open(sfile_path.c_str(), O_RDONLY))){
S3FS_PRN_ERR("failed to read only open cache stat file path(%s) - errno(%d)", path.c_str(), errno);
return false;
}
}else{
if(-1 == (fd = open(sfile_path.c_str(), O_CREAT|O_RDWR, 0600))){
S3FS_PRN_ERR("failed to open cache stat file path(%s) - errno(%d)", path.c_str(), errno);
return false;
}
}
// lock
if(-1 == flock(fd, LOCK_EX)){
@ -263,6 +294,16 @@ bool CacheFileStat::Open()
return true;
}
bool CacheFileStat::Open()
{
return RawOpen(false);
}
bool CacheFileStat::ReadOnlyOpen()
{
return RawOpen(true);
}
bool CacheFileStat::Release()
{
if(-1 == fd){
@ -410,6 +451,181 @@ static fdpage_list_t parse_partsize_fdpage_list(const fdpage_list_t& pages, off_
return parsed_pages;
}
//------------------------------------------------
// PageList class methods
//------------------------------------------------
//
// Examine and return the status of each block in the file.
//
// Assuming the file is a sparse file, check the HOLE and DATA areas
// and return it in fdpage_list_t. The loaded flag of each fdpage is
// set to false for HOLE blocks and true for DATA blocks.
//
bool PageList::GetSparseFilePages(int fd, size_t file_size, fdpage_list_t& sparse_list)
{
// [NOTE]
// Express the status of the cache file using fdpage_list_t.
// There is a hole in the cache file(sparse file), and the
// state of this hole is expressed by the "loaded" member of
// struct fdpage. (the "modified" member is not used)
//
if(0 == file_size){
// file is empty
return true;
}
bool is_hole = false;
int hole_pos = lseek(fd, 0, SEEK_HOLE);
int data_pos = lseek(fd, 0, SEEK_DATA);
if(-1 == hole_pos && -1 == data_pos){
S3FS_PRN_ERR("Could not find the first position both HOLE and DATA in the file(fd=%d).", fd);
return false;
}else if(-1 == hole_pos){
is_hole = false;
}else if(-1 == data_pos){
is_hole = true;
}else if(hole_pos < data_pos){
is_hole = true;
}else{
is_hole = false;
}
for(int cur_pos = 0, next_pos = 0; 0 <= cur_pos; cur_pos = next_pos, is_hole = !is_hole){
fdpage page;
page.offset = cur_pos;
page.loaded = !is_hole;
page.modified = false;
next_pos = lseek(fd, cur_pos, (is_hole ? SEEK_DATA : SEEK_HOLE));
if(-1 == next_pos){
page.bytes = static_cast<off_t>(file_size - cur_pos);
}else{
page.bytes = next_pos - cur_pos;
}
sparse_list.push_back(page);
}
return true;
}
//
// Confirm that the specified area is ZERO
//
bool PageList::CheckZeroAreaInFile(int fd, off_t start, size_t bytes)
{
char* readbuff = new char[CHECK_CACHEFILE_PART_SIZE];
for(size_t comp_bytes = 0, check_bytes = 0; comp_bytes < bytes; comp_bytes += check_bytes){
if(CHECK_CACHEFILE_PART_SIZE < (bytes - comp_bytes)){
check_bytes = CHECK_CACHEFILE_PART_SIZE;
}else{
check_bytes = bytes - comp_bytes;
}
bool found_bad_data = false;
ssize_t read_bytes;
if(-1 == (read_bytes = pread(fd, readbuff, check_bytes, (start + comp_bytes)))){
S3FS_PRN_ERR("Something error is occurred in reading %zu bytes at %lld from file(%d).", check_bytes, static_cast<long long int>(start + comp_bytes), fd);
found_bad_data = true;
}else{
check_bytes = static_cast<size_t>(read_bytes);
for(size_t tmppos = 0; tmppos < check_bytes; ++tmppos){
if('\0' != readbuff[tmppos]){
// found not ZERO data.
found_bad_data = true;
break;
}
}
}
if(found_bad_data){
delete[] readbuff;
return false;
}
}
delete[] readbuff;
return true;
}
//
// Checks that the specified area matches the state of the sparse file.
//
// [Parameters]
// checkpage: This is one state of the cache file, it is loaded from the stats file.
// sparse_list: This is a list of the results of directly checking the cache file status(HOLE/DATA).
// In the HOLE area, the "loaded" flag of fdpage is false. The DATA area has it set to true.
// fd: opened file discriptor to target cache file.
//
bool PageList::CheckAreaInSparseFile(const struct fdpage& checkpage, const fdpage_list_t& sparse_list, int fd, fdpage_list_t& err_area_list, fdpage_list_t& warn_area_list)
{
// Check the block status of a part(Check Area: checkpage) of the target file.
// The elements of sparse_list have 5 patterns that overlap this block area.
//
// File |<---...--------------------------------------...--->|
// Check Area (offset)<-------------------->(offset + bytes - 1)
// Area case(0) <------->
// Area case(1) <------->
// Area case(2) <-------->
// Area case(3) <---------->
// Area case(4) <----------->
// Area case(5) <----------------------------->
//
bool result = true;
for(fdpage_list_t::const_iterator iter = sparse_list.begin(); iter != sparse_list.end(); ++iter){
off_t check_start = 0;
off_t check_bytes = 0;
if((iter->offset + iter->bytes) <= checkpage.offset){
// case 0
continue; // next
}else if((checkpage.offset + checkpage.bytes) <= iter->offset){
// case 1
break; // finish
}else if(iter->offset < checkpage.offset && (iter->offset + iter->bytes) < (checkpage.offset + checkpage.bytes)){
// case 2
check_start = checkpage.offset;
check_bytes = iter->bytes - (checkpage.offset - iter->offset);
}else if(iter->offset < (checkpage.offset + checkpage.bytes) && (checkpage.offset + checkpage.bytes) < (iter->offset + iter->bytes)){
// case 3
check_start = iter->offset;
check_bytes = checkpage.bytes - (iter->offset - checkpage.offset);
}else if(checkpage.offset < iter->offset && (iter->offset + iter->bytes) < (checkpage.offset + checkpage.bytes)){
// case 4
check_start = iter->offset;
check_bytes = iter->bytes;
}else{ // (iter->offset <= checkpage.offset && (checkpage.offset + checkpage.bytes) <= (iter->offset + iter->bytes))
// case 5
check_start = checkpage.offset;
check_bytes = checkpage.bytes;
}
// check target area type
if(checkpage.loaded || checkpage.modified){
// target area must be not HOLE(DATA) area.
if(!iter->loaded){
// Found bad area, it is HOLE area.
fdpage page(check_start, check_bytes, false, false);
err_area_list.push_back(page);
result = false;
}
}else{
// target area should be HOLE area.(If it is not a block boundary, it may be a DATA area.)
if(iter->loaded){
// need to check this area's each data, it should be ZERO.
if(!PageList::CheckZeroAreaInFile(fd, check_start, static_cast<size_t>(check_bytes))){
// Discovered an area that has un-initial status data but it probably does not effect bad.
fdpage page(check_start, check_bytes, true, false);
warn_area_list.push_back(page);
result = false;
}
}
}
}
return result;
}
//------------------------------------------------
// PageList methods
//------------------------------------------------
@ -946,6 +1162,49 @@ void PageList::Dump() const
S3FS_PRN_DBG("}");
}
//
// Compare the fdpage_list_t pages of the object with the state of the file.
//
// The loaded=true or modified=true area of pages must be a DATA block
// (not a HOLE block) in the file.
// The other area is a HOLE block in the file or is a DATA block(but the
// data of the target area in that block should be ZERO).
// If it is a bad area in the previous case, it will be reported as an error.
// If the latter case does not match, it will be reported as a warning.
//
bool PageList::CompareSparseFile(int fd, size_t file_size, fdpage_list_t& err_area_list, fdpage_list_t& warn_area_list)
{
err_area_list.clear();
warn_area_list.clear();
// First, list the block disk allocation area of the cache file.
// The cache file has holes(sparse file) and no disk block areas
// are assigned to any holes.
fdpage_list_t sparse_list;
if(!PageList::GetSparseFilePages(fd, file_size, sparse_list)){
S3FS_PRN_ERR("Something error is occurred in parsing hole/data of the cache file(%d).", fd);
fdpage page(0, static_cast<off_t>(file_size), false, false);
err_area_list.push_back(page);
return false;
}
if(sparse_list.empty() && pages.empty()){
// both file and stats information are empty, it means cache file size is ZERO.
return true;
}
// Compare each pages and sparse_list
bool result = true;
for(fdpage_list_t::const_iterator iter = pages.begin(); iter != pages.end(); ++iter){
if(!PageList::CheckAreaInSparseFile(*iter, sparse_list, fd, err_area_list, warn_area_list)){
result = false;
}
}
return result;
}
//------------------------------------------------
// FdEntity class methods
//------------------------------------------------
@ -2347,6 +2606,7 @@ bool FdManager::is_lock_init(false);
string FdManager::cache_dir;
bool FdManager::check_cache_dir_exist(false);
off_t FdManager::free_disk_space = 0;
std::string FdManager::check_cache_output;
//------------------------------------------------
// FdManager class methods
@ -2361,6 +2621,16 @@ bool FdManager::SetCacheDir(const char* dir)
return true;
}
bool FdManager::SetCacheCheckOutput(const char* path)
{
if(!path || '\0' == path[0]){
check_cache_output.erase();
}else{
check_cache_output = path;
}
return true;
}
bool FdManager::DeleteCacheDirectory()
{
if(FdManager::cache_dir.empty()){
@ -2915,6 +3185,227 @@ void FdManager::FreeReservedDiskSpace(off_t size)
free_disk_space -= size;
}
//
// Inspect all files for stats file for cache file
//
// [NOTE]
// The minimum sub_path parameter is "/".
// The sub_path is a directory path starting from "/" and ending with "/".
//
// This method produces the following output.
//
// * Header
// ------------------------------------------------------------
// Check cache file and its stats file consistency
// ------------------------------------------------------------
// * When the cache file and its stats information match
// File path: <file path> -> [OK] no problem
//
// * If there is a problem with the cache file and its stats information
// File path: <file path>
// -> [P] <If the problem is that parsing is not possible in the first place, the message is output here with this prefix.>
// -> [E] there is a mark that data exists in stats, but there is no data in the cache file.
// <offset address>(bytes)
// ...
// ...
// -> [W] These show no data in stats, but there is evidence of data in the cache file.(no problem.)
// <offset address>(bytes)
// ...
// ...
//
bool FdManager::RawCheckAllCache(FILE* fp, const char* cache_stat_top_dir, const char* sub_path, int& total_file_cnt, int& err_file_cnt, int& err_dir_cnt)
{
if(!cache_stat_top_dir || '\0' == cache_stat_top_dir[0] || !sub_path || '\0' == sub_path[0]){
S3FS_PRN_ERR("Parameter cache_stat_top_dir is empty.");
return false;
}
// allocate dirent buffer
long name_max = pathconf(cache_stat_top_dir, _PC_NAME_MAX);
if(-1 == name_max){
name_max = 255; // [NOTE] Is PATH_MAX better?
}
size_t structlen = offsetof(struct dirent, d_name) + name_max + 1;
struct dirent* pdirent;
if(NULL == (pdirent = reinterpret_cast<struct dirent*>(malloc(structlen)))){
S3FS_PRN_ERR("Could not allocate memory for dirent(length = %zu) by errno(%d)", structlen, errno);
return false;
}
// open directory of cache file's stats
DIR* statsdir;
string target_dir = cache_stat_top_dir;
target_dir += sub_path;
if(NULL == (statsdir = opendir(target_dir.c_str()))){
S3FS_PRN_ERR("Could not open directory(%s) by errno(%d)", target_dir.c_str(), errno);
free(pdirent);
return false;
}
// loop in directory of cache file's stats
struct dirent* pdirent_res = NULL;
int result;
for(result = readdir_r(statsdir, pdirent, &pdirent_res); 0 == result && pdirent_res; result = readdir_r(statsdir, pdirent, &pdirent_res)){
if(DT_DIR == pdirent_res->d_type){
// found directory
if(0 == strcmp(pdirent_res->d_name, ".") || 0 == strcmp(pdirent_res->d_name, "..")){
continue;
}
// reentrant for sub directory
string subdir_path = sub_path;
subdir_path += pdirent_res->d_name;
subdir_path += '/';
if(!RawCheckAllCache(fp, cache_stat_top_dir, subdir_path.c_str(), total_file_cnt, err_file_cnt, err_dir_cnt)){
// put error message for this dir.
++err_dir_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_DIR_PROB, subdir_path.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Something error is occurred in checking this directory");
}
}else{
++total_file_cnt;
// make cache file path
string strOpenedWarn;
string cache_path;
string object_file_path = sub_path;
object_file_path += pdirent_res->d_name;
if(!FdManager::MakeCachePath(object_file_path.c_str(), cache_path, false, false) || cache_path.empty()){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not make cache file path");
continue;
}
// check if the target file is currently in operation.
{
AutoLock auto_lock(&FdManager::fd_manager_lock);
fdent_map_t::iterator iter = fent.find(object_file_path);
if(fent.end() != iter){
// This file is opened now, then we need to put warning message.
strOpenedWarn = CACHEDBG_FMT_WARN_OPEN;
}
}
// open cache file
int cache_file_fd;
if(-1 == (cache_file_fd = open(cache_path.c_str(), O_RDONLY))){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not open cache file");
continue;
}
// get inode number for cache file
struct stat st;
if(0 != fstat(cache_file_fd, &st)){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not get file inode number for cache file");
close(cache_file_fd);
continue;
}
ino_t cache_file_inode = st.st_ino;
// open cache stat file and load page info.
PageList pagelist;
CacheFileStat cfstat(object_file_path.c_str());
if(!cfstat.ReadOnlyOpen() || !pagelist.Serialize(cfstat, false, cache_file_inode)){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not load cache file stats information");
close(cache_file_fd);
continue;
}
cfstat.Release();
// compare cache file size and stats information
if(st.st_size != pagelist.Size()){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD2 "The cache file size(%lld) and the value(%lld) from cache file stats are different", static_cast<long long int>(st.st_size), static_cast<long long int>(pagelist.Size()));
close(cache_file_fd);
continue;
}
// compare cache file stats and cache file blocks
fdpage_list_t err_area_list;
fdpage_list_t warn_area_list;
if(!pagelist.CompareSparseFile(cache_file_fd, st.st_size, err_area_list, warn_area_list)){
// Found some error or warning
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str());
if(!warn_area_list.empty()){
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_WARN_HEAD);
for(fdpage_list_t::const_iterator witer = warn_area_list.begin(); witer != warn_area_list.end(); ++witer){
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_PROB_BLOCK, (intmax_t)(witer->offset), (intmax_t)(witer->bytes));
}
}
if(!err_area_list.empty()){
++err_file_cnt;
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_ERR_HEAD);
for(fdpage_list_t::const_iterator eiter = err_area_list.begin(); eiter != err_area_list.end(); ++eiter){
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_PROB_BLOCK, (intmax_t)(eiter->offset), (intmax_t)(eiter->bytes));
}
}
}else{
// There is no problem!
if(!strOpenedWarn.empty()){
strOpenedWarn += "\n ";
}
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_OK, object_file_path.c_str(), strOpenedWarn.c_str());
}
err_area_list.clear();
warn_area_list.clear();
close(cache_file_fd);
}
}
closedir(statsdir);
free(pdirent);
return true;
}
bool FdManager::CheckAllCache()
{
FILE* fp;
if(FdManager::check_cache_output.empty()){
fp = stdout;
}else{
if(NULL == (fp = fopen(FdManager::check_cache_output.c_str(), "a+"))){
S3FS_PRN_ERR("Could not open(create) output file(%s) for checking all cache by errno(%d)", FdManager::check_cache_output.c_str(), errno);
return false;
}
}
// print head message
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_HEAD);
// Loop in directory of cache file's stats
string top_path = CacheFileStat::GetCacheFileStatTopDir();
int total_file_cnt = 0;
int err_file_cnt = 0;
int err_dir_cnt = 0;
bool result = RawCheckAllCache(fp, top_path.c_str(), "/", total_file_cnt, err_file_cnt, err_dir_cnt);
if(!result){
S3FS_PRN_ERR("Processing failed due to some problem.");
}
// print foot message
S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FOOT, total_file_cnt, err_file_cnt, err_dir_cnt);
if(stdout != fp){
fclose(fp);
}
return result;
}
/*
* Local variables:
* tab-width: 4

View File

@ -34,6 +34,8 @@ class CacheFileStat
private:
static bool MakeCacheFileStatPath(const char* path, std::string& sfile_path, bool is_create_dir = true);
bool RawOpen(bool readonly);
public:
static std::string GetCacheFileStatTopDir(void);
static bool DeleteCacheFileStat(const char* path);
@ -45,6 +47,7 @@ class CacheFileStat
~CacheFileStat();
bool Open(void);
bool ReadOnlyOpen(void);
bool Release(void);
bool SetPath(const char* tpath, bool is_open = true);
int GetFd(void) const { return fd; }
@ -91,6 +94,10 @@ class PageList
};
private:
static bool GetSparseFilePages(int fd, size_t file_size, fdpage_list_t& sparse_list);
static bool CheckZeroAreaInFile(int fd, off_t start, size_t bytes);
static bool CheckAreaInSparseFile(const struct fdpage& checkpage, const fdpage_list_t& sparse_list, int fd, fdpage_list_t& err_area_list, fdpage_list_t& warn_area_list);
void Clear(void);
bool Compress();
bool Parse(off_t new_pos);
@ -118,6 +125,7 @@ class PageList
bool Serialize(CacheFileStat& file, bool is_output, ino_t inode);
void Dump(void) const;
bool CompareSparseFile(int fd, size_t file_size, fdpage_list_t& err_area_list, fdpage_list_t& warn_area_list);
};
//------------------------------------------------
@ -217,12 +225,14 @@ class FdManager
static std::string cache_dir;
static bool check_cache_dir_exist;
static off_t free_disk_space; // limit free disk space
static std::string check_cache_output;
fdent_map_t fent;
private:
static off_t GetFreeDiskSpace(const char* path);
void CleanupCacheDirInternal(const std::string &path = "");
bool RawCheckAllCache(FILE* fp, const char* cache_stat_top_dir, const char* sub_path, int& total_file_cnt, int& err_file_cnt, int& err_dir_cnt);
public:
FdManager();
@ -234,8 +244,10 @@ class FdManager
static bool DeleteCacheDirectory(void);
static int DeleteCacheFile(const char* path);
static bool SetCacheDir(const char* dir);
static bool IsCacheDir(void) { return (0 < FdManager::cache_dir.size()); }
static bool IsCacheDir(void) { return !FdManager::cache_dir.empty(); }
static const char* GetCacheDir(void) { return FdManager::cache_dir.c_str(); }
static bool SetCacheCheckOutput(const char* path);
static const char* GetCacheCheckOutput(void) { return FdManager::check_cache_output.c_str(); }
static bool MakeCachePath(const char* path, std::string& cache_path, bool is_create_dir = true, bool is_mirror_path = false);
static bool CheckCacheTopDir(void);
static bool MakeRandomTempPath(const char* path, std::string& tmppath);
@ -256,6 +268,8 @@ class FdManager
bool Close(FdEntity* ent);
bool ChangeEntityToTempPath(FdEntity* ent, const char* path);
void CleanupCacheDir();
bool CheckAllCache(void);
};
#endif // FD_CACHE_H_

View File

@ -52,6 +52,7 @@
#include "fdcache.h"
#include "s3fs_auth.h"
#include "addhead.h"
#include "sighandlers.h"
using namespace std;
@ -3549,6 +3550,11 @@ static void* s3fs_init(struct fuse_conn_info* conn)
conn->want |= FUSE_CAP_BIG_WRITES;
}
// Signal object
if(S3fsSignals::Initialize()){
S3FS_PRN_ERR("Failed to initialize signal object, but continue...");
}
return NULL;
}
@ -3556,6 +3562,11 @@ static void s3fs_destroy(void*)
{
S3FS_PRN_INFO("destroy");
// Signal object
if(S3fsSignals::Destroy()){
S3FS_PRN_WARN("Failed to clean up signal object.");
}
// cache(remove at last)
if(is_remove_cache && (!CacheFileStat::DeleteCacheFileStatDirectory() || !FdManager::DeleteCacheDirectory())){
S3FS_PRN_WARN("Could not remove cache directory.");
@ -5102,6 +5113,23 @@ static int my_fuse_opt_proc(void* data, const char* arg, int key, struct fuse_ar
}
return 0;
}
//
// Check cache file, using SIGUSR1
//
if(0 == strcmp(arg, "set_check_cache_sigusr1")){
if(!S3fsSignals::SetUsr1Handler(NULL)){
S3FS_PRN_EXIT("could not set sigusr1 for checking cache.");
return -1;
}
return 0;
}else if(0 == STR2NCMP(arg, "set_check_cache_sigusr1=")){
const char* strfilepath = strchr(arg, '=') + sizeof(char);
if(!S3fsSignals::SetUsr1Handler(strfilepath)){
S3FS_PRN_EXIT("could not set sigusr1 for checking cache and output file(%s).", strfilepath);
return -1;
}
return 0;
}
if(0 == STR2NCMP(arg, "accessKeyId=")){
S3FS_PRN_EXIT("option accessKeyId is no longer supported.");

View File

@ -1506,6 +1506,15 @@ void show_help ()
" If \"body\" is specified, some API communication body data will be\n"
" output in addition to the debug message output as \"normal\".\n"
"\n"
" set_check_cache_sigusr1 (default is stdout)\n"
" If the cache is enabled, you can check the integrity of the\n"
" cache file and the cache file's stats info file.\n"
" This option is specified and when sending the SIGUSR1 signal\n"
" to the s3fs process checks the cache status at that time.\n"
" This option can take a file path as parameter to output the\n"
" check result to that file. The file path parameter can be omitted.\n"
" If omitted, the result will be output to stdout or syslog.\n"
"\n"
"FUSE/mount Options:\n"
"\n"
" Most of the generic mount options described in 'man mount' are\n"

230
src/sighandlers.cpp Normal file
View File

@ -0,0 +1,230 @@
/*
* s3fs - FUSE-based file system backed by Amazon S3
*
* Copyright(C) 2007 Randy Rizun <rrizun@gmail.com>
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <syslog.h>
#include <pthread.h>
#include <curl/curl.h>
#include <csignal>
#include <algorithm>
#include <map>
#include <string>
#include <list>
#include <vector>
#include "common.h"
#include "sighandlers.h"
#include "curl.h"
#include "fdcache.h"
#include "psemaphore.h"
using namespace std;
//-------------------------------------------------------------------
// Class S3fsSignals
//-------------------------------------------------------------------
S3fsSignals* S3fsSignals::pSingleton = NULL;
bool S3fsSignals::enableUsr1 = false;
//-------------------------------------------------------------------
// Class methods
//-------------------------------------------------------------------
bool S3fsSignals::Initialize()
{
if(!S3fsSignals::pSingleton){
S3fsSignals::pSingleton = new S3fsSignals;
}
return true;
}
bool S3fsSignals::Destroy()
{
if(S3fsSignals::pSingleton){
delete S3fsSignals::pSingleton;
}
return true;
}
void S3fsSignals::HandlerUSR1(int sig)
{
if(SIGUSR1 != sig){
S3FS_PRN_ERR("The handler for SIGUSR1 received signal(%d)", sig);
return;
}
S3fsSignals* pSigobj = S3fsSignals::get();
if(!pSigobj){
S3FS_PRN_ERR("S3fsSignals object is not initialized.");
return;
}
if(!pSigobj->WakeupUsr1Thread()){
S3FS_PRN_ERR("Failed to wakeup the thread for SIGUSR1.");
return;
}
}
bool S3fsSignals::SetUsr1Handler(const char* path)
{
// set output file
if(!FdManager::SetCacheCheckOutput(path)){
S3FS_PRN_ERR("Could not set output file(%s) for checking cache.", path ? path : "null(stdout)");
return false;
}
S3fsSignals::enableUsr1 = true;
return true;
}
void* S3fsSignals::CheckCacheWorker(void* arg)
{
Semaphore* pSem = static_cast<Semaphore*>(arg);
if(!pSem){
pthread_exit(NULL);
}
if(!S3fsSignals::enableUsr1){
pthread_exit(NULL);
}
// wait and loop
while(S3fsSignals::enableUsr1){
// wait
pSem->wait();
if(!S3fsSignals::enableUsr1){
break; // assap
}
// check all cache
if(!FdManager::get()->CheckAllCache()){
S3FS_PRN_ERR("Processing failed due to some problem.");
}
// do not allow request queuing
for(int value = pSem->get_value(); 0 < value; value = pSem->get_value()){
pSem->wait();
}
}
return NULL;
}
//-------------------------------------------------------------------
// Methods
//-------------------------------------------------------------------
S3fsSignals::S3fsSignals() : pThreadUsr1(NULL), pSemUsr1(NULL)
{
if(S3fsSignals::enableUsr1){
if(!InitUsr1Handler()){
S3FS_PRN_ERR("failed creating thread for SIGUSR1 handler, but continue...");
}
}
}
S3fsSignals::~S3fsSignals()
{
if(S3fsSignals::enableUsr1){
if(!DestroyUsr1Handler()){
S3FS_PRN_ERR("failed stopping thread for SIGUSR1 handler, but continue...");
}
}
}
bool S3fsSignals::InitUsr1Handler()
{
if(pThreadUsr1 || pSemUsr1){
S3FS_PRN_ERR("Already run thread for SIGUSR1");
return false;
}
// create thread
int result;
pSemUsr1 = new Semaphore(0);
pThreadUsr1 = new pthread_t;
if(0 != (result = pthread_create(pThreadUsr1, NULL, S3fsSignals::CheckCacheWorker, static_cast<void*>(pSemUsr1)))){
S3FS_PRN_ERR("Could not create thread for SIGUSR1 by %d", result);
delete pSemUsr1;
delete pThreadUsr1;
pSemUsr1 = NULL;
pThreadUsr1 = NULL;
return false;
}
// set handler
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = S3fsSignals::HandlerUSR1;
sa.sa_flags = SA_RESTART;
if(0 != sigaction(SIGUSR1, &sa, NULL)){
S3FS_PRN_ERR("Could not set signal handler for SIGUSR1");
DestroyUsr1Handler();
return false;
}
return true;
}
bool S3fsSignals::DestroyUsr1Handler()
{
if(!pThreadUsr1 || !pSemUsr1){
return false;
}
// for thread exit
S3fsSignals::enableUsr1 = false;
// wakeup thread
pSemUsr1->post();
// wait for thread exiting
void* retval = NULL;
int result;
if(0 != (result = pthread_join(*pThreadUsr1, &retval))){
S3FS_PRN_ERR("Could not stop thread for SIGUSR1 by %d", result);
return false;
}
delete pSemUsr1;
delete pThreadUsr1;
pSemUsr1 = NULL;
pThreadUsr1 = NULL;
return true;
}
bool S3fsSignals::WakeupUsr1Thread()
{
if(!pThreadUsr1 || !pSemUsr1){
S3FS_PRN_ERR("The thread for SIGUSR1 is not setup.");
return false;
}
pSemUsr1->post();
return true;
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

67
src/sighandlers.h Normal file
View File

@ -0,0 +1,67 @@
/*
* s3fs - FUSE-based file system backed by Amazon S3
*
* Copyright(C) 2007 Randy Rizun <rrizun@gmail.com>
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU 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.
*/
#ifndef S3FS_SIGHANDLERS_H_
#define S3FS_SIGHANDLERS_H_
#include "psemaphore.h"
//----------------------------------------------
// class S3fsSignals
//----------------------------------------------
class S3fsSignals
{
private:
static S3fsSignals* pSingleton;
static bool enableUsr1;
pthread_t* pThreadUsr1;
Semaphore* pSemUsr1;
protected:
static S3fsSignals* get(void) { return pSingleton; }
static void HandlerUSR1(int sig);
static void* CheckCacheWorker(void* arg);
S3fsSignals();
~S3fsSignals();
bool InitUsr1Handler(void);
bool DestroyUsr1Handler(void);
bool WakeupUsr1Thread(void);
public:
static bool Initialize(void);
static bool Destroy(void);
static bool SetUsr1Handler(const char* path);
};
#endif // S3FS_SIGHANDLERS_H_
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/