/*
 * Copyright 2015 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#define G_LOG_DOMAIN "FuMain"

#include "config.h"

#include <fwupd.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <locale.h>
#include <stdlib.h>

#ifdef HAVE_GIO_UNIX
#include <glib-unix.h>
#endif
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif

#include "fu-daemon.h"
#include "fu-debug.h"

#ifdef HAVE_GIO_UNIX
static gboolean
fu_main_sigterm_cb(gpointer user_data)
{
	FuDaemon *daemon = FU_DAEMON(user_data);
	g_autoptr(GError) error = NULL;
	g_warning("Received SIGTERM");
	if (!fu_daemon_stop(daemon, &error))
		g_warning("failed to stop daemon, will wait: %s\n", error->message);
	return G_SOURCE_CONTINUE;
}
#endif

static gboolean
fu_main_timed_exit_cb(gpointer user_data)
{
	FuDaemon *daemon = FU_DAEMON(user_data);
	g_autoptr(GError) error = NULL;
	if (!fu_daemon_stop(daemon, &error))
		g_warning("failed to stop daemon, will wait: %s\n", error->message);
	return G_SOURCE_REMOVE;
}

static void
fu_main_argv_changed_cb(GFileMonitor *monitor,
			GFile *file,
			GFile *other_file,
			GFileMonitorEvent event_type,
			gpointer user_data)
{
	FuDaemon *daemon = FU_DAEMON(user_data);
	g_autoptr(GError) error = NULL;
	g_info("binary changed, shutting down");
	if (!fu_daemon_stop(daemon, &error))
		g_warning("failed to stop daemon, will wait: %s\n", error->message);
}

#if GLIB_CHECK_VERSION(2, 64, 0)
static void
fu_main_memory_monitor_warning_cb(GMemoryMonitor *memory_monitor,
				  GMemoryMonitorWarningLevel level,
				  FuDaemon *daemon)
{
	g_autoptr(GError) error = NULL;
	g_info("OOM event, shutting down");
	if (!fu_daemon_stop(daemon, &error))
		g_warning("failed to stop daemon, will wait: %s\n", error->message);
}
#endif

int
main(int argc, char *argv[])
{
	gboolean immediate_exit = FALSE;
	gboolean timed_exit = FALSE;
	const gchar *socket_filename = g_getenv("FWUPD_DBUS_SOCKET");
	const GOptionEntry options[] = {
	    {"timed-exit",
	     '\0',
	     0,
	     G_OPTION_ARG_NONE,
	     &timed_exit,
	     /* TRANSLATORS: exit after we've started up, used for user profiling */
	     N_("Exit after a small delay"),
	     NULL},
	    {"immediate-exit",
	     '\0',
	     0,
	     G_OPTION_ARG_NONE,
	     &immediate_exit,
	     /* TRANSLATORS: exit straight away, used for automatic profiling */
	     N_("Exit after the engine has loaded"),
	     NULL},
	    {NULL}};
	g_autofree gchar *socket_address = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) argv0_file = g_file_new_for_path(argv[0]);
	g_autoptr(GOptionContext) context = NULL;
	g_autoptr(FuDaemon) daemon = fu_daemon_new();
	g_autoptr(GFileMonitor) argv0_monitor = NULL;
#if GLIB_CHECK_VERSION(2, 64, 0)
	g_autoptr(GMemoryMonitor) memory_monitor = NULL;
#endif

	setlocale(LC_ALL, "");

	bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
	textdomain(GETTEXT_PACKAGE);

	/* TRANSLATORS: program name */
	g_set_application_name(_("Firmware Update Daemon"));
	context = g_option_context_new(NULL);
	g_option_context_add_main_entries(context, options, NULL);
	g_option_context_add_group(context, fu_debug_get_option_group());
	/* TRANSLATORS: program summary */
	g_option_context_set_summary(context, _("Firmware Update D-Bus Service"));
	if (!g_option_context_parse(context, &argc, &argv, &error)) {
		g_printerr("Failed to parse command line: %s\n", error->message);
		return EXIT_FAILURE;
	}

#ifdef FWUPD_DBUS_SOCKET_ADDRESS
	/* this is set for macOS and Windows */
	if (socket_filename == NULL)
		socket_filename = g_strdup(FWUPD_DBUS_SOCKET_ADDRESS);
#endif

	/* convert from filename to address, if required */
	if (socket_filename != NULL) {
		if (g_strrstr(socket_filename, "=") == NULL) {
#ifndef HAVE_SYSTEMD
			/* this must be owned by root */
			if (g_file_test(socket_filename, G_FILE_TEST_EXISTS))
				g_unlink(socket_filename);
#endif
			socket_address = g_strdup_printf("unix:path=%s", socket_filename);
		} else {
			socket_address = g_strdup(socket_filename);
		}
	}

	/* set up the daemon, which includes coldplugging devices */
	if (!fu_daemon_setup(daemon, socket_address, &error)) {
		g_printerr("Failed to load daemon: %s\n", error->message);
		return EXIT_FAILURE;
	}

#ifdef HAVE_GIO_UNIX
	g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGTERM, fu_main_sigterm_cb, daemon, NULL);
#endif /* HAVE_GIO_UNIX */

	/* restart the daemon if the binary gets replaced */
	argv0_monitor = g_file_monitor_file(argv0_file, G_FILE_MONITOR_NONE, NULL, &error);
	g_signal_connect(G_FILE_MONITOR(argv0_monitor),
			 "changed",
			 G_CALLBACK(fu_main_argv_changed_cb),
			 daemon);

#if GLIB_CHECK_VERSION(2, 64, 0)
	/* shut down on low memory event as we can just rescan hardware */
	memory_monitor = g_memory_monitor_dup_default();
	if (memory_monitor != NULL) {
		g_signal_connect(G_MEMORY_MONITOR(memory_monitor),
				 "low-memory-warning",
				 G_CALLBACK(fu_main_memory_monitor_warning_cb),
				 daemon);
	}
#endif

	/* Only timeout and close the mainloop if we have specified it
	 * on the command line */
	if (immediate_exit)
		g_idle_add(fu_main_timed_exit_cb, daemon);
	else if (timed_exit)
		g_timeout_add_seconds(5, fu_main_timed_exit_cb, daemon);

	/* wait */
	g_message("fwupd %s ready for requests (locale %s)", VERSION, g_getenv("LANG"));
	if (!fu_daemon_start(daemon, &error)) {
		g_printerr("Failed to start daemon: %s\n", error->message);
		return EXIT_FAILURE;
	}

#ifdef HAVE_SYSTEMD
	/* notify the service manager */
	sd_notify(0, "STOPPING=1");
#endif

	/* cancel to avoid a deadlock */
	g_file_monitor_cancel(argv0_monitor);

	/* success */
	return EXIT_SUCCESS;
}
