/**
@file
@brief    File system calls hooks
@details  Copyright (c) 2017-2022 Acronis International GmbH
@author   Roman Anufriev (Roman.Anufriev@acronis.com)
@since    $Id: $
*/

#include "fs_syscall_hooks.h"

#include "compat.h"
#include "debug.h"
#include "fs_event.h"
#include "memory.h"
#include "syscall_common.h"
#include "transport.h"
#include "transport_protocol.h"

#include <asm/ia32_unistd.h>	// for ia32_sys_call_table '__NR_ia32_*' system call function indices
#include <asm/processor-flags.h>

#include <linux/dcache.h>	// for 'd_unlinked()'
#include <linux/errno.h>
#include <linux/namei.h> // for 'user_path_at'
#include <linux/fdtable.h>
#include <linux/fs.h>		// for 'MAX_RW_COUNT'
#include <linux/fs_struct.h>	// for 'get_fs_pwd()'
#include <linux/pagemap.h>	// for 'PAGE_CACHE_MASK'
#include <linux/types.h>	// bool, [u]int(8|16|32|64)_t, pid_t

/*
 * For some syscalls (e.g. 'open()') we want to differentiate between three (not
 * two: SKIP and NOT_SKIP) possible cases for filtering optimizations:
 *
 * 1) SKIP: file exists and 'stat()' said that it's not regular, or 'stat()'
 *    failed for reasons other than ENOENT => we can skip sending our events
 *
 * 2) NOT_SKIP: file exists and 'stat()' said that it's regular (or whatever type
 *    of file we interested in) => we definitely need to send our events
 *
 * 3) PATH_NOT_EXIST: file doesn't exist (ENOENT) and:
 * 3.a) ~SKIP: file can't be created (i.e. user didn't pass 'O_CREAT' to syscall) => we
 *      can skip sending our events, as there is no info for us in PRE, and POST will fail.
 * 3.b) ~NOT_SKIP: file can be created (i.e. user passed 'O_CREAT' to syscall) => we can skip PRE,
 *      but want to execute 'stat()' before POST, as user may successfully create the file
 *      and we need to determine whether we interested in it on not.
 *
 *      TODO: introduce considering 'O_EXCL' as an optimization for predicting result of 'stat()'
 *      and thus avoid excessive invoking of 'stat()'
*/
enum skip_state {
	PATH_NOT_EXISTS = -1,	//  not exists ('stat()' = ENOENT)								=> if O_CREAT -- not skip, else -- skip
	NOT_SKIP,		// path exists ('stat()' = 0) and     normal (macros are good)					=> definitely not skip
	SKIP			// path exists ('stat()' = 0) and not normal (macros are bad) or any error beside ENOENT	=> definitely     skip
};

static int should_skip_file_path(int lookup_ret, struct path *path)
{
	struct dentry *dentry;
	struct inode *inode;
	umode_t mode;

	if (lookup_ret == -ENOENT) {
		return PATH_NOT_EXISTS;
	}

	if (lookup_ret) {
		return SKIP;
	}

	dentry = path->dentry;
	if (!dentry) {
		return SKIP;
	}

	inode = dentry->d_inode;
	if (!inode) {
		return SKIP;
	}

	if (!inode->i_sb) {
		return SKIP;
	}

	mode = inode->i_mode;
	if ((S_ISREG(mode)) || (S_ISDIR(mode)))
		return NOT_SKIP;
	else
		return SKIP;
}

static int should_skip_rename_dst(int lookup_ret, struct path *path)
{
	struct dentry *dentry;
	struct inode *inode;

	if (lookup_ret) {
		return SKIP;
	}

	dentry = path->dentry;
	if (!dentry) {
		return SKIP;
	}

	inode = dentry->d_inode;
	if (inode) {
		return NOT_SKIP;
	} else {
		return SKIP;
	}
}

static inline bool file_ok(struct file* file)
{
	struct dentry *dentry;
	struct inode *inode;
	if (!file)
		return false;

	dentry = file->f_path.dentry;
	if (!dentry)
		return false;

	inode = dentry->d_inode;
	if (!inode)
		return false;

	if (!inode->i_sb)
		return false;

	return true;
}

static inline void make_key_from_inode(file_key_t* key, const struct inode *inode) {
	key->ptr = (uint64_t) inode;
	key->ino = (uint64_t) inode->i_ino;
	key->gen = (uint64_t) inode->i_generation;
	key->dev = (uint64_t) inode->i_sb->s_dev;
}

static inline void make_key(file_key_t* key, const struct path *path) {
	return make_key_from_inode(key, path->dentry->d_inode);
}

enum creat_hook_type {
	CREAT_HOOK_TYPE,
	IA32_CREAT_HOOK_TYPE
};

DEFINE_CALL_ORIGINAL_SYSCALL_HANDLER(creat, 2,
	const char __user *, pathname,
	umode_t, mode)
{
	switch (hook_type) {
	case CREAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, creat), pathname, mode);
	case IA32_CREAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, creat), pathname, mode);
	default:
		return -EINVAL;
	}
}

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(creat)
{
	switch (hook_type) {
	case CREAT_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, creat);
	case IA32_CREAT_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, creat);
	default:
		return -EINVAL;
	}
}

