/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 *
 * Copyright 2025 GNOME Foundation, Inc.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *  - Philip Withnall <pwithnall@gnome.org>
 */

#include "config.h"

#include <gio/gio.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#include <locale.h>
#include <signal.h>
#include <sysexits.h>


typedef unsigned int SourceHandle;

static void
source_handle_clear (SourceHandle *handle)
{
  g_clear_handle_id (handle, g_source_remove);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (SourceHandle, source_handle_clear)

const char *error_file = "/var/lib/malcontent-webd/update-error";
const char *error_file_group = "Web Filter Update Error";

/* Writes out a file like the following, containing error information if the
 * malcontent-webd process failed. This can then be reported to the parent when
 * they next log in or check web filtering settings.
 *
 * ```
 * [Web Filter Update Error]
 * ErrorCode=org.freedesktop.MalcontentWeb1.Filtering.Error.Downloading
 * ErrorMessage[en_GB]=There was an error downloading foo from blah.
 * ```
 *
 * Returns an exit status.
 */
static int
report_error (const GError *error)
{
  g_autoptr(GKeyFile) key_file = NULL;
  g_autoptr(GError) local_error = NULL;
  const char *current_locale = setlocale (LC_ALL, NULL);
  g_autoptr(GError) stripped_error = NULL;
  g_autofree char *error_code = g_dbus_error_get_remote_error (error);
  g_autofree char *key_file_data = NULL;
  size_t key_file_data_length = 0;

  /* Output a copy of the error to the journal. */
  stripped_error = g_error_copy (error);
  g_dbus_error_strip_remote_error (stripped_error);

  g_log_structured (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                    "CODE_FILE", __FILE__,
                    "CODE_LINE", G_STRINGIFY (__LINE__),
                    "CODE_FUNC", G_STRFUNC,
                    "SYSLOG_IDENTIFIER", g_get_prgname (),
                    "ERROR_CODE", (error_code != NULL) ? error_code : "",
                    "MESSAGE_ID", "e91b59b0c8e24b9b99a15e0004c2f1d0",
                    "MESSAGE", "%s", stripped_error->message);

  /* If we were cancelled, just ignore everything. */
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    return EX_TEMPFAIL;

  key_file = g_key_file_new ();

  if (error_code != NULL)
    g_key_file_set_string (key_file, error_file_group, "ErrorCode", error_code);
  g_key_file_set_locale_string (key_file, error_file_group, "ErrorMessage", current_locale, stripped_error->message);

  /* Save it using g_file_set_contents_full() so we can set the permissions so
   * all users can read. */
  key_file_data = g_key_file_to_data (key_file, &key_file_data_length, &local_error);
  if (key_file_data == NULL ||
      !g_file_set_contents_full (error_file, key_file_data, key_file_data_length,
                                 G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_DURABLE,
                                 0644, &local_error))
    {
      g_warning (_("Error updating error file ‘%s’: %s"), error_file, local_error->message);
    }

  return EX_UNAVAILABLE;
}

static void
clear_error (void)
{
  g_unlink (error_file);
}

static gboolean
sigint_cb (void *user_data)
{
  GCancellable *cancellable = G_CANCELLABLE (user_data);

  g_cancellable_cancel (cancellable);

  return G_SOURCE_CONTINUE;
}

int
main (int   argc,
      char *argv[])
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GCancellable) cancellable = NULL;
  g_auto(SourceHandle) sigint_id = 0;
  g_autoptr(GDBusConnection) connection = NULL;
  g_autoptr(GVariant) reply = NULL;
  g_autoptr(GError) local_error = NULL;

  setlocale (LC_ALL, "");

  /* --help handling */
  context = g_option_context_new (_("- trigger a web filter update"));
  if (!g_option_context_parse (context, &argc, &argv, &local_error))
    {
      g_warning ("%s", local_error->message);
      return EX_USAGE;
    }

  /* Handle cancellation, since everything here runs sync. */
  cancellable = g_cancellable_new ();
  sigint_id = g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, sigint_cb,
                                      g_object_ref (cancellable), g_object_unref);

  /* Call the malcontent-webd daemon via D-Bus and get it to update all the
   * users’ web filters. */
  connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, &local_error);

  if (connection == NULL)
    return report_error (local_error);

  reply = g_dbus_connection_call_sync (connection,
                                       "org.freedesktop.MalcontentWeb1",
                                       "/org/freedesktop/MalcontentWeb1",
                                       "org.freedesktop.MalcontentWeb1.Filtering",
                                       "UpdateFilters",
                                       g_variant_new ("(u)", 0),  /* zero means ‘all users’ */
                                       NULL,
                                       G_DBUS_CALL_FLAGS_NONE,
                                       G_MAXINT,  /* no timeout */
                                       cancellable,
                                       &local_error);

  if (reply == NULL)
    return report_error (local_error);

  clear_error ();

  return 0;
}

