/**
@file
@brief    File system events messages
@details  Copyright (c) 2017-2021 Acronis International GmbH
@author   Mikhail Krivtsov (mikhail.krivtsov@acronis.com)
@since    $Id: $
*/

#include "fs_event.h"

#include "compat.h"
#include "debug.h"
#include "memory.h"
#include "message.h"
#include "task_info_map.h"
#include "transport.h"
#include "transport_protocol.h"

// 'linux/cred.h' appeared in 'stable/v2.6.27'
#include <linux/fcntl.h>	// for O_CREAT, etc. flags
#include <linux/kernel.h>	// for macroses such as 'ARRAY_SIZE'
#include <linux/limits.h>	// PATH_MAX

// 'fs_event_img_t' accessors

#define FSE_IMG_PID(fs_event_img)	*(&(fs_event_img)->pid)
#define FSE_IMG_TID(fs_event_img)	*(&(fs_event_img)->tid)
#define FSE_IMG_PAYLOAD(fs_event_img)	(void *) (fs_event_img)->payload

#define PATH_FILTERING_ENABLED // comment this #define to disable path filtering


#ifdef PATH_FILTERING_ENABLED
#define DEFINE_FILTER_MASK(path) {path, sizeof(path) - 1}

typedef struct filter_mask {
	char *filter_mask_path;
	size_t filter_mask_len;
} filter_mask_t;

filter_mask_t filter_masks[] = {
	DEFINE_FILTER_MASK("/sys"),
	DEFINE_FILTER_MASK("/proc"),
	DEFINE_FILTER_MASK("/dev")
};

#define FILTER_MASKS_NUMB ARRAY_SIZE(filter_masks)
#endif // PATH_FILTERING_ENABLED

static msg_t *fs_event_msg_new(msg_type_t msg_type, size_t payload_size)
{
	msg_t *msg;
	size_t fs_event_img_size;
	size_t msg_img_size;
	DPRINTF("msg_type=%i/%s payload_size=%zu", msg_type, msg_type_to_string(msg_type), payload_size);

	fs_event_img_size = sizeof(fs_event_img_t) + payload_size;
	msg_img_size = sizeof(msg_img_t) + fs_event_img_size;

	msg = msg_new_type(msg_img_size, msg_type);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		/*
		 * userspace kernel
		 * getpid()  current->tgid
		 * gettid()  current->pid
		 */
		fse_img->pid = current->tgid;
		fse_img->tid = current->pid;
		get_current_fsuid_fsgid_compat(&fse_img->fsuid, &fse_img->fsgid);
	}
	DPRINTF("msg=%p", msg);
	return msg;
}

#ifdef PATH_FILTERING_ENABLED
/*
 * FIXME: This function is actually useless in case of relative path or any path
 * that contains "..", because we do not normalize paths, so arbitrary amount of
 * ".." can eventually point us to any file inside any directory.
 */
static bool is_path_filtered(const char *pathname)
{
	size_t i = 0;

	for (i = 0; i < FILTER_MASKS_NUMB; i++) {
		// if 'path' is in 'filter_mask' folder (strncmp = 0) -> it is filtered
		if (((bool)strncmp(pathname, filter_masks[i].filter_mask_path, filter_masks[i].filter_mask_len)) == 0) {
			DPRINTF("pathname '%s' is filtered by filter_mask='%s'", pathname, filter_masks[i].filter_mask_path);
			return 1;
		}
	}

	DPRINTF("pathname '%s' isn't filtered by filter_masks", pathname);

	return 0;
}
#else
static inline bool is_path_filtered(const char *pathname) { return 0; }
#endif // PATH_FILTERING_ENABLED

static inline bool is_msg_filtered(const char *pathname)
{
    if (is_path_filtered(pathname))
    {
        return true;
    }

    return false;
}

static msg_t *fs_rw_msg_new(msg_type_t msg_type, long ret_val,
		const char *pathname, unsigned int flags, loff_t offset, size_t count)
{
	msg_t *msg;
	size_t path_size;
	size_t fs_event_rw_img_size;
	DPRINTF("msg_type=%i/%s ret_val=%ld pathname=%s "
	        "flags=0o%o/0x%X offset=0x%llX/%lli count=%zu/0x%zX",
			msg_type, msg_type_to_string(msg_type),
			ret_val, pathname,
			flags, flags,
			(long long)offset, (long long)offset,
			count, count);

	if (is_msg_filtered(pathname)) {
		msg = NULL;
		goto out;
	}

	path_size = strlen(pathname) + 1;
	fs_event_rw_img_size = sizeof(fs_event_rw_img_t) + path_size;

	msg = fs_event_msg_new(msg_type, fs_event_rw_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_rw_img_t *rw_img = FSE_IMG_PAYLOAD(fse_img);

		rw_img->ret_val = ret_val;
		rw_img->flags = flags;
		rw_img->offset = offset;
		rw_img->count = count;

		memcpy(rw_img->path, pathname, path_size);
	}

out:
	DPRINTF("msg=%p", msg);
	return msg;
}

