/*
 * Copyright © 2020-2021 Collabora Ltd.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "graphics-provider.h"

#include "steam-runtime-tools/resolve-in-sysroot-internal.h"
#include "steam-runtime-tools/system-info-internal.h"
#include "steam-runtime-tools/utils-internal.h"

#include "utils.h"

enum {
  PROP_0,
  PROP_PATH_IN_CURRENT_NS,
  PROP_PATH_IN_CONTAINER_NS,
  N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = { NULL };

static void pv_graphics_provider_initable_iface_init (GInitableIface *iface,
                                                      gpointer unused);

G_DEFINE_TYPE_WITH_CODE (PvGraphicsProvider, pv_graphics_provider, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                pv_graphics_provider_initable_iface_init))

static void
pv_graphics_provider_init (PvGraphicsProvider *self)
{
  self->fd = -1;
}

static void
pv_graphics_provider_get_property (GObject *object,
                                   guint prop_id,
                                   GValue *value,
                                   GParamSpec *pspec)
{
  PvGraphicsProvider *self = PV_GRAPHICS_PROVIDER (object);

  switch (prop_id)
    {
      case PROP_PATH_IN_CURRENT_NS:
        g_value_set_string (value, self->path_in_current_ns);
        break;

      case PROP_PATH_IN_CONTAINER_NS:
        g_value_set_string (value, self->path_in_container_ns);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
pv_graphics_provider_set_property (GObject *object,
                                   guint prop_id,
                                   const GValue *value,
                                   GParamSpec *pspec)
{
  PvGraphicsProvider *self = PV_GRAPHICS_PROVIDER (object);

  switch (prop_id)
    {
      case PROP_PATH_IN_CURRENT_NS:
        /* Construct-only */
        g_return_if_fail (self->path_in_current_ns == NULL);
        self->path_in_current_ns = g_value_dup_string (value);
        break;

      case PROP_PATH_IN_CONTAINER_NS:
        /* Construct-only */
        g_return_if_fail (self->path_in_container_ns == NULL);
        self->path_in_container_ns = g_value_dup_string (value);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
pv_graphics_provider_constructed (GObject *object)
{
  PvGraphicsProvider *self = PV_GRAPHICS_PROVIDER (object);

  G_OBJECT_CLASS (pv_graphics_provider_parent_class)->constructed (object);

  g_return_if_fail (self->path_in_current_ns != NULL);
  g_return_if_fail (self->path_in_container_ns != NULL);
}

static gboolean
pv_graphics_provider_initable_init (GInitable *initable,
                                    GCancellable *cancellable G_GNUC_UNUSED,
                                    GError **error)
{
  PvGraphicsProvider *self = PV_GRAPHICS_PROVIDER (initable);

  g_return_val_if_fail (PV_IS_GRAPHICS_PROVIDER (self), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!glnx_opendirat (-1, self->path_in_current_ns, FALSE,
                       &self->fd, error))
    return FALSE;

  /* Path that, when resolved in the host namespace, points to us */
  self->path_in_host_ns = pv_current_namespace_path_to_host_path (self->path_in_current_ns);

  return TRUE;
}

static void
pv_graphics_provider_finalize (GObject *object)
{
  PvGraphicsProvider *self = PV_GRAPHICS_PROVIDER (object);

  glnx_close_fd (&self->fd);
  g_free (self->path_in_current_ns);
  g_free (self->path_in_host_ns);
  g_free (self->path_in_container_ns);
}

static void
pv_graphics_provider_class_init (PvGraphicsProviderClass *cls)
{
  GObjectClass *object_class = G_OBJECT_CLASS (cls);

  object_class->get_property = pv_graphics_provider_get_property;
  object_class->set_property = pv_graphics_provider_set_property;
  object_class->constructed = pv_graphics_provider_constructed;
  object_class->finalize = pv_graphics_provider_finalize;

  properties[PROP_PATH_IN_CURRENT_NS] =
    g_param_spec_string ("path-in-current-ns", "Path in current namespace",
                         ("Path to the graphics provider in the current "
                          "namespace, typically /"),
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_PATH_IN_CONTAINER_NS] =
    g_param_spec_string ("path-in-container-ns", "Path in container namespace",
                         ("Path to the graphics provider in the container "
                          "namespace, typically /run/host"),
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}

gchar *
pv_graphics_provider_search_in_path_and_bin (PvGraphicsProvider *self,
                                             const gchar *program_name)
{
  const gchar * const common_bin_dirs[] =
  {
    "/usr/bin",
    "/bin",
    "/usr/sbin",
    "/sbin",
    NULL
  };

  g_return_val_if_fail (PV_IS_GRAPHICS_PROVIDER (self), NULL);
  g_return_val_if_fail (program_name != NULL, NULL);

  if (g_strcmp0 (self->path_in_current_ns, "/") == 0)
    {
      gchar *found_path = g_find_program_in_path (program_name);
      if (found_path != NULL)
        return found_path;
    }

  for (gsize i = 0; i < G_N_ELEMENTS (common_bin_dirs) - 1; i++)
    {
      g_autofree gchar *test_path = g_build_filename (common_bin_dirs[i],
                                                      program_name,
                                                      NULL);
      if (_srt_file_test_in_sysroot (self->path_in_current_ns, self->fd,
                                     test_path, G_FILE_TEST_IS_EXECUTABLE))
        return g_steal_pointer (&test_path);
    }

  return NULL;
}

PvGraphicsProvider *
pv_graphics_provider_new (const char *path_in_current_ns,
                          const char *path_in_container_ns,
                          GError **error)
{
  g_return_val_if_fail (path_in_current_ns != NULL, NULL);
  g_return_val_if_fail (path_in_container_ns != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  return g_initable_new (PV_TYPE_GRAPHICS_PROVIDER,
                         NULL,
                         error,
                         "path-in-container-ns", path_in_container_ns,
                         "path-in-current-ns", path_in_current_ns,
                         NULL);
}

/*
 * Create a new SrtSystemInfo, suitable for use in a separate thread.
 */
SrtSystemInfo *
pv_graphics_provider_create_system_info (PvGraphicsProvider *self)
{
  g_autoptr(SrtSystemInfo) system_info = NULL;

  g_return_val_if_fail (PV_IS_GRAPHICS_PROVIDER (self), NULL);

  system_info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (system_info, self->path_in_current_ns);
  _srt_system_info_set_check_flags (system_info,
                                    (SRT_CHECK_FLAGS_SKIP_SLOW_CHECKS
                                     | SRT_CHECK_FLAGS_SKIP_EXTRAS));
  return g_steal_pointer (&system_info);
}

static void
pv_graphics_provider_initable_iface_init (GInitableIface *iface,
                                          gpointer unused G_GNUC_UNUSED)
{
  iface->init = pv_graphics_provider_initable_init;
}