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

#include <linux/version.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)

#include "fs_syscall_hooks.h"

#include "compat.h"
#include "debug.h"
#include "file_contexts.h"
#include "file_key_tools.h"
#include "ftrace_hooks/ftrace_events.h"
#include "ftrace_hooks/fsnotify_listener.h"
#include "fs_event.h"
#include "message.h"
#include "memory.h"
#include "path_tools.h"
#include "syscall_common.h"
#include "task_tools.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 <linux/dcache.h>	// for 'd_unlinked()'
#include <linux/errno.h>
#include <linux/exportfs.h>
#include <linux/namei.h> // for 'user_path_at'
#include <linux/fdtable.h>
#include <linux/fs.h>		// for 'MAX_RW_COUNT'
#include <linux/fsnotify.h>
#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)
{
	if (lookup_ret == -ENOENT) {
		return PATH_NOT_EXISTS;
	}

	if (lookup_ret) {
		return SKIP;
	}

	if (!path_is_valid(path)) {
		return SKIP;
	}

	return NOT_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;
	}
}

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;
	}
}

// It is a common occurance that syscall hooks generate multiple fs events (PRE->POST or OPEN+CREATE for example).
// In this case, it is convenient to use this function to check if we can skip a particular event mask generation
static bool want_fs_event(task_info_t* task_info, const transport_ids_t* transport_ids, uint64_t mask)
{
	if (!(transport_global_get_combined_mask() & mask))
		return false;

	if (task_info_can_skip(task_info, transport_ids, mask))
		return false;

	return true;
}

// 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;
	hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	long block = 0;
	struct path path = (struct path){};
	task_info_t* task_info;
	transport_ids_t transport_ids;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN)
	                                   | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN)
	                                   | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CREATE);
	const uint64_t generatedOpenEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN)
	                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN);
	const uint64_t generatedCreateEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CREATE);

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

	if (!(transport_global_get_combined_mask() & (generatedEventsMask | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_PROCESS_EXEC)))
     || transport_process_belongs_to_control(current)) {
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
		goto out;
	}

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	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 (skipping == NOT_SKIP) {
		DPRINTF("hook_type=%d calling fs_event_pre_creat()", hook_type);
		{
			file_context_info_t file_context_info = {0};
			if (!want_fs_event(task_info, &transport_ids, generatedOpenEventsMask))
			{
				SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
				goto skip_send_pre_open;
			}

			if (make_file_context_info(&file_context_info, &path, O_CREAT | O_WRONLY | O_TRUNC, 0, 0, READ_ONCE(task_info->pid_version)) &&
				check_open_cache(&transport_ids, &file_context_info))
			{
				SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
				goto skip_send_pre_open;
			}

			block = fs_event_pre_open(task_info, O_CREAT | O_WRONLY | O_TRUNC, &path, &file_context_info.msg_info);
		skip_send_pre_open:;
		}
		DPRINTF("hook_type=%d finished 'fs_event_pre_creat()", hook_type);

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

	if (skipping == PATH_NOT_EXISTS && want_fs_event(task_info, &transport_ids, generatedCreateEventsMask)) {
		CALL_ORIGINAL_SYSCALL_HANDLER(creat, hook_type, pathname, mode);
		if (!IS_ERR_VALUE(ret)) {
			struct compat_fd f = compat_fdget(ret);
			if (f.file && file_is_valid(f.file)) {
				struct path* fpath = &f.file->f_path;
				DPRINTF("hook_type=%d calling fs_event_creat(ret_val=%ld)", hook_type, ret);
				fs_event_create(task_info, fpath);
				DPRINTF("hook_type=%d finished 'fs_event_creat(ret_val=%ld)", hook_type, ret);
				compat_fdput(f);
			} 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 isn't trackable", hook_type);
		}
	} else {
		SET_ORIGINAL_SYSCALL_HANDLER(creat, hook_type);
	}

err_exit:
	path_put(&path);
	task_info_put(task_info);

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;
	hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	long block = 0;
	struct path path = (struct path){};
	int lookup_flags = 0;
	bool is_create_event;
	task_info_t* task_info;
	transport_ids_t transport_ids;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN)
	                                   | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN)
	                                   | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CREATE);
	const uint64_t generatedOpenEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN)
	                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN);
	const uint64_t generatedCreateEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CREATE);
	if (!HOOK_PROLOG()) {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		return syscall_hook_ret;
	}

