/**
@file
@brief    Linux kernel 'module'
@details  Copyright (c) 2017 Acronis International GmbH
@author   Mikhail Krivtsov (mikhail.krivtsov@acronis.com)
@since    $Id: $
*/

#include "debug.h"		// DPRINTF
#include "exited_tasks.h"
#include "file_contexts.h"
#include "ftrace_hooks/fsnotify_listener.h"
#include "module_rundown_protection.h"
#include "network/raw_socket_manager.h"
#include "network/tcp_socket_manager.h"
#include "network/udp_socket_manager.h"
#include "syscall_common.h"
#include "task_info_map.h"
#include "tracepoints.h"
#include "procfs_manager.h"
#include "ptrace_manager.h"
#include "transport.h"

#include <linux/rcupdate.h>
#include <linux/module.h>
#include <linux/version.h>	// LINUX_VERSION_CODE, KERNEL_VERSION()

#ifdef __DEBUG_LEVEL__
#ifdef DEFAULT_LOGGER_DEBUG_LEVEL
unsigned int logger_debug_level = DEFAULT_LOGGER_DEBUG_LEVEL;
#else
unsigned int logger_debug_level = 0;
#endif
#endif
/*
    Initialization order is very important.
    'transport' depends on 'pid_bw_list'
    'syscall_hooks' and 'tracepoints' depend on 'transport'
*/
static int __init module_init_impl(void)
{
	int ret;

	IPRINTF("DRIVER_VERSION=%s", DRIVER_VERSION_STRING);
	IPRINTF("LINUX_VERSION_CODE=%u.%u.%u", LINUX_VERSION_CODE >> 16,
		(LINUX_VERSION_CODE >> 8) & 0xFF, LINUX_VERSION_CODE & 0xFF);
#if defined RHEL_RELEASE_CODE
	IPRINTF("RHEL_RELEASE_CODE=%u.%u",
		RHEL_RELEASE_CODE >> 8, RHEL_RELEASE_CODE & 0xFF);
#endif

	compat_init();
	ret = memory_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "memory_init", ret);
		return ret;
	}

	ret = task_info_maps_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "task_info_maps_init", ret);
		goto fail_task_info_maps;
	}

	ret = file_contexts_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "file_contexts_init", ret);
		goto fail_file_contexts;
	}

	ret = fsnotify_events_listener_global_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "fsnotify_events_listener_global", ret);
		goto fail_fsnotify_events_listener;
	}

	ret = udp_socket_manager_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "udp_socket_manager_init", ret);
		goto fail_udp_socket_manager;
	}

	ret = procfs_manager_init();
	if (ret)
	{
		EPRINTF("'%s()' failure %i", "procfs_manager_init", ret);
		goto fail_procfs_manager;
	}

	ret = ptrace_manager_init();
	if (ret)
	{
		EPRINTF("'%s()' failure %i", "ptrace_manager_init", ret);
		goto fail_ptrace_manager;
	}

	ret = exited_tasks_init();
	if (ret)
	{
		EPRINTF("'%s()' failure %i", "exited_tasks_init", ret);
		goto fail_exited_tasks_start;
	}

	mod_rundown_protection_init(false /*not ready*/);
	tcp_socket_manager_init();
	raw_socket_manager_init();

	ret = transport_mod_init();
	if (ret) {
		EPRINTF("'%s()' failure %i", "transport_mod_init", ret);
		goto fail_transport_mod;
	}


	DPRINTF("ret=%i", ret);
	return ret;

fail_transport_mod:
	exited_tasks_deinit();
fail_exited_tasks_start:
	ptrace_manager_deinit();
fail_ptrace_manager:
	procfs_manager_deinit();
fail_procfs_manager:
	udp_socket_manager_deinit();
fail_udp_socket_manager:
	fsnotify_events_listener_global_init_fail_free();
fail_fsnotify_events_listener:
	file_contexts_init_fail_free();
fail_file_contexts:
	task_info_maps_init_fail_free();
fail_task_info_maps:
	memory_deinit();
	return ret;
}

static void __exit module_down_impl(void)
{
	IPRINTF("module unload started");
	exited_tasks_deinit();
	ptrace_manager_deinit();
	procfs_manager_deinit();
	transport_mod_down();
	fsnotify_events_listener_global_deinit();
	task_info_maps_deinit();
	file_contexts_deinit();
	udp_socket_manager_deinit();
	memory_deinit();
	IPRINTF("module unload finished");
}

MODULE_AUTHOR("Acronis International GmbH");
MODULE_DESCRIPTION("APL (Active Protection for Linux)");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION_STRING);
module_init(module_init_impl);
module_exit(module_down_impl);