// man creat: 'creat()' is equivalent to 'open()' with flags equal to "O_CREAT|O_WRONLY|O_TRUNC".
DEFINE_SYSCALL_HANDLER(creat, 2, const char __user *, pathname, umode_t, mode)
{
	enum skip_state skipping = 0;
	syscall_hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	char *dst_name_buf = NULL;
	char *dst_name; // a pointer inside 'dst_name_buf'
	long block = 0;
	struct path path = (struct path){};
	file_key_t key;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_EXEC)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_CREATE)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_CREATE_EX)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_OPEN_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask)) {
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
		goto out;
	}

	ret = user_path_at(AT_FDCWD, pathname, 0, &path);
	skipping = should_skip_file_path(ret, &path);
	if (skipping == SKIP) {
		DPRINTF("skipped sending FILE_(PRE)_CREAT, hook_type=%d, because pathname (__user ptr=%p) is not trackable",
			hook_type, pathname);

		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
		goto err_exit;
	}

	if ((dst_name_buf = mem_alloc(PATH_MAX)) == NULL) {
		DPRINTF("'%s()' for '%s' failed", "mem_alloc", "dst_name_buf");

		SET_HOOK_RETURN(-ENOMEM);
		goto err_exit;
	}

	if (skipping == NOT_SKIP) {
		// If 'skipping' is 'NOT_SKIP' path must exist so use it
		dst_name = d_path(&path, dst_name_buf, PATH_MAX);
		if (IS_ERR(dst_name)) {
			DPRINTF("'d_path()' failure %i", ret);

			SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
			goto err_free_name_buf;
		}

		DPRINTF("hook_type=%d calling fs_event_pre_creat(path=%s)",
				hook_type, dst_name);
		make_key(&key, &path);
		block = fs_event_pre_create(dst_name, &path);
		block = block ? block : fs_event_pre_open_ex(dst_name, &key, O_CREAT | O_WRONLY | O_TRUNC, &path);
		DPRINTF("hook_type=%d finished 'fs_event_pre_creat(path=%s)",
				hook_type, dst_name);

		if (block) {
			SET_HOOK_RETURN(block);
			goto err_free_name_buf;
		}
	} else {
		DPRINTF("skipped sending FILE_PRE_CREAT (hook_type=%d, path=%s), because \"skipping != NOT_SKIP\"",
				hook_type, dst_name);
	}

	if (skipping == PATH_NOT_EXISTS) {
		CALL_ORIGINAL_SYSCALL_HANDLER(creat, hook_type, pathname, mode);
	} else {
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
	}

	if (skipping == PATH_NOT_EXISTS && !IS_ERR_VALUE(ret)) {
		struct compat_fd f = compat_fdget(ret);
		if (file_ok(f.file)) {
			struct path* fpath = &f.file->f_path;
			make_key(&key, fpath);
			dst_name = d_path(fpath, dst_name_buf, PATH_MAX);
			compat_fdput(f);
			if (!IS_ERR(dst_name)) {
				DPRINTF("hook_type=%d calling fs_event_creat(ret_val=%ld, path=%s)",
						hook_type, ret, dst_name);
				fs_event_create_ex(ret, dst_name, &key, O_CREAT | O_WRONLY | O_TRUNC, fpath);
				DPRINTF("hook_type=%d finished 'fs_event_creat(ret_val=%ld, path=%s)",
						hook_type, ret, dst_name);
			} else {
				DPRINTF("skipped sending FILE_CREAT (hook_type=%d ret_val=%ld), because can't get path",
						hook_type, ret);
			}
		} else {
			DPRINTF("skipped sending FILE_CREAT (hook_type=%d ret_val=%ld), because can't get fd",
					hook_type, ret);
		}
	} else {
		DPRINTF("skipped sending FILE_CREAT, hook_type=%d, because path=%s isn't trackable", hook_type, dst_name);
	}

err_free_name_buf:
	mem_free(dst_name_buf);

err_exit:
	path_put(&path);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, creat, 2, const char __user *, pathname, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(creat, CREAT_HOOK_TYPE, pathname, mode);
}

DEFINE_SYSCALL_HOOK(ia32_sys, creat, 2, const char __user *, pathname, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(creat, IA32_CREAT_HOOK_TYPE, pathname, mode);
}

enum open_hook_type {
	OPEN_HOOK_TYPE,
	IA32_OPEN_HOOK_TYPE,

	OPENAT_HOOK_TYPE,
	IA32_OPENAT_HOOK_TYPE
};

DEFINE_CALL_ORIGINAL_SYSCALL_HANDLER(open, 4,
	int, dfd, const char __user *, filename, int, flags, umode_t, mode)
{
	switch (hook_type) {
	case OPEN_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, open), filename, flags, mode);
		break;
	case IA32_OPEN_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, open), filename, flags, mode);
		break;
	case OPENAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, openat), dfd, filename, flags, mode);
		break;
	case IA32_OPENAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, openat), dfd, filename, flags, mode);
		break;
	default:
		return -EINVAL;
	}
}

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(open)
{
	switch (hook_type) {
	case OPEN_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, open);
		break;
	case IA32_OPEN_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, open);
		break;
	case OPENAT_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, openat);
		break;
	case IA32_OPENAT_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, openat);
		break;
	default:
		return -EINVAL;
	}
}

DEFINE_SYSCALL_HANDLER(open, 4, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
{
	enum skip_state skipping = 0;
	syscall_hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	char *dst_name_buf = NULL;
	char *dst_name;
	long block = 0;
	file_key_t key;
	struct path path = (struct path){};
	int lookup_flags = 0;
	bool is_create_event;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_EXEC)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_OPEN)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_CREATE_EX)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_OPEN_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		goto out;
	}

#if defined(O_DIRECTORY) && defined(LOOKUP_DIRECTORY)
	if (flags & O_DIRECTORY)
		lookup_flags |= LOOKUP_DIRECTORY;
#endif
#if defined(O_NOFOLLOW) && defined(LOOKUP_FOLLOW)
	if (!(flags & O_NOFOLLOW))
		lookup_flags |= LOOKUP_FOLLOW;
