# SPDX-License-Identifier: GPL-2.0-only OR MIT
#
# Copyright (C) 2023 The Falco Authors.
#
# This file is dual licensed under either the MIT or GPL 2. See MIT.txt or GPL.txt for full copies
# of the license.
#

cmake_minimum_required(VERSION 3.12)
project(driver)

set(TARGET_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR})
if((NOT TARGET_ARCH STREQUAL "x86_64")
   AND (NOT TARGET_ARCH STREQUAL "aarch64")
   AND (NOT TARGET_ARCH STREQUAL "s390x")
   AND (NOT TARGET_ARCH STREQUAL "riscv64")
   AND (NOT TARGET_ARCH STREQUAL "ppc64le")
   AND (NOT TARGET_ARCH STREQUAL "loongarch64")
)
	message(WARNING "Target architecture not officially supported by our drivers!")
else()
	# Load current kernel version
	execute_process(
		COMMAND uname -r
		OUTPUT_VARIABLE UNAME_RESULT
		OUTPUT_STRIP_TRAILING_WHITESPACE
	)
	string(REGEX MATCH "[0-9]+.[0-9]+" LINUX_KERNEL_VERSION ${UNAME_RESULT})
	message(STATUS "Kernel version: ${UNAME_RESULT}")

	# Check minimum kernel version
	set(kmod_min_kver_map_x86_64 2.6)
	set(kmod_min_kver_map_aarch64 3.16)
	set(kmod_min_kver_map_s390x 2.6)
	set(kmod_min_kver_map_riscv64 5.0)
	set(kmod_min_kver_map_ppc64le 2.6)
	set(kmod_min_kver_map_loongarch64 5.10)

	if(LINUX_KERNEL_VERSION VERSION_LESS ${kmod_min_kver_map_${TARGET_ARCH}})
		message(
			WARNING
				"[KMOD] To run this driver you need a Linux kernel version >= ${kmod_min_kver_map_${TARGET_ARCH}} but actual kernel version is: ${UNAME_RESULT}"
		)
	endif()
endif()

option(BUILD_DRIVER "Build the driver on Linux" ON)
option(ENABLE_DKMS "Enable DKMS on Linux" ON)

if(NOT DEFINED DRIVER_VERSION)
	message(
		FATAL_ERROR
			"No DRIVER_VERSION set.\nPlease either explicitly set it or build the root project 'falcosecurity/libs' from a git working directory."
	)
endif()

if(NOT DEFINED DRIVER_COMPONENT_NAME)
	set(DRIVER_COMPONENT_NAME "scap-driver")
endif()
if(NOT DEFINED DRIVER_KMOD_COMPONENT_NAME)
	set(DRIVER_KMOD_COMPONENT_NAME ${DRIVER_COMPONENT_NAME})
endif()

if(NOT DEFINED DRIVER_PACKAGE_NAME)
	set(DRIVER_PACKAGE_NAME "scap")
endif()

if(NOT DEFINED DRIVER_NAME)
	set(DRIVER_NAME "scap")
endif()

if(NOT DEFINED DRIVER_DEVICE_NAME)
	set(DRIVER_DEVICE_NAME "${DRIVER_NAME}")
endif()

# The driver build process is somewhat involved because we use the same sources for building the
# driver locally and for shipping as a DKMS module.
#
# We need a single directory with the following files inside: - all the driver *.c/*.h sources -
# Makefile generated from Makefile.in - driver_config.h generated from driver_config.h.in
#
# The Makefile _must_ be called just Makefile (and not e.g. Makefile.dkms) because of the module
# build process, which looks like this: 1. The user (or some script) runs make in our driver
# directory 2. Our Makefile runs the Makefile from kernel sources/headers 3. The kernel Makefile
# calls our original Makefile again, with options that trigger the actual build. This step cannot
# know that our Makefile has a different name.
#
# (DKMS needs a Makefile called Makefile as well).
#
# The files need to be in a single directory because we cannot know where the sources will be built
# (especially by DKMS) so we cannot put _any_ paths in the Makefile.
#
# The chosen directory must not be ${CMAKE_CURRENT_BINARY_DIR} because CMake puts its own generated
# Makefile in there, so we (arbitrarily) choose ${CMAKE_CURRENT_BINARY_DIR}/src. To maintain
# compatibility with older versions, after the build we copy the compiled module one directory up,
# to ${CMAKE_CURRENT_BINARY_DIR}.
include(compute_versions RESULT_VARIABLE RESULT)
if(RESULT STREQUAL NOTFOUND)
	message(FATAL_ERROR "problem with compute_versions.cmake in ${CMAKE_MODULE_PATH}")