static msg_t *fs_rw_ex_msg_new(msg_type_t msg_type, long ret_val,
		const char *pathname, const file_key_t* key, unsigned int flags, loff_t offset, size_t count)
{
	msg_t *msg;
	size_t path_size;
	size_t fs_event_rw_img_size;
	DPRINTF("msg_type=%i/%s ret_val=%ld pathname=%s "
	        "flags=0o%o/0x%X offset=0x%llX/%lli count=%zu/0x%zX",
			msg_type, msg_type_to_string(msg_type),
			ret_val, pathname,
			flags, flags,
			(long long)offset, (long long)offset,
			count, count);

	if (is_msg_filtered(pathname)) {
		msg = NULL;
		goto out;
	}

	path_size = strlen(pathname) + 1;
	fs_event_rw_img_size = sizeof(fs_event_rw_ex_img_t) + path_size;

	msg = fs_event_msg_new(msg_type, fs_event_rw_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_rw_ex_img_t *rw_img = FSE_IMG_PAYLOAD(fse_img);

		rw_img->ret_val = ret_val;
		rw_img->flags = flags;
		rw_img->offset = offset;
		rw_img->count = count;
		rw_img->key = *key;

		memcpy(rw_img->path, pathname, path_size);
	}

out:
	DPRINTF("msg=%p", msg);
	return msg;
}

static msg_t *fs_rw_key_msg_new(msg_type_t msg_type, long ret_val,
		const file_key_t* key, unsigned int flags, loff_t offset, size_t count)
{
	msg_t *msg;
	DPRINTF("msg_type=%i/%s ret_val=%ld "
	        "flags=0o%o/0x%X offset=0x%llX/%lli count=%zu/0x%zX",
			msg_type, msg_type_to_string(msg_type),
			ret_val,
			flags, flags,
			(long long)offset, (long long)offset,
			count, count);

	msg = fs_event_msg_new(msg_type, sizeof(fs_event_rw_key_img_t));
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_rw_key_img_t *rw_img = FSE_IMG_PAYLOAD(fse_img);

		rw_img->ret_val = ret_val;
		rw_img->flags = flags;
		rw_img->offset = offset;
		rw_img->count = count;
		rw_img->key = *key;
	}

	DPRINTF("msg=%p", msg);
	return msg;
}

inline static msg_t *move_path_in_msg(msg_t *msg, struct path *path)
{
	if (msg) {
		thread_safe_path_store_move_directly(&msg->path, path);
	}
	return msg;
}

inline static msg_t *copy_path_in_msg(msg_t *msg, const struct path *path)
{
	if (msg) {
		thread_safe_path_store_copy_directly(&msg->path, path);
	}
	return msg;
}

inline static msg_t *move_path2_if_exists_in_msg(msg_t *msg, struct path *path)
{
	if (msg && path) {
		thread_safe_path_store_move_directly(&msg->path2, path);
	}
	return msg;
}

inline static msg_t *fs_pre_create_msg_new(const char *pathname, const struct path *path)
{
	/* man 2 creat: creat() is equivalent to open() with
	 * flags equal to O_CREAT|O_WRONLY|O_TRUNC
	 *
	 * To faciliate userspace business logic optimisation, send this flags here.
	 */
	return copy_path_in_msg(fs_rw_msg_new(MT_FILE_PRE_CREATE, 0, pathname,
				O_CREAT | O_WRONLY | O_TRUNC, 0, 0), path);
}

inline static msg_t *fs_create_ex_msg_new(long ret_val, const char *pathname, const file_key_t* key, unsigned int flags, const struct path *path)
{
	return copy_path_in_msg(fs_rw_ex_msg_new(MT_FILE_CREATE_EX, ret_val, pathname, key, flags, 0, 0), path);
}

inline static msg_t *fs_pre_open_msg_new(const char *filename,
		unsigned int flags, const struct path *path)
{
	return copy_path_in_msg(fs_rw_msg_new(MT_FILE_PRE_OPEN, 0, filename, flags, 0, 0), path);
}