#endif

	ret = user_path_at(dfd, filename, lookup_flags, &path);
	skipping = should_skip_file_path(ret, &path);
	if ((skipping == SKIP) ||
	    ((skipping == PATH_NOT_EXISTS) && !(flags & O_CREAT))) {
		DPRINTF("skipped sending FILE_(PRE)_OPEN hook_type=%d because filename (__user ptr=%p) is not trackable",
			hook_type, filename);

		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		goto err_exit;
	}

	if ((dst_name_buf = mem_alloc(PATH_MAX)) == NULL) {
		DPRINTF("'%s()' for '%s' failed", "mem_alloc", "dst_name_buf");
		SET_HOOK_RETURN(-ENOMEM);
		goto err_exit;
	}

	if (skipping == NOT_SKIP) {
		// If 'skipping' is 'NOT_SKIP' path must exist so use it
		dst_name = d_path(&path, dst_name_buf, PATH_MAX);
		if (IS_ERR(dst_name)) {
			DPRINTF("'d_path()' failure %i", ret);

			SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
			goto err_free_name_buf;
		}

		DPRINTF("hook_type=%d calling fs_event_pre_open(path=%s flags=0o%o/0x%x)",
				hook_type, dst_name, flags, flags);
		make_key(&key, &path);
		block = fs_event_pre_open(dst_name, flags, &path);
		block = block ? block : fs_event_pre_open_ex(dst_name, &key, flags, &path);
		DPRINTF("hook_type=%d finished 'fs_event_pre_open(path=%s flags=0o%o/0x%x)",
				hook_type, dst_name, flags, flags);

		if (block) {
			SET_HOOK_RETURN(block);
			goto err_free_name_buf;
		}
	} else {
		DPRINTF("skipped sending FILE_PRE_OPEN (hook_type=%d path=%s flags=0o%o/0x%x) because \"skipping != NOT_SKIP\"",
				hook_type, dst_name, flags, flags);
	}

	is_create_event = skipping == PATH_NOT_EXISTS && (flags & O_CREAT);
	if (is_create_event) {
		CALL_ORIGINAL_SYSCALL_HANDLER(open, hook_type, dfd, filename, flags, mode);
	} else {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
	}

	if (is_create_event && !IS_ERR_VALUE(ret)) {
		struct compat_fd f = compat_fdget(ret);
		if (file_ok(f.file)) {
			struct path* fpath = &f.file->f_path;
			make_key(&key, fpath);
			dst_name = d_path(fpath, dst_name_buf, PATH_MAX);
			compat_fdput(f);
			if (!IS_ERR(dst_name)) {
				DPRINTF("hook_type=%d calling fs_event_creat(ret_val=%ld, path=%s)",
						hook_type, ret, dst_name);
				fs_event_create_ex(ret, dst_name, &key, flags, fpath);
				DPRINTF("hook_type=%d finished 'fs_event_creat(ret_val=%ld, path=%s)",
						hook_type, ret, dst_name);
			} else {
				DPRINTF("skipped sending FILE_CREAT (hook_type=%d ret_val=%ld), because can't get path",
						hook_type, ret);
			}
		} else {
			DPRINTF("skipped sending FILE_CREAT (hook_type=%d ret_val=%ld), because can't get fd",
					hook_type, ret);
		}
	} else {
		DPRINTF("skipped sending FILE_CREAT (hook_type=%d path=%s flags=0o%o/0x%x)",
				hook_type, dst_name, flags, flags);
	}

err_free_name_buf:
	mem_free(dst_name_buf);

err_exit:
	path_put(&path);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, open, 3, const char __user *, filename, int, flags, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(open, OPEN_HOOK_TYPE, AT_FDCWD, filename, flags, mode);
}

DEFINE_SYSCALL_HOOK(ia32_sys, open, 3, const char __user *, filename, int, flags, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(open, IA32_OPEN_HOOK_TYPE, AT_FDCWD, filename, flags, mode);
}

DEFINE_SYSCALL_HOOK(sys, openat, 4, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(open, OPENAT_HOOK_TYPE, dfd, filename, flags, mode);
}

DEFINE_SYSCALL_HOOK(ia32_sys, openat, 4, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
{
	return CALL_SYSCALL_HANDLER(open, IA32_OPENAT_HOOK_TYPE, dfd, filename, flags, mode);
}

struct fd_mini_info
{
	umode_t mode;
	int unlinked;
	int flags;
	unsigned long magic;
};

static void fd_get_mini_info(const struct file *file, struct fd_mini_info *info)
{
	struct inode *inode = file_inode_compat(file);
	info->mode = inode->i_mode;
	info->flags = file->f_flags;
	info->magic = inode->i_sb->s_magic;
	info->unlinked = d_unlinked(file->f_path.dentry);
}

enum close_hook_type
{
	CLOSE_HOOK_TYPE,
	IA32_CLOSE_HOOK_TYPE,
};

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(close)
{
	switch (hook_type)
	{
	case CLOSE_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, close);
		break;
	case IA32_CLOSE_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, close);
		break;
	default:
		return -EINVAL;
	}
}

#ifndef NSFS_MAGIC
#define NSFS_MAGIC 0x6e736673
#endif

#ifndef O_ACCMODE
#define O_ACCMODE	00000003
#endif

static int magic_ok(int magic)
{
	return magic != NSFS_MAGIC;
}

DEFINE_SYSCALL_HANDLER(close, 1, unsigned int, fd)
{
	syscall_hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	char *name_buf = NULL, *filename = NULL;
	struct fd_mini_info info;
	struct path *path;
	struct compat_fd f;
	file_key_t key;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_CLOSE_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto out;
	}

	f = compat_fdget(fd);
	if (!file_ok(f.file))
	{
		DPRINTF("'%s()' failed", "compat_fdget");
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto out;
	}

	fd_get_mini_info(f.file, &info);

	if (!S_ISREG(info.mode) || (info.unlinked) || !magic_ok(info.magic) || !(info.flags & O_ACCMODE))
	{
		DPRINTF("skipped sending FILE_(PRE)_CLOSE hook_type=%d, because filename (fd=%lu) isn't trackable", hook_type, fd);
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto err_exit;
	}

	if ((name_buf = mem_alloc(PATH_MAX)) == NULL)
	{
		IPRINTF("'%s()' for '%s' failed", "mem_alloc", "name_buf");
		SET_HOOK_RETURN(-ENOMEM);
		goto err_exit;
	}

	path = &f.file->f_path;
	filename = d_path(path, name_buf, PATH_MAX);
	if (IS_ERR(filename))
	{
		ret = PTR_ERR(filename);
		DPRINTF("'%s()' [%u] failed %ld", "d_path", fd, ret);
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto err_free_name_buf;
	}

	DPRINTF("'fd_get_info_for_path()': filename=%s, f_flags=0o%o/0x%x",
			filename,
			info.flags, info.flags);

	make_key(&key, path);
	DPRINTF("hook_type=%d calling fs_event_pre_close_ex(path=%s flags=0o%o/0x%x)",
			hook_type, filename, info.flags, info.flags);
	fs_event_pre_close_ex(filename, info.flags, path, &key);
	DPRINTF("hook_type=%d finished fs_event_pre_close_ex(path=%s flags=0o%o/0x%x)",
			hook_type, filename, info.flags, info.flags);

	SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);

err_free_name_buf:
	if (name_buf != NULL)
		mem_free(name_buf);

err_exit:
	compat_fdput(f);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, close, 1, unsigned int, fd)
{
	return CALL_SYSCALL_HANDLER(close, CLOSE_HOOK_TYPE, fd);
}

DEFINE_SYSCALL_HOOK(ia32_sys, close, 1, unsigned int, fd)
{
	return CALL_SYSCALL_HANDLER(close, IA32_CLOSE_HOOK_TYPE, fd);
}