endif()
compute_versions(API_VERSION SCHEMA_VERSION)

configure_file(dkms.conf.in src/dkms.conf)
configure_file(Makefile.in src/Makefile)
configure_file(driver_config.h.in src/driver_config.h)

#
# Copy all the "configure" modules
#
file(GLOB configure_modules "${CMAKE_CURRENT_SOURCE_DIR}/configure/*")
foreach(subdir ${configure_modules})
	if(IS_DIRECTORY "${subdir}")
		file(RELATIVE_PATH CONFIGURE_MODULE "${CMAKE_CURRENT_SOURCE_DIR}/configure" "${subdir}")
		configure_file(
			configure/${CONFIGURE_MODULE}/test.c src/configure/${CONFIGURE_MODULE}/test.c COPYONLY
		)
		configure_file(configure/Makefile src/configure/${CONFIGURE_MODULE}/Makefile COPYONLY)
		configure_file(configure/build.sh src/configure/${CONFIGURE_MODULE}/build.sh COPYONLY)
		configure_file(configure/Makefile.inc.in src/configure/${CONFIGURE_MODULE}/Makefile.inc)
		if(ENABLE_DKMS)
			install(
				FILES "${CMAKE_CURRENT_BINARY_DIR}/src/configure/${CONFIGURE_MODULE}/build.sh"
					  "${CMAKE_CURRENT_BINARY_DIR}/src/configure/${CONFIGURE_MODULE}/test.c"
					  "${CMAKE_CURRENT_BINARY_DIR}/src/configure/${CONFIGURE_MODULE}/Makefile"
					  "${CMAKE_CURRENT_BINARY_DIR}/src/configure/${CONFIGURE_MODULE}/Makefile.inc"
				DESTINATION
					"src/${DRIVER_PACKAGE_NAME}-${DRIVER_VERSION}/configure/${CONFIGURE_MODULE}"
				COMPONENT ${DRIVER_KMOD_COMPONENT_NAME}
			)
		endif()
	endif()
endforeach()

set(DRIVER_SOURCES
	dynamic_params_table.c
	event_table.c
	fillers_table.c
	flags_table.c
	kernel_hacks.h
	feature_gates.h
	main.c
	ppm.h
	ppm_api_version.h
	ppm_events.c
	ppm_events.h
	ppm_events_public.h
	ppm_fillers.c
	ppm_fillers.h
	ppm_flag_helpers.h
	ppm_ringbuffer.h
	syscall_table.c
	syscall_table64.c
	ppm_cputime.c
	ppm_version.h
	systype_compat.h
	ppm_tp.h
	ppm_tp.c
	ppm_consumer.h
	capture_macro.h
	socketcall_to_syscall.h
	syscall_ia32_64_map.c
)

foreach(FILENAME IN LISTS DRIVER_SOURCES)
	configure_file(${FILENAME} src/${FILENAME} COPYONLY)
endforeach()

# make can be self-referenced as $(MAKE) only from Makefiles but this triggers syntax errors with
# other generators such as Ninja
if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
	set(MAKE_COMMAND "$(MAKE)")
else()
	set(MAKE_COMMAND "make")
endif()

# This if/else is needed because you currently cannot manipulate dependencies of built-in targets
# like "all" in CMake: http://public.kitware.com/Bug/view.php?id=8438
if(BUILD_DRIVER)
	add_custom_target(
		driver ALL
		COMMAND ${MAKE_COMMAND}
		COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${DRIVER_NAME}.ko
				"${CMAKE_CURRENT_BINARY_DIR}"
		WORKING_DIRECTORY src
		VERBATIM
	)
else()
	add_custom_target(
		driver
		COMMAND ${MAKE_COMMAND}
		COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${DRIVER_NAME}.ko
				"${CMAKE_CURRENT_BINARY_DIR}"
		WORKING_DIRECTORY src
		VERBATIM
	)
endif()

add_custom_target(
	install_driver
	COMMAND ${MAKE_COMMAND} install
	DEPENDS driver
	WORKING_DIRECTORY src
	VERBATIM
)

if(ENABLE_DKMS)
	install(
		FILES ${CMAKE_CURRENT_BINARY_DIR}/src/Makefile ${CMAKE_CURRENT_BINARY_DIR}/src/dkms.conf
			  ${CMAKE_CURRENT_BINARY_DIR}/src/driver_config.h ${DRIVER_SOURCES}
		DESTINATION "src/${DRIVER_PACKAGE_NAME}-${DRIVER_VERSION}"
		COMPONENT ${DRIVER_KMOD_COMPONENT_NAME}
	)
endif()

add_subdirectory(bpf)