inline static msg_t *fs_pre_open_ex_msg_new(const char *filename, const file_key_t* key,
		unsigned int flags, struct path *path)
{
	return move_path_in_msg(fs_rw_ex_msg_new(MT_FILE_PRE_OPEN_EX, 0, filename, key, flags, 0, 0), path);
}

inline static msg_t *fs_pre_close_ex_msg_new(const char *filename, const file_key_t* key,
										  unsigned int flags, const struct path *path)
{
	return copy_path_in_msg(fs_rw_ex_msg_new(MT_FILE_PRE_CLOSE_EX, 0, filename, key, flags, 0, 0), path);
}

inline static msg_t *fs_pre_read_msg_new(const file_key_t* key,
										 unsigned int f_flags, loff_t offset, size_t count)
{
	return fs_rw_key_msg_new(MT_FILE_PRE_READ_EX, 0, key, f_flags, offset, count);
}

inline static msg_t *fs_pre_write_msg_new(const file_key_t* key,
										 unsigned int f_flags, loff_t offset, size_t count, const struct path *path)
{
	return copy_path_in_msg(fs_rw_key_msg_new(MT_FILE_PRE_WRITE_EX, 0, key, f_flags, offset, count), path);
}

static long send_msg_sync_unref_and_get_block(msg_t *msg)
{
	long block = 0;
	if (msg) {
		send_msg_sync(msg);
		// If message was interrupted, the process was killed.
		// For the consistency, this means that syscall must be
		// blocked, otherwise APL might fail to backup.
		// Thankfully, process will not care about the
		// syscall result as it will be dead anyways.
		if (msg->block)
			block = -EPERM;

		if (msg->interrupted)
			block = -EINTR;

		thread_safe_path_clear(&msg->path);
		thread_safe_path_clear(&msg->path2);
		msg_unref(msg);
	}

	return block;
}

long fs_event_pre_create(const char *pathname, const struct path *path)
{
	detect_exec();

	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_CREATE)) {
		return send_msg_sync_unref_and_get_block(fs_pre_create_msg_new(pathname, path));
	} else {
		return 0;
	}
}

void fs_event_create_ex(long ret_val, const char *pathname, const file_key_t* key, unsigned int flags, const struct path *path)
{
    detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_CREATE_EX)) {
		send_msg_sync_unref(fs_create_ex_msg_new(ret_val, pathname, key, flags, path));
	}
}

long fs_event_pre_open(const char *filename, unsigned int flags, const struct path *path)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_OPEN)) {
		return send_msg_sync_unref_and_get_block(fs_pre_open_msg_new(filename, flags, path));
	} else {
		return 0;
	}
}

long fs_event_pre_open_ex(const char *filename, const file_key_t* key, unsigned int flags, struct path *path)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_OPEN_EX)) {
		return send_msg_sync_unref_and_get_block(fs_pre_open_ex_msg_new(filename, key, flags, path));
	} else {
		return 0;
	}
}

void fs_event_pre_close_ex(const char *filename, unsigned int flags, const struct path *path, const file_key_t* key)
{
	detect_exec();
	// TODO DK: This should be asynchronous but with 'reply'?
	// TODO DK: This is problematic with current 'thread_safe' path approach
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_CLOSE_EX)) {
		send_msg_sync_unref(fs_pre_close_ex_msg_new(filename, key, flags, path));
	}
}

void fs_event_pre_read_ex(const file_key_t* key, unsigned int f_flags, loff_t offset, size_t count)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_READ_EX)) {
		return send_msg_async_unref(fs_pre_read_msg_new(key, f_flags, offset, count));
	} else {
		return;
	}
}

long fs_event_pre_write_ex(const file_key_t* key, unsigned int f_flags, loff_t offset, size_t count, const struct path *path)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_FILE_PRE_WRITE_EX)) {
		return send_msg_sync_unref_and_get_block(fs_pre_write_msg_new(key, f_flags, offset, count, path));
	} else {
		return 0;
	}
}

static inline int is_rename_msg_filtered(const char *oldname, const char *newname)
{
	if (is_msg_filtered(oldname)) {
		return true;
	}

	if (newname && is_path_filtered(newname)) {
		return true;
	}

	return false;
}