#define IS_COMPAT_READ_HOOK_TYPE(read_hook_type) ((read_hook_type) >= 512)

// from fs/read_write.c:
static inline loff_t pos_from_hilo(unsigned long high, unsigned long low)
{
#ifndef HALF_LONG_BITS
	#define HALF_LONG_BITS (BITS_PER_LONG / 2)
#endif
	return (((loff_t)high << HALF_LONG_BITS) << HALF_LONG_BITS) | low;
}

/*
 * Get total length of all I/O vectors passed to '*readv()' and '*writev()'
 *
 * RETURN VALUE: ssize_t
 */
#define get_total_len(vec, vlen)						\
({										\
	int i = 0;								\
	ssize_t ret = 0;							\
	/* By default, 'vec' is ptr to const, so 'typeof' will produce		\
	   const type which is not suitable for this func. In order to remove	\
	   const'ness we can do 'x + 0' trick. */				\
	__typeof__(vec->iov_len + 0) len = 0;					\
										\
	for (i = 0; i < vlen; i++) {						\
		if (get_user(len, &(vec[i].iov_len))) {				\
			DPRINTF("'%s()' failed", "get_user");			\
			ret = -EFAULT;						\
			break;							\
		}								\
										\
		if (((ssize_t)len) <= 0)					\
			continue;						\
										\
		if (len > MAX_RW_COUNT - ret) {					\
			ret = MAX_RW_COUNT;					\
			break;							\
		}								\
										\
		ret += len;							\
										\
		DPRINTF("'%s()': cur_tot_len=%zd, len=%zd", "get_total_len",	\
				ret, (ssize_t)len);				\
	}									\
										\
	ret;									\
})

enum read_hook_type
{
	READ_HOOK_TYPE,
	READV_HOOK_TYPE,
	PREAD_HOOK_TYPE,
	PREADV_HOOK_TYPE,
	PREADV2_HOOK_TYPE,
	IA32_READ_HOOK_TYPE,
	/*
	 * In "/arch/x86/syscalls/syscall_64.tbl" all ordinary syscall numbers
	 * end before 512, as from 512 start x32-specific syscalls.
	 *
	 * Similar logic (non-ordinary syscalls start from 512) is chosen here,
	 * as we sometimes need to distinguish 'compat' syscalls from 'ordinary'
	 * ones.
	 */
	COMPAT_READV_HOOK_TYPE = 512,
	COMPAT_PREAD_HOOK_TYPE,
	COMPAT_PREADV_HOOK_TYPE,
	COMPAT_PREADV2_HOOK_TYPE,
};

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(read)
{
	switch (hook_type) {
	case READ_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, read);
	case IA32_READ_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, read);
	case PREAD_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, pread64);
	case READV_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, readv);
	case PREADV_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, preadv);
	case PREADV2_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, preadv2);
	case COMPAT_PREAD_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, pread64);
	case COMPAT_READV_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, readv);
	case COMPAT_PREADV_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, preadv);
	case COMPAT_PREADV2_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, preadv2);
	default:
		return -EINVAL;
	}
}

