* Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <thread>
#include <chrono>
#include <list>
#include "file_check_utils.h"
#include "service_configure.h"
#include "memfs_logger.h"
#include "mem_file_system.h"
#include "mem_fs_constants.h"
#include "inode_evictor.h"
#include "memfs_api.h"
using namespace ock::common;
using namespace ock::common::config;
namespace ock {
namespace memfs {
std::vector<std::string> PreloadProgressView::g_loadPathVec;
std::mutex PreloadProgressView::gViewMutex;
std::condition_variable PreloadProgressView::gCond;
#ifdef ENABLE_UT
static constexpr int MAX_ALLOC_RETRY_TIMES = 1;
#else
static constexpr int MAX_ALLOC_RETRY_TIMES = 60;
#endif
static FileOpNotify g_fileOpNotify;
static MemFileSystem *g_fileSystem;
static ExternalStat g_externalStat;
static uint64_t g_dataBlockSize = MemFsConstants::DATA_BLOCK_SIZE;
static void ReservePhysicalPage(void *mappedAddress, uint64_t reserveSize)
{
auto pageSize = 4096UL;
uint64_t setLength = 0;
auto startPos = static_cast<uint8_t *>(mappedAddress);
while (setLength < reserveSize) {
*startPos = 0;
setLength += pageSize;
startPos += pageSize;
}
}
static void ReleaseMultiBlocks(MemFsBMM &bmm, const std::vector<uint64_t> &blocks)
{
for (auto &blockId : blocks) {
bmm.ReleaseOne(blockId);
}
}
static bool AllocateMultiBlocks(MemFsBMM &bmm, uint64_t count, std::vector<uint64_t> &blocks)
{
uint64_t blockId;
blocks.clear();
blocks.reserve(count);
auto retryCount = 0;
for (auto i = 0UL; i < count; i++) {
auto ret = bmm.AllocateOne(blockId);
while (ret != MFS_OK) {
InodeEvictor::GetInstance().RecycleInodes(g_dataBlockSize * (count - i));
ret = bmm.AllocateOne(blockId);
if (ret == MFS_OK) {
retryCount = 0;
break;
}
if (++retryCount > MAX_ALLOC_RETRY_TIMES) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (ret != MFS_OK) {
MFS_LOG_ERROR("allocate block(" << i << " of " << count << ") failed(" << ret << ")");
ReleaseMultiBlocks(bmm, blocks);
blocks.clear();
return false;
}
blocks.push_back(blockId);
}
return true;
}
int MemFsApi::Initialize() noexcept
{
g_dataBlockSize = ServiceConfigure::GetInstance().GetMemFsConfig().dataBlockSize;
auto datBlockCount = ServiceConfigure::GetInstance().GetMemFsConfig().dataBlockCount;
g_fileSystem = new (std::nothrow) MemFileSystem(g_dataBlockSize, datBlockCount, "mfs");
if (g_fileSystem == nullptr) {
MFS_LOG_ERROR("create memfs failed.");
return -1;
}
auto ret = g_fileSystem->Initialize();
if (ret != 0) {
MFS_LOG_ERROR("initialize memfs failed(" << ret << ").");
delete g_fileSystem;
g_fileSystem = nullptr;
return -1;
}
ret = InodeEvictor::GetInstance().Initialize();
if (ret != 0) {
MFS_LOG_ERROR("initialize InodeEvictor failed(" << ret << ").");
g_fileSystem->Destroy();
delete g_fileSystem;
g_fileSystem = nullptr;
return -1;
}
return 0;
}
void MemFsApi::Destroy() noexcept
{
if (g_fileSystem != nullptr) {
g_fileSystem->Destroy();
delete g_fileSystem;
g_fileSystem = nullptr;
}
}
int MemFsApi::GetShareMemoryFd() noexcept
{
if (g_fileSystem == nullptr) {
return -1;
}
return g_fileSystem->GetMemFsBMM().GetFD();
}
bool MemFsApi::BackgroundTaskEmpty() noexcept
{
return g_fileOpNotify.bgTaskEmptyNotify();
}
int MemFsApi::RegisterFileOpNotify(const FileOpNotify ¬ify) noexcept
{
g_fileOpNotify = notify;
return 0;
}
int MemFsApi::OpenFile(const std::string &path, int flags, mode_t mode) noexcept
{
int fd = -1;
uint64_t inodeNum;
if (flags == (O_CREAT | O_TRUNC | O_WRONLY)) {
fd = g_fileSystem->Create(path, mode, inodeNum);
if (fd >= 0) {
auto ret = g_fileOpNotify.openNotify(fd, path, flags, inodeNum);
if (ret != 0) {
DiscardFile(path, fd);
fd = -1;
errno = EIO;
MFS_LOG_ERROR("notify file:" << path << " failed(" << ret << "), errno:" << errno);
}
}
} else if (flags == O_RDONLY) {
fd = g_fileSystem->Open(path, inodeNum);
} else {
errno = EINVAL;
}
return fd;
}
int MemFsApi::CreateAndOpenFile(const std::string &path, uint64_t &inodeNum, mode_t mode) noexcept
{
auto pos = path.find_last_of('/');
if (pos == std::string::npos) {
MFS_LOG_ERROR("cannot find parent path");
return -EINVAL;
}
auto parent = path.substr(0, pos);
if (!parent.empty() && parent != "/") {
auto ret = MemFsApi::CreateDirectoryWithParents(parent, mode);
if (ret != 0) {
return -errno;
}
}
return g_fileSystem->Create(path, mode, inodeNum);
}
int MemFsApi::CloseFile(int fd) noexcept
{
auto ret = g_fileSystem->Close(fd);
g_fileOpNotify.closeNotify(fd, false);
return ret;
}
int MemFsApi::DiscardFile(const std::string &path, int fd) noexcept
{
struct stat fdStat {};
struct stat pathStat {};
* check path matches fd
* compare inode number in stat for path and fd
*/
auto ret = g_fileSystem->GetFileMeta(fd, fdStat);
if (ret != 0) {
MFS_LOG_WARN("discard file(" << fd << ":" << path << ") stat fd failed : " << errno << " : " <<
strerror(errno));
return -1;
}
ret = g_fileSystem->GetMeta(path, pathStat);
if (ret != 0) {
MFS_LOG_WARN("discard file(" << fd << ":" << path << ") stat path failed : " << errno << " : " <<
strerror(errno));
return -1;
}
if (fdStat.st_ino != pathStat.st_ino) {
MFS_LOG_WARN("discard file(" << fd << ":" << path << ") stat path failed, fd inode:" << fdStat.st_ino <<
", path inode:" << pathStat.st_ino);
errno = EINVAL;
return -1;
}
ret = g_fileSystem->Close(fd);
g_fileOpNotify.closeNotify(fd, true);
if (ret != MFS_OK) {
MFS_LOG_WARN("Discard file close failed as " << ret);
return -1;
}
uint64_t inode;
ret = g_fileSystem->RemoveFile(path, inode);
if (ret != MFS_OK) {
MFS_LOG_WARN("Discard file remove failed as " << ret);
return -1;
}
return 0;
}
int MemFsApi::SetBackupFinished(int fd) noexcept
{
auto ret = g_fileSystem->SetBackupFinished(fd);
return ret;
}
int MemFsApi::AllocDataBlock(int fd, uint64_t &blockId, uint64_t &blockSize) noexcept
{
auto &bmm = g_fileSystem->GetMemFsBMM();
auto allocateResult = bmm.AllocateOne(blockId);
auto retryCount = 0;
while (allocateResult != MFS_OK) {
InodeEvictor::GetInstance().RecycleInodes(g_dataBlockSize);
allocateResult = bmm.AllocateOne(blockId);
if (allocateResult == MFS_OK) {
break;
}
if (++retryCount > MAX_ALLOC_RETRY_TIMES) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (allocateResult != MFS_OK) {
MFS_LOG_ERROR("allocate block failed(" << allocateResult << ")");
errno = ENOMEM;
return -1;
}
blockSize = g_fileSystem->GetBlockSize();
ReservePhysicalPage(MemFsApi::BlockToAddress(blockId), blockSize);
auto ret = g_fileSystem->AppendFileDataBlock(fd, blockId);
if (ret != 0) {
bmm.ReleaseOne(blockId);
return -1;
}
return 0;
}
int MemFsApi::AllocDataBlocks(int fd, uint64_t bytes, std::vector<uint64_t> &blocks, uint64_t &blockSize) noexcept
{
auto &bmm = g_fileSystem->GetMemFsBMM();
blockSize = g_fileSystem->GetBlockSize();
if (blockSize == 0) {
return -1;
}
auto blockCount = (bytes + blockSize - 1UL) / blockSize;
if (!AllocateMultiBlocks(bmm, blockCount, blocks)) {
errno = ENOMEM;
return -1;
}
auto reserveTask = [blockSize](const std::vector<uint64_t> &blocks) {
for (auto &block : blocks) {
ReservePhysicalPage(MemFsApi::BlockToAddress(block), blockSize);
}
};
auto threadNum = ock::common::config::ServiceConfigure::GetInstance().GetMemFsConfig().writeParallel.threadNum;
auto everyThreadBlk = blockCount / threadNum;
auto leftBlk = blockCount % threadNum;
if (everyThreadBlk == 0) {
everyThreadBlk = 1;
threadNum = leftBlk;
}
uint64_t pos = 0UL;
std::vector<std::thread> threadPool;
threadPool.reserve(threadNum);
for (uint32_t i = 0UL; i < threadNum; ++i) {
auto curBlk = everyThreadBlk;
if (threadNum != leftBlk && i < leftBlk) {
curBlk++;
}
threadPool.emplace_back(reserveTask,
std::vector<uint64_t>(blocks.begin() + pos, blocks.begin() + pos + curBlk));
pos += curBlk;
}
for (auto &th : threadPool) {
th.join();
}
auto ret = g_fileSystem->AppendFileDataBlocks(fd, blocks);
if (ret != 0) {
ReleaseMultiBlocks(bmm, blocks);
blocks.clear();
return -1;
}
return 0;
}
int MemFsApi::TruncateFile(int fd, uint64_t length) noexcept
{
return g_fileSystem->TruncateFile(fd, length);
}
int MemFsApi::GetFileMeta(int fd, struct stat &statBuf) noexcept
{
return g_fileSystem->GetFileMeta(fd, statBuf);
}
int MemFsApi::GetFileBlocks(int fd, std::vector<uint64_t> &blocks) noexcept
{
auto ret = g_fileSystem->GetFileDataBlocks(fd, blocks);
return blocks.empty() ? MFS_ERROR : ret;
}
void *MemFsApi::BlockToAddress(uint64_t blockId) noexcept
{
BmmBlkId bmmBlkId;
bmmBlkId.whole = blockId;
return reinterpret_cast<void *>(bmmBlkId.blkAddress);
}
uint64_t MemFsApi::GetBlockOffset(uint64_t blockId) noexcept
{
return g_fileSystem->GetMemFsBMM().GetBlockOffset(blockId);
}
int MemFsApi::CreateDirectory(const std::string &path, mode_t mode, bool recursive) noexcept
{
if (recursive) {
return CreateDirectoryWithParents(path, mode);
}
{
auto ret = g_fileOpNotify.mkdirNotify(path, mode, getuid(), getgid());
if (ret != 0) {
return ret;
}
}
uint64_t inode;
return g_fileSystem->MakeDirectory(path, mode, inode);
}
int MemFsApi::CreateDirectoryWithParents(const std::string &path, mode_t mode) noexcept
{
std::string currentPath;
auto items = MemFileSystem::GetPathNameTokens(path);
for (auto &item : items) {
currentPath.append("/").append(item);
auto ret = CreateOneLevelDirectory(currentPath, mode);
if (ret != 0) {
return -errno;
}
}
return 0;
}
int MemFsApi::RemoveDirectory(const std::string &path) noexcept
{
uint64_t inode;
auto ret = g_fileSystem->RemoveDirectory(path, inode);
if (ret == 0) {
g_fileOpNotify.unlinkNotify(path, inode);
}
return ret;
}
int MemFsApi::ReadDirectory(const std::string &path, std::vector<std::pair<std::string, bool>> &entries) noexcept
{
std::vector<std::pair<std::string, InodeType>> fsEntries;
auto ret = g_fileSystem->ListDirectory(path, fsEntries);
if (ret != 0) {
return ret;
}
entries.clear();
entries.reserve(fsEntries.size());
for (auto &e : fsEntries) {
entries.emplace_back(e.first, e.second == INODE_REG);
}
return 0;
}
int MemFsApi::GetMeta(const std::string &path, struct stat &statBuf) noexcept
{
return g_fileSystem->GetMeta(path, statBuf);
}
int MemFsApi::GetMetaAcl(const std::string &path, struct stat &statBuf, MemfsFileAcl &acl) noexcept
{
return g_fileSystem->GetMeta(path, statBuf, acl);
}
int MemFsApi::Link(const std::string &oldPath, const std::string &newPath) noexcept
{
uint64_t inode;
auto ret = g_fileSystem->LinkFile(oldPath, newPath, inode);
if (ret == 0) {
auto result = g_fileOpNotify.newFileNotify(newPath, inode);
if (result != 0) {
LOG_ERROR("link path: " << newPath << ", notify failed: " << result);
errno = EIO;
g_fileSystem->RemoveFile(newPath, inode);
return -1;
}
}
return ret;
}
int MemFsApi::Rename(const std::string &oldPath, const std::string &newPath, uint32_t flags) noexcept
{
if (oldPath == newPath) {
return 0;
}
auto ret = g_fileSystem->Rename(oldPath, newPath, flags);
if (ret != 0) {
LOG_ERROR("rename(" << FileCheckUtils::RemovePrefixPath(oldPath) << ") to(" <<
FileCheckUtils::RemovePrefixPath(newPath) << ") with flags: 0x" << std::hex << flags << std::oct <<
"failed: " << errno << ":" << strerror(errno));
}
return ret;
}
int MemFsApi::Unlink(const std::string &path) noexcept
{
uint64_t inode;
auto ret = g_fileSystem->RemoveFile(path, inode);
if (ret == 0) {
g_fileOpNotify.unlinkNotify(path, inode);
}
return ret;
}
int MemFsApi::Chmod(const std::string &path, mode_t mode) noexcept
{
return g_fileSystem->Chmod(path, mode);
}
int MemFsApi::Chown(const std::string &path, uid_t uid, gid_t gid) noexcept
{
return g_fileSystem->Chown(path, uid, gid);
}
int MemFsApi::GetFileSystemStat(struct statvfs &statBuf) noexcept
{
auto ret = g_fileSystem->GetFileSystemStat(statBuf);
return ret;
}
void MemFsApi::SetExternalStat(const ExternalStat &externalStat) noexcept
{
if (externalStat == nullptr || g_fileSystem == nullptr) {
return;
}
g_externalStat = externalStat;
g_fileSystem->SetInjectMkdirCheck(
[](MemFileSystem *fs, const std::string &path, struct stat &statBuf, InodeAcl &acl) {
if (fs == g_fileSystem) {
auto ret = g_externalStat(path, statBuf, acl);
if (ret != 0) {
return ret;
}
return 0;
}
return -EINVAL;
});
}
int MemFsApi::CreateOneLevelDirectory(const std::string &path, mode_t mode) noexcept
{
struct stat statBuf {};
auto ret = g_fileSystem->GetMeta(path, statBuf);
if (ret == 0) {
if (S_ISDIR(statBuf.st_mode)) {
return 0;
}
errno = ENOTDIR;
return -1;
}
if (errno != ENOENT) {
return -1;
}
ret = CreateDirectory(path, mode, false);
if (ret == 0) {
return 0;
}
if (errno == EEXIST) {
return 0;
}
return -1;
}
void MemFsApi::Serviceable(bool state) noexcept
{
g_fileSystem->Serviceable(state);
}
bool MemFsApi::Serviceable() noexcept
{
return g_fileSystem->Serviceable();
}
void MemFsApi::GetShareFileCfg(uint64_t &blockSize, uint64_t &blockCnt) noexcept
{
blockSize = g_fileSystem->GetBlockSize();
blockCnt = g_fileSystem->GetBlockCount();
}
int MemFsApi::PreloadFile(const std::string &path) noexcept
{
int ret = 0;
if (g_fileSystem->Exist(path)) {
LOG_INFO("preload path(" << path << ") already exist, no need load again.");
} else {
ret = g_fileOpNotify.preloadFileNotify(path);
if (ret < 0) {
LOG_ERROR("preload path(" << path << ") notify failed: " << ret);
} else {
PreloadProgressView::InsertPath(path);
}
}
return ret;
}
}
}