static msg_t *fs_event_rename_msg_new_impl(msg_type_t msg_type, long ret_val,
				const char* oldname,
				const char* newname,
				unsigned int flags)
{
	msg_t *msg;
	size_t oldname_size, newname_size;
	size_t fs_event_rename_img_size;
	DPRINTF("msg_type=%i/%s, ret_val=%ld, oldname = %s, "
					     "newname = %s, "
					     "flags = %u",
	                msg_type, msg_type_to_string(msg_type),
	                ret_val, oldname,
	                         newname,
	                         flags);

	if (is_rename_msg_filtered(oldname, newname)) {
		msg = NULL;
		goto out;
	}

	oldname_size = strlen(oldname) + 1;
	newname_size = strlen(newname) + 1;

	fs_event_rename_img_size = sizeof(fs_event_rename_img_t)
			+ oldname_size + newname_size;

	msg = fs_event_msg_new(msg_type, fs_event_rename_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_rename_img_t *rename_img = FSE_IMG_PAYLOAD(fse_img);

		size_t newname_offset = oldname_size;
		rename_img->newname_offset = newname_offset;

		memcpy(rename_img->names, oldname, oldname_size);
		memcpy(rename_img->names + newname_offset, newname, newname_size);

		rename_img->ret_val = ret_val;
		rename_img->flags = flags;
	}
out:
	DPRINTF("msg=%p", msg);
	return msg;
}

static msg_t *fs_event_rename_ex_msg_new_impl(msg_type_t msg_type, long ret_val,
				const char* oldname, const file_key_t* source_key,
				const char* newname, const file_key_t* target_key,
				unsigned int flags)
{
	msg_t *msg;
	size_t oldname_size, newname_size;
	size_t fs_event_rename_img_size;
	DPRINTF("msg_type=%i/%s, ret_val=%ld, oldname = %s, "
					     "newname = %s, "
					     "flags = %u",
	                msg_type, msg_type_to_string(msg_type),
	                ret_val, oldname,
	                         newname,
	                         flags);

	if (is_rename_msg_filtered(oldname, newname)) {
		msg = NULL;
		goto out;
	}

	oldname_size = strlen(oldname) + 1;
	newname_size = newname ? strlen(newname) + 1 : 0;

	fs_event_rename_img_size = sizeof(fs_event_rename_ex_img_t)
			+ oldname_size + newname_size;

	msg = fs_event_msg_new(msg_type, fs_event_rename_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_rename_ex_img_t *rename_img = FSE_IMG_PAYLOAD(fse_img);

		size_t newname_offset = oldname_size;
		rename_img->newname_offset = newname_offset;

		rename_img->source_key = *source_key;
		rename_img->target_exists = !!target_key;
		if (target_key)
		{
			rename_img->target_key = *target_key;
		}
		else
		{
			rename_img->target_key.ptr = 0;
			rename_img->target_key.ino = 0;
			rename_img->target_key.dev = 0;
		}

		memcpy(rename_img->names, oldname, oldname_size);
		if (newname)
		{
			memcpy(rename_img->names + newname_offset, newname, newname_size);
		}

		rename_img->ret_val = ret_val;
		rename_img->flags = flags;
	}
out:
	DPRINTF("msg=%p", msg);
	return msg;
}

inline static msg_t *fs_event_pre_rename_ex_msg_new(const char* oldname, const file_key_t* source_key,
                                                 const char* newname, const file_key_t* target_key,
                                                 unsigned int flags, const struct path *oldpath, struct path *newpath)
{
	return move_path2_if_exists_in_msg(
				 copy_path_in_msg(fs_event_rename_ex_msg_new_impl(MT_PRE_RENAME_EX, 0, oldname, source_key,
							      newname, target_key,
							      flags), oldpath), newpath);
}

inline static msg_t *fs_event_rename_msg_new(long ret_val, const char* oldname,
                                                           const char* newname,
                                                           unsigned int flags)
{
	return fs_event_rename_msg_new_impl(MT_RENAME, ret_val, oldname, newname, flags);
}

inline static msg_t *fs_event_rename_ex_msg_new(long ret_val, const char* oldname, const file_key_t* source_key,
                                                              const char* newname, const file_key_t* target_key,
                                                              unsigned int flags, struct path *oldpath)
{
	return move_path_in_msg(fs_event_rename_ex_msg_new_impl(MT_RENAME_EX, ret_val, oldname, source_key, newname, target_key, flags), oldpath);
}

long fs_event_pre_rename_ex(const char* oldname, const file_key_t* source_key,
			 const char* newname, const file_key_t* target_key,
			 unsigned int flags, const struct path *oldpath, struct path *newpath)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_PRE_RENAME_EX)) {
		return send_msg_sync_unref_and_get_block(
			fs_event_pre_rename_ex_msg_new(oldname, source_key,
							newname, target_key,
							flags, oldpath, newpath));
	} else {
		return 0;
	}
}

void fs_event_rename(long ret_val, const char* oldname,
                                   const char* newname,
                                   unsigned int flags)
{
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_RENAME)) {
		send_msg_sync_unref(fs_event_rename_msg_new(ret_val, oldname, newname, flags));
	}
}