DEFINE_SYSCALL_HANDLER(read, 9, unsigned long, fd, size_t, count,
					   loff_t, pos,
					   const struct iovec __user *, vec,
					   unsigned long, vlen,
					   unsigned long, pos_l,
					   unsigned long, pos_h,
					   const struct compat_iovec __user *, cvec,
					   compat_ulong_t, cvlen)
{
	syscall_hook_ret_t syscall_hook_ret;
	ssize_t ret_len = -EINVAL;
	loff_t offset = -1;
	struct fd_mini_info info;
	file_key_t key;
	struct path *path;
	struct compat_fd f;
	int is_compat = IS_COMPAT_READ_HOOK_TYPE(hook_type);
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_READ_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		goto out;
	}

	f = compat_fdget(fd);
	if (!file_ok(f.file))
	{
		DPRINTF("'%s()' failed", "compat_fdget");
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		goto out;
	}

	fd_get_mini_info(f.file, &info);
	if ((!S_ISREG(info.mode) && !S_ISDIR(info.mode)) || (info.unlinked) || !magic_ok(info.magic)) {
		DPRINTF("skipped sending FILE_(PRE)_READ, hook_type=%d, because filename (fd=%lu) isn't trackable", hook_type, fd);
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		goto err_exit;
	}

	path = &f.file->f_path;
	make_key(&key, path);
	offset = f.file->f_pos;

	DPRINTF("'fd_get_info_for_read()': f_flags=0o%o/0x%x, offset=%lld, mode=0o%o/0x%x",
			info.flags, info.flags,
			offset,
			info.mode, info.mode);

	switch (hook_type) {
	case PREAD_HOOK_TYPE:
		offset = pos;
		break;
	case READ_HOOK_TYPE:
		break;

	case COMPAT_PREAD_HOOK_TYPE:
		offset = pos_from_hilo(pos_h, pos_l);
		break;
	case IA32_READ_HOOK_TYPE:
		break;

	case COMPAT_PREADV2_HOOK_TYPE:
	case PREADV2_HOOK_TYPE:
	case COMPAT_PREADV_HOOK_TYPE:
	case PREADV_HOOK_TYPE:
		offset = pos_from_hilo(pos_h, pos_l);
		if (!is_compat)
			ret_len = get_total_len(vec, vlen);
		else
			ret_len = get_total_len(cvec, cvlen);

		if (ret_len < 0)
		{
			SET_HOOK_RETURN(ret_len);
			goto err_exit;
		}

		count = ret_len;
		break;

	case COMPAT_READV_HOOK_TYPE:
	case READV_HOOK_TYPE:
		if (!is_compat)
			ret_len = get_total_len(vec, vlen);
		else
			ret_len = get_total_len(cvec, cvlen);

		if (ret_len < 0)
		{
			SET_HOOK_RETURN(ret_len);
			goto err_exit;
		}

		count = ret_len;
		break;

	default:
		DPRINTF("unknown read hook type: %d", hook_type);
		SET_HOOK_RETURN(-EINVAL);
		goto err_exit;
	}

	DPRINTF("hook_type=%d calling fs_event_pre_read_ex(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, info.flags, info.flags, offset, count);

	fs_event_pre_read_ex(&key, info.flags, offset, count);

	DPRINTF("hook_type=%d finished 'fs_event_pre_read_ex(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, info.flags, info.flags, offset, count);

	SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);

err_exit:
	compat_fdput(f);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, read, 3, unsigned int, fd, char __user *, buf, size_t, count)
{
	return CALL_SYSCALL_HANDLER(read, READ_HOOK_TYPE, (unsigned long)fd, count,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, /*pos_l*/ 0, /*pos_h*/ 0,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_SYSCALL_HOOK(sys, pread64, 4, unsigned int, fd, char __user *, buf,
					size_t, count, loff_t, pos)
{
	return CALL_SYSCALL_HANDLER(read, PREAD_HOOK_TYPE, (unsigned long)fd, count,
							   pos, /*vec*/ NULL, /*vlen*/ 0, /*pos_l*/ 0, /*pos_h*/ 0,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_SYSCALL_HOOK(sys, readv, 3, unsigned long, fd, const struct iovec __user *, vec,
					unsigned long, vlen)
{
	return CALL_SYSCALL_HANDLER(read, READV_HOOK_TYPE, fd, /*count*/ 0,
							   /*pos*/ 0, vec, vlen, /*pos_l*/ 0, /*pos_h*/ 0,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_SYSCALL_HOOK(sys, preadv, 5, unsigned long, fd, const struct iovec __user *, vec,
					unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h)
{
	return CALL_SYSCALL_HANDLER(read, PREADV_HOOK_TYPE, fd, /*count*/ 0,
							   /*pos*/ 0, vec, vlen, pos_l, pos_h,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_SYSCALL_HOOK(sys, preadv2, 6, unsigned long, fd, const struct iovec __user *, vec,
					unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h, int, flags)
{
	return CALL_SYSCALL_HANDLER(read, PREADV2_HOOK_TYPE, fd, /*count*/ 0,
							   /*pos*/ 0, vec, vlen, pos_l, pos_h,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_SYSCALL_HOOK(ia32_sys, read, 3, unsigned int, fd, char __user *, buf, size_t, count)
{
	return CALL_SYSCALL_HANDLER(read, IA32_READ_HOOK_TYPE, (unsigned long)fd, count,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, /*pos_l*/ 0, /*pos_h*/ 0,
							   /*cvec*/ NULL, /*cvlen*/ 0);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, pread64, 5, unsigned int, fd, const char __user *, ubuf, u32, count,
						   u32, poslo, u32, poshi)
{
	return CALL_SYSCALL_HANDLER(read, COMPAT_PREAD_HOOK_TYPE, (unsigned long)fd, (size_t)count,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, (unsigned long)poslo, (unsigned long)poshi,
							   /*cvec*/ NULL, /*cvlen)*/ 0);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, readv, 3,
						   compat_ulong_t, fd,
						   const struct compat_iovec __user *, cvec,
						   compat_ulong_t, cvlen)
{
	return CALL_SYSCALL_HANDLER(read, COMPAT_READV_HOOK_TYPE, (unsigned long)fd, /*count*/ 0,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, /*pos_l*/ 0, /*pos_h*/ 0,
							   cvec, cvlen);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, preadv, 5,
						   compat_ulong_t, fd,
						   const struct compat_iovec __user *, cvec,
						   compat_ulong_t, cvlen,
						   u32, pos_low,
						   u32, pos_high)
{
	return CALL_SYSCALL_HANDLER(read, COMPAT_PREADV_HOOK_TYPE, (unsigned long)fd, /*count*/ 0,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, (unsigned long)pos_low, (unsigned long)pos_high,
							   cvec, cvlen);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, preadv2, 6,
						   compat_ulong_t, fd,
						   const struct compat_iovec __user *, cvec,
						   compat_ulong_t, cvlen,
						   u32, pos_low,
						   u32, pos_high,
						   int, flags)
{
	return CALL_SYSCALL_HANDLER(read, COMPAT_PREADV2_HOOK_TYPE, (unsigned long)fd, 0,
							   /*pos*/ 0, /*vec*/ NULL, /*vlen*/ 0, (unsigned long)pos_low, (unsigned long)pos_high,
							   cvec, cvlen);
}

enum write_hook_type {
	WRITE_HOOK_TYPE,
	IA32_WRITE_HOOK_TYPE,

	PWRITE_HOOK_TYPE,
	WRITEV_HOOK_TYPE,
	PWRITEV_HOOK_TYPE,
	PWRITEV2_HOOK_TYPE,

	/*
	 * In "/arch/x86/syscalls/syscall_64.tbl" all ordinary syscall numbers
	 * end before 512, as from 512 start x32-specific syscalls.
	 *
	 * Similar logic (non-ordinary syscalls start from 512) is chosen here,
	 * as we sometimes need to distinguish 'compat' syscalls from 'ordinary'
	 * ones.
	 */
	COMPAT_PWRITE_HOOK_TYPE = 512,
	COMPAT_WRITEV_HOOK_TYPE,
	COMPAT_PWRITEV_HOOK_TYPE,
	COMPAT_PWRITEV2_HOOK_TYPE
};

#define IS_COMPAT_WRITE_HOOK_TYPE(write_hook_type) ((write_hook_type) >= 512)

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(write)
{
	switch (hook_type) {
	case WRITE_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, write);
	case IA32_WRITE_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, write);
	case PWRITE_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, pwrite64);
	case WRITEV_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, writev);
	case PWRITEV_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, pwritev);
	case PWRITEV2_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, pwritev2);
	case COMPAT_PWRITE_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, pwrite64);
	case COMPAT_WRITEV_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, writev);
	case COMPAT_PWRITEV_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, pwritev);
	case COMPAT_PWRITEV2_HOOK_TYPE:
		return COMPAT_SYSCALL_ORIG_FN(compat_sys, pwritev2);
	default:
		return -EINVAL;
	}
}

DEFINE_SYSCALL_HANDLER(write, 11,
	unsigned long, fd,
	const char __user *, buf,
	size_t, count,
	loff_t, pos,
	const struct iovec __user *, vec,
	unsigned long, vlen,
	unsigned long, pos_l,
	unsigned long, pos_h,
	const struct compat_iovec __user *, cvec,
	compat_ulong_t, cvlen,
	int, flags)
{
	syscall_hook_ret_t syscall_hook_ret;
	ssize_t ret_len = -EINVAL;
	loff_t offset = -1;
	struct fd_mini_info info;
	int is_compat = IS_COMPAT_WRITE_HOOK_TYPE(hook_type);
	long block = 0;
	file_key_t key;
	struct path *path;
	struct compat_fd f;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_WRITE_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto out;
	}

	f = compat_fdget(fd);
	if (!file_ok(f.file))
	{
		DPRINTF("'%s()' failed", "compat_fdget");
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto out;
	}

	fd_get_mini_info(f.file, &info);
	if ((!S_ISREG(info.mode) && !S_ISDIR(info.mode)) || (info.unlinked) || !magic_ok(info.magic)) {
		DPRINTF("skipped sending FILE_(PRE)_WRITE, hook_type=%d, because filename (fd=%lu) isn't trackable", hook_type, fd);
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto err_exit;
	}

	path = &f.file->f_path;
	make_key(&key, path);
	offset = f.file->f_pos;

	DPRINTF("'fd_get_info_for_write()': f_flags=0o%o/0x%x, offset=%lld, mode=0o%o/0x%x",
			info.flags, info.flags,
			offset,
			info.mode, info.mode);

	switch (hook_type) {

	case PWRITE_HOOK_TYPE:
		offset = pos;
	case WRITE_HOOK_TYPE:
		break;

	case COMPAT_PWRITE_HOOK_TYPE:
		offset = pos_from_hilo(pos_h, pos_l);
		break;
	case IA32_WRITE_HOOK_TYPE:
		break;

	case COMPAT_PWRITEV2_HOOK_TYPE:
	case PWRITEV2_HOOK_TYPE:
	case COMPAT_PWRITEV_HOOK_TYPE:
	case PWRITEV_HOOK_TYPE:
		offset = pos_from_hilo(pos_h, pos_l);
		if (!is_compat)
			ret_len = get_total_len(vec, vlen);
		else
			ret_len = get_total_len(cvec, cvlen);

		if (ret_len < 0) {
			SET_HOOK_RETURN(ret_len);
			goto err_exit;
		}

		count = ret_len;
		break;

	case COMPAT_WRITEV_HOOK_TYPE:
	case WRITEV_HOOK_TYPE:
		if (!is_compat)
			ret_len = get_total_len(vec, vlen);
		else
			ret_len = get_total_len(cvec, cvlen);

		if (ret_len < 0) {
			SET_HOOK_RETURN(ret_len);
			goto err_exit;
		}

		count = ret_len;
		break;

	default:
		DPRINTF("unknown write hook type: %d", hook_type);
		SET_HOOK_RETURN(-EINVAL);
		goto err_exit;
	}

	DPRINTF("hook_type=%d calling fs_event_pre_write_ex(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, info.flags, info.flags, offset, count);
	/*
	 * Syscall 'pwritev2()':
	 * 1) exists only on Debian 9 (of all our supported distros)
	 * 2) used relatively rare (compared to other '*write*()' syscalls)
	 *
	 * so, not passing 'flags' from 'pwritev2()' here in order to keep our
	 * kernel/user space dataflow slim
	 */
	block = fs_event_pre_write_ex(&key, info.flags, offset, count, path);
	DPRINTF("hook_type=%d finished 'fs_event_pre_write_ex(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, info.flags, info.flags, offset, count);

	if (block) {
		SET_HOOK_RETURN(block);
		goto err_exit;
	}

	SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);

err_exit:
	compat_fdput(f);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, write, 3,
	unsigned int, fd,
	const char __user *, buf,
	size_t, count)
{
	return CALL_SYSCALL_HANDLER(write, WRITE_HOOK_TYPE, fd, buf, count, 0, NULL, 0, 0, 0, NULL, 0, 0);
}

DEFINE_SYSCALL_HOOK(ia32_sys, write, 3,
	unsigned int, fd,
	const char __user *, buf,
	size_t, count)
{
	return CALL_SYSCALL_HANDLER(write, IA32_WRITE_HOOK_TYPE, fd, buf, count, 0, NULL, 0, 0, 0, NULL, 0, 0);
}

DEFINE_SYSCALL_HOOK(sys, pwrite64, 4,
	unsigned int, fd,
	const char __user *, buf,
	size_t, count,
	loff_t, pos)
{
	return CALL_SYSCALL_HANDLER(write, PWRITE_HOOK_TYPE, fd, buf, count, pos, NULL, 0, 0, 0, NULL, 0, 0);
}

DEFINE_SYSCALL_HOOK(sys, writev, 3,
	unsigned long, fd,
	const struct iovec __user *, vec,
	unsigned long, vlen)
{
	return CALL_SYSCALL_HANDLER(write, WRITEV_HOOK_TYPE, fd, NULL, 0, 0, vec, vlen, 0, 0, NULL, 0, 0);
}

DEFINE_SYSCALL_HOOK(sys, pwritev, 5,
	unsigned long, fd,
	const struct iovec __user *, vec,
	unsigned long, vlen,
	unsigned long, pos_l,
	unsigned long, pos_h)
{
	return CALL_SYSCALL_HANDLER(write, PWRITEV_HOOK_TYPE, fd, NULL, 0, 0, vec, vlen, pos_l, pos_h, NULL, 0, 0);
}

DEFINE_SYSCALL_HOOK(sys, pwritev2, 6,
	unsigned long, fd,
	const struct iovec __user *, vec,
	unsigned long, vlen,
	unsigned long, pos_l,
	unsigned long, pos_h,
	int, flags)
{
	return CALL_SYSCALL_HANDLER(write, PWRITEV2_HOOK_TYPE, fd, NULL, 0, 0, vec, vlen, pos_l, pos_h, NULL, 0, flags);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, pwrite64, 5,
	unsigned int, fd,
	const char __user *, ubuf,
	u32, count,
	u32, poslo,
	u32, poshi)
{
	return CALL_SYSCALL_HANDLER(write, COMPAT_PWRITE_HOOK_TYPE, fd, ubuf, count, 0, NULL, 0, poslo, poshi, NULL, 0, 0);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, writev, 3,
	compat_ulong_t, fd,
	const struct compat_iovec __user *, vec,
	compat_ulong_t, vlen)
{
	return CALL_SYSCALL_HANDLER(write, COMPAT_WRITEV_HOOK_TYPE, fd, NULL, 0, 0, NULL, 0, 0, 0, vec, vlen, 0);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, pwritev, 5,
	compat_ulong_t, fd,
	const struct compat_iovec __user *, vec,
	compat_ulong_t, vlen,
	u32, pos_low,
	u32, pos_high)
{
	return CALL_SYSCALL_HANDLER(write, COMPAT_PWRITEV_HOOK_TYPE, fd, NULL, 0, 0, NULL, 0, pos_low, pos_high, vec, vlen, 0);
}

DEFINE_COMPAT_SYSCALL_HOOK(compat_sys, pwritev2, 6,
	compat_ulong_t, fd,
	const struct compat_iovec __user *, vec,
	compat_ulong_t, vlen,
	u32, pos_low,
	u32, pos_high,
	int, flags)
{
	return CALL_SYSCALL_HANDLER(write, COMPAT_PWRITEV2_HOOK_TYPE, fd, NULL, 0, 0, NULL, 0, pos_low, pos_high, vec, vlen, flags);
}

enum rename_hook_type {
	RENAME_HOOK_TYPE,
	IA32_RENAME_HOOK_TYPE,

	RENAMEAT_HOOK_TYPE,
	IA32_RENAMEAT_HOOK_TYPE,

	RENAMEAT2_HOOK_TYPE,
	IA32_RENAMEAT2_HOOK_TYPE
};

DEFINE_CALL_ORIGINAL_SYSCALL_HANDLER(rename, 5,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname,
	unsigned int, flags)
{
	switch (hook_type) {
	case RENAME_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, rename), oldname, newname);
	case IA32_RENAME_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, rename), oldname, newname);
	case RENAMEAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, renameat), olddfd, oldname, newdfd, newname);
	case IA32_RENAMEAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, renameat), olddfd, oldname, newdfd, newname);
	case RENAMEAT2_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, renameat2), olddfd, oldname, newdfd, newname, flags);
	case IA32_RENAMEAT2_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, renameat2), olddfd, oldname, newdfd, newname, flags);
	default:
		return -EINVAL;
	}
}

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(rename)
{
	switch (hook_type) {
	case RENAME_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, rename);
	case IA32_RENAME_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, rename);
	case RENAMEAT_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, renameat);
	case IA32_RENAMEAT_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, renameat);
	case RENAMEAT2_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, renameat2);
	case IA32_RENAMEAT2_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, renameat2);
	default:
		return -EINVAL;
	}
}

