/**                                                                             
 * 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 separate 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);
}
