blob: 1dfdc9b86d3cad8cb374b97e263319587e814f96 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
#define FUSE_USE_VERSION 31
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef linux
/* For pread()/pwrite()/utimensat() */
#define _XOPEN_SOURCE 700
#endif
#include <libgen.h>
#include <stdlib.h>
#include <fuse3/fuse.h>
#include <stdio.h>
#include <ctime>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#ifdef __FreeBSD__
#include <sys/socket.h>
#include <sys/un.h>
#endif
#include <sys/time.h>
#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif
#include "failure_injector_fs.h"
#include "failure_injector.h"
#include "run_grpc_service.h"
#include <string>
#include <iostream>
#include <sstream>
#include <strings.h>
#include <thread>
#include <cstdio>
using NoiseInjector::FailureInjector;
using NoiseInjector::RunGrpcService;
using namespace std;
using namespace NoiseInjector::FileSystem;
#define LOGFILE "/var/log/noise_injector_log.txt"
struct fuse_operations FailureInjectorFs::mFuseOperationsVec;
FailureInjector *FailureInjectorFs::mFailureInjector = NULL;
RunGrpcService *FailureInjectorFs::mGrpcSever = NULL;
FailureInjectorFs::FailureInjectorFs(FailureInjector *injector)
{
mFailureInjector = injector;
mGrpcSever = new RunGrpcService(mFailureInjector);
}
int FailureInjectorFs::mknod_wrapper(
int dirfd,
const char *path,
const char *link,
int mode,
dev_t rdev)
{
int res;
if (S_ISREG(mode)) {
res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
if (res >= 0)
res = close(res);
} else if (S_ISDIR(mode)) {
res = mkdirat(dirfd, path, mode);
} else if (S_ISLNK(mode) && link != NULL) {
res = symlinkat(link, dirfd, path);
} else if (S_ISFIFO(mode)) {
res = mkfifoat(dirfd, path, mode);
} else {
res = mknodat(dirfd, path, mode, rdev);
}
return res;
}
void *FailureInjectorFs::fifs_init(
struct fuse_conn_info *conn,
struct fuse_config *cfg)
{
extern void RunServer();
(void) conn;
cfg->use_ino = 1;
std::freopen(LOGFILE, "w", stdout);
/*
* Pick up changes from lower filesystem right away. This is
* also necessary for better hardlink support. When the kernel
* calls the unlink() handler, it does not know the inode of
* the to-be-removed entry and can therefore not invalidate
* the cache of the associated inode - resulting in an
* incorrect st_nlink value being reported for any remaining
* hardlinks to this inode.
*/
cfg->entry_timeout = 0;
cfg->attr_timeout = 0;
cfg->negative_timeout = 0;
/* Create a seperate GRPC server thread to accept failure injection. */
std::thread th(&RunGrpcService::RunServer, mGrpcSever);
th.detach();
return NULL;
}
int FailureInjectorFs::fifs_getattr(
const char *path,
struct stat *stbuf,
struct fuse_file_info *fi)
{
(void) fi;
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"GETATTR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = lstat(path, stbuf);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_access(
const char *path,
int mask)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"ACCESS", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = access(path, mask);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_readlink(
const char *path,
char *buf,
size_t size)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"READLINK", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = readlink(path, buf, size - 1);
if (res == -1)
return -errno;
buf[res] = '\0';
return 0;
}
int FailureInjectorFs::fifs_readdir(
const char *path,
void *buf,
fuse_fill_dir_t filler,
off_t offset,
struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
DIR *dp;
struct dirent *de;
int injected_error = 0;
(void) offset;
(void) fi;
(void) flags;
if (CheckForInjectedError(path,"READDIR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
dp = opendir(path);
if (dp == NULL)
return -errno;
while ((de = readdir(dp)) != NULL) {
struct stat st;
memset(&st, 0, sizeof(st));
st.st_ino = de->d_ino;
st.st_mode = de->d_type << 12;
if (filler(buf, de->d_name, &st, 0, (fuse_fill_dir_flags)0))
break;
}
closedir(dp);
return 0;
}
int FailureInjectorFs::fifs_mknod(
const char *path,
mode_t mode,
dev_t rdev)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"MKNOD", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_mkdir(
const char *path,
mode_t mode)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"MKDIR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = mkdir(path, mode);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_unlink(const char *path)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"UNLINK", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = unlink(path);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_rmdir(const char *path)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"RMDIR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = rmdir(path);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_symlink(
const char *from,
const char *to)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(from, "SYMLINK", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = symlink(from, to);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_rename(
const char *from,
const char *to,
unsigned int flags)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(from, "RENAME", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
if (flags)
return -EINVAL;
res = rename(from, to);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_link(
const char *from,
const char *to)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(from,"LINK", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = link(from, to);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_chmod(
const char *path,
mode_t mode,
struct fuse_file_info *fi)
{
(void) fi;
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"CHMOD", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = chmod(path, mode);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_chown(
const char *path,
uid_t uid,
gid_t gid,
struct fuse_file_info *fi)
{
(void) fi;
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"CHOWN", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = lchown(path, uid, gid);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_truncate(
const char *path,
off_t size,
struct fuse_file_info *fi)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"TRUNCATE", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
if (fi != NULL)
res = ftruncate(fi->fh, size);
else
res = truncate(path, size);
if (res == -1)
return -errno;
return 0;
}
#ifdef HAVE_UTIMENSAT
int FailureInjectorFs::fifs_utimens(
const char *path,
const struct timespec ts[2],
struct fuse_file_info *fi)
{
(void) fi;
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"UTIMENS", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
/* don't use utime/utimes since they follow symlinks */
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
if (res == -1)
return -errno;
return 0;
}
#endif
int FailureInjectorFs::fifs_create(
const char *path,
mode_t mode,
struct fuse_file_info *fi)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"CREATE", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = open(path, fi->flags, mode);
if (res == -1)
return -errno;
fi->fh = res;
return 0;
}
int FailureInjectorFs::fifs_open(
const char *path,
struct fuse_file_info *fi)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path,"OPEN", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = open(path, fi->flags);
if (res == -1)
return -errno;
fi->fh = res;
return 0;
}
bool FailureInjectorFs::CheckForInjectedError(
string path,
string op,
int *injected_error)
{
char *mpath = strdup(path.c_str());
if (mFailureInjector == NULL) {
return false;
}
auto failures = mFailureInjector->GetFailures(path, op);
while (failures == NULL) {
// Check if failures are injected in any of the parent directories.
char *dir = dirname(mpath);
failures = mFailureInjector->GetFailures(string(dir), op);
if ((failures == NULL) && (!strcmp(dir, ".") || !strcmp(dir, "/"))) {
free(mpath);
return false;
}
mpath = dir;
}
free(mpath);
*injected_error = 0;
useconds_t delay = 1;
for (auto f : *failures) {
switch(f.code) {
case InjectedAction::DELAY:
delay = f.delay * MICROSECONDS_IN_A_SECOND;
break;
case InjectedAction::FAIL:
if (*injected_error != CORRUPT_DATA_ERROR_CODE) {
*injected_error = f.error_code;
}
break;
case InjectedAction::CORRUPT:
*injected_error = CORRUPT_DATA_ERROR_CODE;
break;
default:
// Ignore for now.
break;
}
}
// First create the delay.
usleep(delay);
if (*injected_error) {
return true;
}
return false;
}
void FailureInjectorFs::FillCorruptData(
char *buf,
size_t size)
{
// For now just fill with some pattern based on input.
// TBD : Fill with random data pattern.
for (size_t i = 0; i < size; ++ i) {
buf[i] = buf[i] + 2;
}
}
int FailureInjectorFs::fifs_read(
const char *path,
char *buf,
size_t size,
off_t offset,
struct fuse_file_info *fi)
{
int fd;
int res;
int injected_error = 0;
if (CheckForInjectedError(path, "READ", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
if (injected_error != CORRUPT_DATA_ERROR_CODE) {
return -injected_error;
}
}
if(fi == NULL)
fd = open(path, O_RDONLY);
else
fd = fi->fh;
if (fd == -1)
return -errno;
res = pread(fd, buf, size, offset);
if (res == -1)
res = -errno;
if(fi == NULL)
close(fd);
if ((res != -1) && (injected_error == CORRUPT_DATA_ERROR_CODE)) {
FillCorruptData(buf, size);
return res;
}
return res;
}
int FailureInjectorFs::fifs_write(
const char *path,
const char *buf,
size_t size,
off_t offset,
struct fuse_file_info *fi)
{
int fd;
int res;
int injected_error = 0;
(void) fi;
if (CheckForInjectedError(path, "WRITE", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
if (injected_error != CORRUPT_DATA_ERROR_CODE) {
return -injected_error;
} else {
FillCorruptData(const_cast<char *>(buf), size);
}
}
if(fi == NULL)
fd = open(path, O_WRONLY);
else
fd = fi->fh;
if (fd == -1)
return -errno;
res = pwrite(fd, buf, size, offset);
if (res == -1)
res = -errno;
if(fi == NULL)
close(fd);
return res;
}
int FailureInjectorFs::fifs_statfs(
const char *path,
struct statvfs *stbuf)
{
int res;
int injected_error = 0;
if (CheckForInjectedError(path, "STATFS", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
res = statvfs(path, stbuf);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_release(
const char *path,
struct fuse_file_info *fi)
{
(void) path;
close(fi->fh);
return 0;
}
int FailureInjectorFs::fifs_fsync(
const char *path,
int isdatasync,
struct fuse_file_info *fi)
{
int injected_error = 0;
if (CheckForInjectedError(path,"FSYNC", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
(void) path;
(void) isdatasync;
(void) fi;
return 0;
}
#ifdef HAVE_POSIX_FALLOCATE
int FailureInjectorFs::fifs_fallocate(
const char *path,
int mode,
off_t offset,
off_t length,
struct fuse_file_info *fi)
{
int fd;
int res;
int injected_error = 0;
(void) fi;
if (CheckForInjectedError(path,"FALLOCATE", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
if (mode)
return -EOPNOTSUPP;
if(fi == NULL)
fd = open(path, O_WRONLY);
else
fd = fi->fh;
if (fd == -1)
return -errno;
res = -posix_fallocate(fd, offset, length);
if(fi == NULL)
close(fd);
return res;
}
#endif
#ifdef HAVE_SETXATTR
/* xattr operations are optional and can safely be left unimplemented */
int FailureInjectorFs::fifs_setxattr(
const char *path,
const char *name,
const char *value,
size_t size,
int flags)
{
int injected_error = 0;
if (CheckForInjectedError(path,"SETXATTR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
int res = lsetxattr(path, name, value, size, flags);
if (res == -1)
return -errno;
return 0;
}
int FailureInjectorFs::fifs_getxattr(
const char *path,
const char *name,
char *value,
size_t size)
{
int injected_error = 0;
if (CheckForInjectedError(path,"GETXATTR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
int res = lgetxattr(path, name, value, size);
if (res == -1)
return -errno;
return res;
}
int FailureInjectorFs::fifs_listxattr(
const char *path,
char *list,
size_t size)
{
int injected_error = 0;
if (CheckForInjectedError(path,"LISTXATTR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
int res = llistxattr(path, list, size);
if (res == -1)
return -errno;
return res;
}
int FailureInjectorFs::fifs_removexattr(
const char *path,
const char *name)
{
int injected_error = 0;
if (CheckForInjectedError(path,"REMOVEXATTR", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
int res = lremovexattr(path, name);
if (res == -1)
return -errno;
return 0;
}
#endif /* HAVE_SETXATTR */
off_t FailureInjectorFs::fifs_lseek(
const char *path,
off_t off,
int whence,
struct fuse_file_info *fi)
{
int fd;
off_t res;
int injected_error = 0;
if (CheckForInjectedError(path,"LSEEK", &injected_error)) {
cout << "Returning Injected error " << injected_error << "\n";
return -injected_error;
}
if (fi == NULL)
fd = open(path, O_RDONLY);
else
fd = fi->fh;
if (fd == -1)
return -errno;
res = lseek(fd, off, whence);
if (res == -1)
res = -errno;
if (fi == NULL)
close(fd);
return res;
}
void FailureInjectorFs::load_operations() {
mFuseOperationsVec.getattr = FailureInjectorFs::fifs_getattr;
mFuseOperationsVec.readlink = FailureInjectorFs::fifs_readlink;
mFuseOperationsVec.mknod = FailureInjectorFs::fifs_mknod;
mFuseOperationsVec.mkdir = FailureInjectorFs::fifs_mkdir;
mFuseOperationsVec.unlink = FailureInjectorFs::fifs_unlink;
mFuseOperationsVec.rmdir = FailureInjectorFs::fifs_rmdir;
mFuseOperationsVec.symlink = FailureInjectorFs::fifs_symlink;
mFuseOperationsVec.rename = FailureInjectorFs::fifs_rename;
mFuseOperationsVec.link = FailureInjectorFs::fifs_link;
mFuseOperationsVec.chmod = FailureInjectorFs::fifs_chmod;
mFuseOperationsVec.chown = FailureInjectorFs::fifs_chown;
mFuseOperationsVec.truncate = FailureInjectorFs::fifs_truncate;
mFuseOperationsVec.open = FailureInjectorFs::fifs_open;
mFuseOperationsVec.read = FailureInjectorFs::fifs_read;
mFuseOperationsVec.write = FailureInjectorFs::fifs_write;
mFuseOperationsVec.statfs = FailureInjectorFs::fifs_statfs;
mFuseOperationsVec.flush = NULL;
mFuseOperationsVec.release = FailureInjectorFs::fifs_release;
mFuseOperationsVec.fsync = FailureInjectorFs::fifs_fsync;
#ifdef HAVE_SETXATTR
mFuseOperationsVec.setxattr = FailureInjectorFs::fifs_setxattr;
mFuseOperationsVec.getxattr = FailureInjectorFs::fifs_getxattr;
mFuseOperationsVec.listxattr = FailureInjectorFs::fifs_listxattr;
mFuseOperationsVec.removexattr = FailureInjectorFs::fifs_removexattr;
#else
mFuseOperationsVec.setxattr = NULL;
mFuseOperationsVec.getxattr = NULL;
mFuseOperationsVec.listxattr = NULL;
mFuseOperationsVec.removexattr = NULL;
#endif
mFuseOperationsVec.opendir = NULL;
mFuseOperationsVec.readdir = FailureInjectorFs::fifs_readdir;
mFuseOperationsVec.releasedir = NULL;
mFuseOperationsVec.fsyncdir = NULL;
mFuseOperationsVec.init = FailureInjectorFs::fifs_init;
mFuseOperationsVec.destroy = NULL;
mFuseOperationsVec.access = FailureInjectorFs::fifs_access;
mFuseOperationsVec.create = FailureInjectorFs::fifs_create;
mFuseOperationsVec.lock = NULL;
#ifdef HAVE_UTIMENSAT
mFuseOperationsVec.utimens = FailureInjectorFs::fifs_utimens;
#else
mFuseOperationsVec.utimens = NULL;
#endif
mFuseOperationsVec.bmap = NULL;
mFuseOperationsVec.ioctl = NULL;
mFuseOperationsVec.poll = NULL;
mFuseOperationsVec.write_buf = NULL;
mFuseOperationsVec.read_buf = NULL;
mFuseOperationsVec.flock = NULL;
#ifdef HAVE_POSIX_FALLOCATE
mFuseOperationsVec.fallocate = FailureInjectorFs::fifs_fallocate;
#else
mFuseOperationsVec.fallocate = NULL;
#endif
mFuseOperationsVec.copy_file_range = NULL;
}
const struct fuse_operations *FailureInjectorFs::getOperations() {
return &mFuseOperationsVec;
}
int main(int argc, char *argv[])
{
FailureInjector *injector = new FailureInjector();
FailureInjectorFs noise_fs(injector);
noise_fs.load_operations();
umask(0);
fuse_main(argc, argv, noise_fs.getOperations(), NULL);
}