DEFINE_SYSCALL_HANDLER(rename, 5,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname,
	unsigned int, flags)
{
	syscall_hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	file_key_t source_key, target_key;
	char *dst_oldname_buf = NULL, *dst_oldname = NULL;
	char *dst_newname_buf = NULL, *dst_newname = NULL;
	long block = 0;
	struct path oldpath = (struct path){};
	struct path newpath = (struct path){};
	int is_newpath_ok = 0;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_RENAME)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_PRE_RENAME_EX)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_RENAME_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		goto out;
	}

	ret = user_path_at(olddfd, oldname, 0, &oldpath);
	if (should_skip_file_path(ret, &oldpath) != NOT_SKIP) {
		DPRINTF("skipped sending FILE_(PRE)_RENAME, hook_type=%d, because 'oldname' isn't trackable", hook_type);
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		goto err_exit;
	}

	if ((dst_oldname_buf = mem_alloc(2 * PATH_MAX)) == NULL) {
		DPRINTF("'%s()' for '%s' failed", "mem_alloc", "dst_name_buf");
		SET_HOOK_RETURN(-ENOMEM);
		goto err_exit;
	}

	dst_oldname = d_path(&oldpath, dst_oldname_buf, PATH_MAX);
	if (IS_ERR(dst_oldname)) {
		DPRINTF("'d_path()' failure %i", ret);
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		goto err_free_name_buf;
	}

	dst_newname_buf = dst_oldname_buf + PATH_MAX;
	make_key(&source_key, &oldpath);

	ret = user_path_at(newdfd, newname, 0, &newpath);
	is_newpath_ok = NOT_SKIP == should_skip_rename_dst(ret, &newpath);
	if (is_newpath_ok) {
		dst_newname = d_path(&newpath, dst_newname_buf, PATH_MAX);
		if (IS_ERR(dst_newname)) {
			DPRINTF("'d_path()' failure %i", ret);
			SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
			goto err_free_name_buf;
		}
		make_key(&target_key, &newpath);
	}

	DPRINTF("hook_type=%d calling fs_event_pre_rename(oldname=%s, newname=%s, flags=%u)",
			hook_type, dst_oldname, is_newpath_ok ? dst_newname : "?", flags);
	block = fs_event_pre_rename_ex(dst_oldname, &source_key, is_newpath_ok ? dst_newname : NULL, is_newpath_ok ? &target_key : NULL, flags, &oldpath, is_newpath_ok ? &newpath : NULL);
	DPRINTF("hook_type=%d finished 'fs_event_pre_rename(oldname=%s, newname=%s, flags=%u)",
			hook_type, dst_oldname, is_newpath_ok ? dst_newname : "?", flags);

	if (block) {
		SET_HOOK_RETURN(block);
		goto err_free_name_buf;
	}

	CALL_ORIGINAL_SYSCALL_HANDLER(rename, hook_type, olddfd, oldname, newdfd, newname, flags);

	if (!is_newpath_ok) {
		// TODO: Does this trick really work?
		dst_newname = d_path(&oldpath, dst_newname_buf, PATH_MAX);
		if (IS_ERR(dst_newname)) {
			DPRINTF("'d_path()' failure %i", ret);
			goto err_free_name_buf;
		}
	}

	DPRINTF("hook_type=%d calling fs_event_rename(ret_val=%ld, oldname=%s, newname=%s, flags=%u)",
			hook_type, ret, dst_oldname, dst_newname, flags);
	fs_event_rename(ret, dst_oldname, dst_newname, flags);
	fs_event_rename_ex(ret, dst_oldname, &source_key, dst_newname, is_newpath_ok ? &target_key : NULL, flags, &oldpath);
	DPRINTF("hook_type=%d finished 'fs_event_rename(ret_val=%ld, oldname=%s, newname=%s, flags=%u)",
			hook_type, ret, dst_oldname, dst_newname, flags);

