// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2015  Intel Corporation. All rights reserved.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <string.h>
#include <poll.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>

#ifndef WAIT_ANY
#define WAIT_ANY (-1)
#endif

#include "src/shared/mainloop.h"
#include "src/shared/util.h"
#include "peripheral/efivars.h"
#include "peripheral/attach.h"
#include "peripheral/gap.h"
#include "peripheral/log.h"

static bool is_init = false;
static pid_t shell_pid = -1;

static const struct {
	const char *target;
	const char *linkpath;
} dev_table[] = {
	{ "/proc/self/fd",	"/dev/fd"	},
	{ "/proc/self/fd/0",	"/dev/stdin"	},
	{ "/proc/self/fd/1",	"/dev/stdout"	},
	{ "/proc/self/fd/2",	"/dev/stderr"	},
	{ }
};

static const struct {
	const char *fstype;
	const char *target;
	const char *options;
	unsigned long flags;
} mount_table[] = {
	{ "sysfs",    "/sys",		NULL,	MS_NOSUID|MS_NOEXEC|MS_NODEV },
	{ "proc",     "/proc",		NULL,	MS_NOSUID|MS_NOEXEC|MS_NODEV },
	{ "devtmpfs", "/dev",		NULL,	MS_NOSUID|MS_STRICTATIME },
	{ "efivarfs", "/sys/firmware/efi/efivars",
					NULL,	MS_NOSUID|MS_NOEXEC|MS_NODEV },
	{ "pstore",   "/sys/fs/pstore",	NULL,	MS_NOSUID|MS_NOEXEC|MS_NODEV },
	{ }
};

static void prepare_filesystem(void)
{
	int i;

	if (!is_init)
		return;

	for (i = 0; mount_table[i].fstype && mount_table[i].target; i++) {
		struct stat st;

		if (lstat(mount_table[i].target, &st) < 0) {
			printf("Creating %s\n", mount_table[i].target);
			if (mkdir(mount_table[i].target, 0755) < 0)
				perror("Failed to create dir");
		}

		printf("Mounting %s to %s\n", mount_table[i].fstype,
						mount_table[i].target);

		if (mount(mount_table[i].fstype,
				mount_table[i].target,
				mount_table[i].fstype,
				mount_table[i].flags, NULL) < 0)
			perror("Failed to mount filesystem");
	}

	for (i = 0; dev_table[i].target; i++) {
		printf("Linking %s to %s\n", dev_table[i].linkpath,
						dev_table[i].target);

		if (symlink(dev_table[i].target, dev_table[i].linkpath) < 0)
			perror("Failed to create device symlink");
	}
}

static void run_shell(void)
{
	pid_t pid;

	printf("Starting shell\n");

	pid = fork();
	if (pid < 0) {
		perror("Failed to fork new process");
		return;
	}

	if (pid == 0) {
		char *prg_argv[] = { "/bin/sh", NULL };
		char *prg_envp[] = { NULL };

		execve(prg_argv[0], prg_argv, prg_envp);
		exit(0);
	}

	printf("PID %d created\n", pid);

	shell_pid = pid;
}

static void exit_shell(void)
{
	shell_pid = -1;

	if (!is_init) {
		mainloop_quit();
		return;
	}

	run_shell();
}

static void signal_callback(int signum, void *user_data)
{
	switch (signum) {
	case SIGINT:
	case SIGTERM:
		mainloop_quit();
		break;
	case SIGCHLD:
		while (1) {
			pid_t pid;
			int status;

			pid = waitpid(WAIT_ANY, &status, WNOHANG);
			if (pid < 0 || pid == 0)
				break;

			if (WIFEXITED(status)) {
				printf("PID %d exited (status=%d)\n",
						pid, WEXITSTATUS(status));

				if (pid == shell_pid)
					exit_shell();
			} else if (WIFSIGNALED(status)) {
				printf("PID %d terminated (signal=%d)\n",
							pid, WTERMSIG(status));

				if (pid == shell_pid)
					exit_shell();
			}
		}
		break;
	}
}

int main(int argc, char *argv[])
{
	int exit_status;

	if (getpid() == 1 && getppid() == 0)
		is_init = true;

	mainloop_init();

	printf("Bluetooth periperhal ver %s\n", VERSION);

	prepare_filesystem();

	if (is_init) {
		uint8_t addr[6];

		if (efivars_read("BluetoothStaticAddress", NULL,
							addr, 6) < 0) {
			printf("Generating new persistent static address\n");

			if (util_getrandom(addr, sizeof(addr), 0) < 0) {
				perror("Failed to get random static address");
				return EXIT_FAILURE;
			}
			/* Overwrite the MSB to make it a static address */
			addr[5] = 0xc0;

			efivars_write("BluetoothStaticAddress",
					EFIVARS_NON_VOLATILE |
					EFIVARS_BOOTSERVICE_ACCESS |
					EFIVARS_RUNTIME_ACCESS,
					addr, 6);
		}

		gap_set_static_address(addr);

		run_shell();
	}

	log_open();
	gap_start();

	if (is_init)
		attach_start();

	exit_status = mainloop_run_with_signal(signal_callback, NULL);

	if (is_init)
		attach_stop();

	gap_stop();
	log_close();

	return exit_status;
}