#ifdef O_PATH
	// This logic is here for "feature parity" with LSM - LSM does not notify about O_PATH events.
	// They are filtered out in the "do_dentry_open". Do the same, those are useless anyways.
	if (O_PATH & flags) {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		return syscall_hook_ret;
	}
#endif

	// I am not checking subtype here because it is cumbersome + 'open' syscalls are hooked only in very rare cases
	// when CONFIG_SECURITY is missing. It is fair to assume those will be very rare cases.

	// Workaround when proper EXEC is not available - use open
	if (!(transport_global_get_combined_mask() & (generatedEventsMask | (MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_PROCESS_EXEC))))
     || transport_process_belongs_to_control(current)) {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		goto out;
	}

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
		goto err_exit;
	}

#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 (skipping == NOT_SKIP)
	{
		DPRINTF("hook_type=%d calling fs_event_pre_open()", hook_type);
		fsnotify_events_listen_sb(path.mnt->mnt_sb);
		{
			file_context_info_t file_context_info = {0};
			if (!want_fs_event(task_info, &transport_ids, generatedOpenEventsMask))
			{
				SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
				goto skip_send_pre_open;
			}

			if (make_file_context_info(&file_context_info, &path, flags, 0, 0, READ_ONCE(task_info->pid_version)) &&
				check_open_cache(&transport_ids, &file_context_info))
			{
				SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
				goto skip_send_pre_open;
			}

			block = fs_event_pre_open(task_info, flags, &path, &file_context_info.msg_info);
		skip_send_pre_open:;
		}
		DPRINTF("hook_type=%d finished 'fs_event_pre_open()", hook_type);

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

	is_create_event = skipping == PATH_NOT_EXISTS && (flags & O_CREAT);
	if (is_create_event && want_fs_event(task_info, &transport_ids, generatedCreateEventsMask)) {
		CALL_ORIGINAL_SYSCALL_HANDLER(open, hook_type, dfd, filename, flags, mode);
		if (!IS_ERR_VALUE(ret)) {
			struct compat_fd f = compat_fdget(ret);
			if (f.file && file_is_valid(f.file)) {
				struct path* fpath = &f.file->f_path;
				DPRINTF("hook_type=%d calling fs_event_creat(ret_val=%ld)", hook_type, ret);
				fs_event_create(task_info, fpath);
				DPRINTF("hook_type=%d finished 'fs_event_creat(ret_val=%ld)", hook_type, ret);
			} else {
				DPRINTF("skipped sending FILE_CREAT (hook_type=%d ret_val=%ld), because can't get path",
						hook_type, ret);
			}
			compat_fdput(f);
		} else {
			DPRINTF("skipped sending FILE_CREAT (hook_type=%d flags=0o%o/0x%x)",
					hook_type, flags, flags);
		}
	} else {
		SET_ORIGINAL_SYSCALL_HANDLER(open, hook_type);
	}

err_exit:
	path_put(&path);
	task_info_put(task_info);

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);
}

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;
	}
}

DEFINE_SYSCALL_HANDLER(close, 1, unsigned int, fd)
{
	hook_ret_t syscall_hook_ret;
	int flags;
	struct path *path;
	struct compat_fd f;
	task_info_t* task_info;
	transport_ids_t transport_ids;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_CLOSE)
	                                   | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_CLOSE);
	subtypes_t subtypes;

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

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

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto err_skipped;
	}

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

	flags = f.file->f_flags;
	if (!file_is_valid(f.file))
	{
		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;
	}

	subtypes = fs_close_subtypes((flags & (O_RDWR | O_WRONLY)), f.file->f_path.dentry->d_inode);
	if (!transport_global_subtype_needed(subtypes.sync)
	 && !transport_global_subtype_needed(subtypes.notify))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);
		goto err_exit;
	}

	path = &f.file->f_path;
	DPRINTF("hook_type=%d calling fs_event_pre_close(flags=0o%o/0x%x)", hook_type, flags, flags);
	fs_event_pre_close(task_info, flags, path);
	DPRINTF("hook_type=%d finished fs_event_pre_close(flags=0o%o/0x%x)", hook_type, flags, flags);

	SET_ORIGINAL_SYSCALL_HANDLER(close, hook_type);