err_free_name_buf:
	mem_free(dst_oldname_buf);

err_exit:
	path_put(&oldpath);
	path_put(&newpath);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, rename, 2,
	const char __user *, oldname,
	const char __user *, newname)
{
	return CALL_SYSCALL_HANDLER(rename, RENAME_HOOK_TYPE, AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}

DEFINE_SYSCALL_HOOK(ia32_sys, rename, 2,
	const char __user *, oldname,
	const char __user *, newname)
{
	return CALL_SYSCALL_HANDLER(rename, IA32_RENAME_HOOK_TYPE, AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}

DEFINE_SYSCALL_HOOK(sys, renameat, 4,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname)
{
	return CALL_SYSCALL_HANDLER(rename, RENAMEAT_HOOK_TYPE, olddfd, oldname, newdfd, newname, 0);
}

DEFINE_SYSCALL_HOOK(ia32_sys, renameat, 4,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname)
{
	return CALL_SYSCALL_HANDLER(rename, IA32_RENAMEAT_HOOK_TYPE, olddfd, oldname, newdfd, newname, 0);
}

DEFINE_SYSCALL_HOOK(sys, renameat2, 5,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname,
	unsigned int, flags)
{
	return CALL_SYSCALL_HANDLER(rename, RENAMEAT2_HOOK_TYPE, olddfd, oldname, newdfd, newname, flags);
}

DEFINE_SYSCALL_HOOK(ia32_sys, renameat2, 5,
	int, olddfd,
	const char __user *, oldname,
	int, newdfd,
	const char __user *, newname,
	unsigned int, flags)
{
	return CALL_SYSCALL_HANDLER(rename, IA32_RENAMEAT2_HOOK_TYPE, olddfd, oldname, newdfd, newname, flags);
}

enum unlink_hook_type {
	UNLINK_HOOK_TYPE,
	IA32_UNLINK_HOOK_TYPE,

	UNLINKAT_HOOK_TYPE,
	IA32_UNLINKAT_HOOK_TYPE
};

DEFINE_CALL_ORIGINAL_SYSCALL_HANDLER(unlink, 3,
	int, dfd,
	const char __user *, pathname,
	int, flag)
{
	switch(hook_type) {
	case UNLINK_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, unlink), pathname);
	case IA32_UNLINK_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, unlink), pathname);
	case UNLINKAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(SYSCALL_ORIG(sys, unlinkat), dfd, pathname, flag);
	case IA32_UNLINKAT_HOOK_TYPE:
		return CALL_ORIGINAL_SYSCALL_HANDLER_PTR(IA32_SYSCALL_ORIG(ia32_sys, unlinkat), dfd, pathname, flag);
	default:
		return -EINVAL;
	}
}