void fs_event_rename_ex(long ret_val, const char* oldname, const file_key_t* source_key,
                                      const char* newname, const file_key_t* target_key,
                                      unsigned int flags, struct path *oldpath)
{
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_RENAME_EX)) {
		send_msg_sync_unref(fs_event_rename_ex_msg_new(ret_val, oldname, source_key, newname, target_key, flags, oldpath));
	}
}

static msg_t *fs_event_unlink_msg_new_impl(msg_type_t msg_type, long ret_val, const char *pathname, int flag)
{
	msg_t *msg;
	size_t path_size;
	size_t fs_event_unlink_img_size;
	DPRINTF("msg_type=%i/%s, ret_val=%ld, pathname = %s, flag = %d",
			msg_type, msg_type_to_string(msg_type),
			ret_val, pathname, flag);

	if (is_msg_filtered(pathname)) {
		msg = NULL;
		goto out;
	}

	path_size = strlen(pathname) + 1;
	fs_event_unlink_img_size = sizeof(fs_event_unlink_img_t) + path_size;

	msg = fs_event_msg_new(msg_type, fs_event_unlink_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_unlink_img_t *unlink_img = FSE_IMG_PAYLOAD(fse_img);

		unlink_img->ret_val = ret_val;
		unlink_img->flag = flag;

		memcpy(unlink_img->path, pathname, path_size);
	}
out:
	DPRINTF("msg=%p", msg);
	return msg;
}

static msg_t *fs_event_unlink_ex_msg_new_impl(msg_type_t msg_type, long ret_val,
				const char *pathname, const file_key_t *key, int flag)
{
	msg_t *msg;
	size_t path_size;
	size_t fs_event_unlink_img_size;
	DPRINTF("msg_type=%i/%s, ret_val=%ld, pathname = %s, flag = %d",
			msg_type, msg_type_to_string(msg_type),
			ret_val, pathname, flag);

	if (is_msg_filtered(pathname)) {
		msg = NULL;
		goto out;
	}

	path_size = strlen(pathname) + 1;
	fs_event_unlink_img_size = sizeof(fs_event_unlink_ex_img_t) + path_size;

	msg = fs_event_msg_new(msg_type, fs_event_unlink_img_size);
	if (msg) {
		msg_img_t *msg_img = MSG_IMG(msg);
		fs_event_img_t *fse_img = IMG_PAYLOAD(msg_img);
		fs_event_unlink_ex_img_t *unlink_img = FSE_IMG_PAYLOAD(fse_img);

		unlink_img->ret_val = ret_val;
		unlink_img->flag = flag;
		unlink_img->key = *key;

		memcpy(unlink_img->path, pathname, path_size);
	}
out:
	DPRINTF("msg=%p", msg);
	return msg;
}

inline static msg_t *fs_event_pre_unlink_ex_msg_new(const char *pathname, const file_key_t *key, int flag, struct path *path)
{
	return move_path_in_msg(fs_event_unlink_ex_msg_new_impl(MT_PRE_UNLINK_EX, 0, pathname, key, flag), path);
}

inline static msg_t *fs_event_unlink_msg_new(long ret_val, const char* pathname, int flag)
{
	return fs_event_unlink_msg_new_impl(MT_UNLINK, ret_val, pathname, flag);
}

inline static msg_t *fs_event_unlink_ex_msg_new(long ret_val, const char* pathname, const file_key_t *key, int flag)
{
	return fs_event_unlink_ex_msg_new_impl(MT_UNLINK_EX, ret_val, pathname, key, flag);
}

long fs_event_pre_unlink_ex(const char* pathname, const file_key_t *key, int flag, struct path *path)
{
	detect_exec();
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_PRE_UNLINK_EX)) {
		return send_msg_sync_unref_and_get_block(fs_event_pre_unlink_ex_msg_new(pathname, key, flag, path));
	} else {
		return 0;
	}
}

void fs_event_unlink(long ret_val, const char* pathname, int flag)
{
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_UNLINK)) {
		send_msg_sync_unref(fs_event_unlink_msg_new(ret_val, pathname, flag));
	}
}

void fs_event_unlink_ex(long ret_val, const char* pathname, const file_key_t* key, int flag)
{
	if (transport_global_get_combined_mask() & MSG_TYPE_TO_EVENT_MASK(MT_UNLINK_EX)) {
		send_msg_sync_unref(fs_event_unlink_ex_msg_new(ret_val, pathname, key, flag));
	}
}