err_exit:
	compat_fdput(f);

err_skipped:
	task_info_put(task_info);

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)						\
({										\
	unsigned long 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)
{
	hook_ret_t syscall_hook_ret;
	ssize_t ret_len = -EINVAL;
	loff_t offset = -1;
	int flags;
	file_key_t key;
	struct path *path;
	struct compat_fd f;
	int is_compat = IS_COMPAT_READ_HOOK_TYPE(hook_type);
	task_info_t* task_info;
	transport_ids_t transport_ids;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_FULL_READ);

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

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

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
		goto err_skipped;
	}

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

	flags = f.file->f_flags;
	if (!file_is_valid(f.file)) {
		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;
	}

	if (!S_ISREG(f.file->f_path.dentry->d_inode->i_mode)) {
		DPRINTF("skipped sending FILE_(PRE)_READ, hook_type=%d, because filename (fd=%lu) isn't regular", 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",
			flags, flags,
			offset);

	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_full_read(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, flags, flags, offset, count);

	{
		file_context_info_t file_context_info = {0};
		if (make_file_context_info(&file_context_info, path, flags, offset, offset + count, READ_ONCE(task_info->pid_version)) &&
			check_and_update_read_cache(&transport_ids, &file_context_info))
		{
			SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);
			goto err_exit;
		}
	}

	fs_event_pre_full_read(task_info, &key, flags, offset, count);

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

	SET_ORIGINAL_SYSCALL_HANDLER(read, hook_type);

err_exit:
	compat_fdput(f);

err_skipped:
	task_info_put(task_info);

out:
	HOOK_EPILOG();
	return syscall_hook_ret;
}

DEFINE_SYSCALL_HOOK(sys, read, 3, unsigned int, fd, char __user *, buf, size_t, count)
{
	(void) buf;
	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)
{
	(void) buf;
	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)
{
	(void) 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)
{
	(void) buf;
	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)
{
	(void) ubuf;
	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)
{
	(void) 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, write_flags)
{
	hook_ret_t syscall_hook_ret;
	ssize_t ret_len = -EINVAL;
	loff_t offset = -1;
	int flags;
	int is_compat = IS_COMPAT_WRITE_HOOK_TYPE(hook_type);
	long block = 0;
	struct path *path;
	struct compat_fd f;
	file_context_info_t file_context_info = {0};
	task_info_t* task_info;
	transport_ids_t transport_ids;
	const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_WRITE);

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

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

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto err_skipped;
	}

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

	flags = f.file->f_flags;
	if (!file_is_valid(f.file)) {
		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;
	}

	if (!S_ISREG(f.file->f_path.dentry->d_inode->i_mode)) {
		DPRINTF("skipped sending FILE_(PRE)_WRITE, hook_type=%d, because file (fd=%lu) isn't regular", hook_type, fd);
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto err_exit;
	}

	path = &f.file->f_path;
	offset = f.file->f_pos;

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

	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(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, flags, 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
	 */

	if (make_file_context_info(&file_context_info, path, flags, offset, offset + count, READ_ONCE(task_info->pid_version)) &&
		check_write_cache(&transport_ids, &file_context_info, FILE_CONTEXT_WRITE_TABLE))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);
		goto err_exit;
	}

	block = fs_event_pre_write(task_info, flags, offset, count, path, &file_context_info.msg_info);
	DPRINTF("hook_type=%d finished 'fs_event_pre_write(f_flags=0o%o/0x%x, offset=%lld, count=%zu)",
			hook_type, flags, flags, offset, count);

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

	SET_ORIGINAL_SYSCALL_HANDLER(write, hook_type);

err_exit:
	compat_fdput(f);