DEFINE_GET_ORIGINAL_SYSCALL_HANDLER(unlink)
{
	switch(hook_type) {
	case UNLINK_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, unlink);
	case IA32_UNLINK_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, unlink);
	case UNLINKAT_HOOK_TYPE:
		return SYSCALL_ORIG_FN(sys, unlinkat);
	case IA32_UNLINKAT_HOOK_TYPE:
		return IA32_SYSCALL_ORIG_FN(ia32_sys, unlinkat);
	default:
		return -EINVAL;
	}
}

DEFINE_SYSCALL_HANDLER(unlink, 3,
	int, dfd,
	const char __user *, pathname,
	int, flag)
{
	syscall_hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	char *dst_name_buf = NULL;
	char *dst_name = NULL;
	long block = 0;
	struct path path = (struct path){};
	file_key_t key;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(MT_UNLINK)
																		 | MSG_TYPE_TO_EVENT_MASK(MT_UNLINK_EX)
	                                   | MSG_TYPE_TO_EVENT_MASK(MT_PRE_UNLINK_EX);

	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		return syscall_hook_ret;
	}

	if (!(transport_global_get_combined_mask() & generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		goto out;
	}

	ret = user_path_at(dfd, pathname, 0, &path);
	if ((should_skip_file_path(ret, &path) != NOT_SKIP)) {
		DPRINTF("skipped sending FILE_(PRE)_UNLINK, hook_type=%d, because pathname (__user ptr=%p) is not trackable",
			hook_type, pathname);

		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		goto err_exit;
	}

	if ((dst_name_buf = mem_alloc(PATH_MAX)) == NULL) {
		DPRINTF("'%s()' for '%s' failed", "mem_alloc", "dst_name_buf");
		SET_HOOK_RETURN(-ENOMEM);
		goto err_exit;
	}

	dst_name = d_path(&path, dst_name_buf, PATH_MAX);
	if (IS_ERR(dst_name)) {
		DPRINTF("'d_path()' failure %i", ret);

		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		goto err_free_name_buf;
	}

	make_key(&key, &path);
	DPRINTF("hook_type=%d calling fs_event_pre_unlink_ex(path=%s, flag=%d)",
				hook_type, dst_name, flag);
	block = fs_event_pre_unlink_ex(dst_name, &key, flag, &path);
	DPRINTF("hook_type=%d finished 'fs_event_pre_unlink_ex(path=%s, flag=%d)",
				hook_type, dst_name, flag);

	if (block) {
		SET_HOOK_RETURN(block);
		goto err_free_name_buf;
	}

	CALL_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type, dfd, pathname, flag);

	DPRINTF("hook_type=%d calling fs_event_unlink(ret_val=%ld, path=%s, flag=%d)",
				hook_type, ret, dst_name, flag);
	fs_event_unlink(ret, dst_name, flag);
	fs_event_unlink_ex(ret, dst_name, &key, flag);
	DPRINTF("hook_type=%d finished 'fs_event_unlink(ret_val=%ld, path=%s, flag=%d)",
				hook_type, ret, dst_name, flag);

err_free_name_buf:
	mem_free(dst_name_buf);

err_exit:
	path_put(&path);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, unlink, 1,
	const char __user *, pathname)
{
	return CALL_SYSCALL_HANDLER(unlink, UNLINK_HOOK_TYPE, AT_FDCWD, pathname, 0);
}

DEFINE_SYSCALL_HOOK(ia32_sys, unlink, 1,
	const char __user *, pathname)
{
	return CALL_SYSCALL_HANDLER(unlink, IA32_UNLINK_HOOK_TYPE, AT_FDCWD, pathname, 0);
}

DEFINE_SYSCALL_HOOK(sys, unlinkat, 3,
	int, dfd,
	const char __user *, pathname,
	int, flag)
{
	return CALL_SYSCALL_HANDLER(unlink, UNLINKAT_HOOK_TYPE, dfd, pathname, flag);
}

DEFINE_SYSCALL_HOOK(ia32_sys, unlinkat, 3,
	int, dfd,
	const char __user *, pathname,
	int, flag)
{
	return CALL_SYSCALL_HANDLER(unlink, IA32_UNLINKAT_HOOK_TYPE, dfd, pathname, flag);
}