err_skipped:
	task_info_put(task_info);

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)
{
	hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	file_key_t target_key;
	uint64_t target_subtype = 0;
	long block = 0;
	struct path oldpath = (struct path){};
	struct path newpath = (struct path){};
	int is_newpath_ok = 0;
	task_info_t* task_info;
	transport_ids_t transport_ids;
	uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_RENAME)
	                             | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_RENAME)
	                             | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_RENAME)
	                             | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_RENAME);

	if (!ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) {
		generatedEventsMask |= MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK);
	}

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

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

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
	{
		SET_ORIGINAL_SYSCALL_HANDLER(rename, hook_type);
		goto err_exit;
	}

	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;
	}

	fsnotify_events_listen_sb(oldpath.mnt->mnt_sb);
	ret = user_path_at(newdfd, newname, 0, &newpath);
	is_newpath_ok = NOT_SKIP == should_skip_rename_dst(ret, &newpath);
	if (is_newpath_ok) {
		make_key(&target_key, &newpath);
		target_subtype = fs_generic_subtype(newpath.dentry->d_inode);
	}

	if (want_fs_event(task_info, &transport_ids, MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_RENAME)
	                                            | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_RENAME)))
	{
		DPRINTF("hook_type=%d calling fs_event_pre_rename(flags=%u)", hook_type, flags);
		block = fs_event_pre_rename(task_info, flags, &oldpath, is_newpath_ok ? &newpath : NULL, is_newpath_ok ? true : false);
		DPRINTF("hook_type=%d finished 'fs_event_pre_rename(flags=%u)", hook_type, flags);
	}

	path_put(&newpath);
	newpath = (struct path){};

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

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

	if (!ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) {
		if (!IS_ERR_VALUE(ret) && is_newpath_ok) {
			remove_common_cache_all(&target_key);
			if (want_fs_event(task_info, &transport_ids, MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK)))
				fs_event_fsnotify(task_info, &target_key, FS_DELETE_SELF, FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK, target_subtype);
		}
	}

	DPRINTF("hook_type=%d calling fs_event_rename(flags=%u)", hook_type, flags);
	if (!IS_ERR_VALUE(ret)) {
		if (want_fs_event(task_info, &transport_ids, (MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_RENAME)
		                                            | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_RENAME))))
		{
			fs_event_rename(task_info, &oldpath, is_newpath_ok);
		}
	}

	DPRINTF("hook_type=%d finished 'fs_event_rename(flags=%u)", hook_type, flags);

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

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)
{
	hook_ret_t syscall_hook_ret;
	long ret = -EINVAL;
	long block = 0;
	struct path path = (struct path){};
	file_key_t key;
	uint64_t subtype;
	task_info_t* task_info;
	transport_ids_t transport_ids;
	uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_UNLINK)
	                             | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_UNLINK);
	if (!ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) {
		generatedEventsMask |= MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK);
	}

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

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

	task_info = task_info_map_get(current);
	if (!task_info) {
		DPRINTF("'%s()' failed", "task_info_map_get");
		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		goto out;
	}
	transport_global_get_ids(&transport_ids);
	refresh_task(task_info, &transport_ids);

	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;
	}

	fsnotify_events_listen_sb(path.mnt->mnt_sb);
	make_key(&key, &path);
	subtype = fs_generic_subtype(path.dentry->d_inode);
	if (want_fs_event(task_info, &transport_ids, MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_UNLINK)
	                                           | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_UNLINK)))
	{
		DPRINTF("hook_type=%d calling fs_event_pre_unlink(flag=%d)", hook_type, flag);
		block = fs_event_pre_unlink(task_info, &path);
		DPRINTF("hook_type=%d finished 'fs_event_pre_unlink(flag=%d)", hook_type, flag);
	}

	path_put(&path);
	path = (struct path){};

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

	// If we have fsnotify, no need to have post-unlink activity generating fsnotify-like events
	if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) {
		SET_ORIGINAL_SYSCALL_HANDLER(unlink, hook_type);
		goto err_exit;
	}

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

	DPRINTF("hook_type=%d calling fs_event_unlink(ret_val=%ld, flag=%d)", hook_type, ret, flag);
	if (!IS_ERR_VALUE(ret)) {
		if (want_fs_event(task_info, &transport_ids, MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK))) {
			fs_event_fsnotify(task_info, &key, FS_DELETE_SELF, FP_SI_OT_NOTIFY_FSNOTIFY_UNLINK, subtype);
		}
	}
	DPRINTF("hook_type=%d finished 'fs_event_unlink(ret_val=%ld, flag=%d)", hook_type, ret, flag);

	if (!IS_ERR_VALUE(ret))
		remove_common_cache_all(&key);

err_exit:
	path_put(&path);
	task_info_put(task_info);

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);
}

#endif
