/*
 * Copyright © 2014-2018 Red Hat, Inc
 * Copyright © 2020 Collabora Ltd.
 *
 * 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 "environ.h"

/*
 * PvEnviron:
 *
 * Each environment variable we deal with in pressure-vessel has
 * the following possible values:
 *
 * - Set to a value (empty or non-empty)
 * - Forced to be unset
 * - Inherited from the execution environment of bwrap(1)
 *
 * together with a *locked* flag. The locked flag indicates that if the
 * container is running pressure-vessel-launcher(1), then the variable
 * cannot be overridden by pressure-vessel-launch(1).
 *
 * In particular, the locked flag can be combined with any value, and
 * a variable can also be locked to the inherited value.
 *
 * We represent this as follows:
 *
 * - Set to a value: values[VAR] = VAL
 * - Forced to be unset: values[VAR] = NULL
 * - Inherited from the execution environment of bwrap(1): VAR not in `values`
 *
 * Variables that are locked are also added to `locked`.
 */
struct _PvEnviron
{
  /* (element-type filename filename) */
  GHashTable *values;
  /* (element-type filename ignored) */
  GHashTable *locked;
};

PvEnviron *
pv_environ_new (void)
{
  g_autoptr(PvEnviron) self = g_slice_new0 (PvEnviron);

  self->values = g_hash_table_new_full (g_str_hash, g_str_equal,
                                        g_free, g_free);
  self->locked = g_hash_table_new_full (g_str_hash, g_str_equal,
                                        g_free, NULL);
  return g_steal_pointer (&self);
}

void
pv_environ_free (PvEnviron *self)
{
  g_return_if_fail (self != NULL);

  g_clear_pointer (&self->values, g_hash_table_unref);
  g_clear_pointer (&self->locked, g_hash_table_unref);
  g_slice_free (PvEnviron, self);
}

/*
 * Lock @var to be set to @val, which may be %NULL to unset it.
 * It cannot be overridden by a subsequent pressure-vessel-launch
 * invocation.
 */
void
pv_environ_lock_env (PvEnviron *self,
                     const char *var,
                     const char *val)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (var != NULL);
  /* val may be NULL, to lock it to be unset */

  g_hash_table_replace (self->values, g_strdup (var), g_strdup (val));
  g_hash_table_add (self->locked, g_strdup (var));
}

/*
 * Lock @var to whatever value it happens to have inherited, in a way
 * that cannot be overridden by a subsequent pressure-vessel-launch
 * invocation.
 */
void
pv_environ_lock_inherit_env (PvEnviron *self,
                             const char *var)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (var != NULL);

  g_hash_table_remove (self->values, var);
  g_hash_table_add (self->locked, g_strdup (var));
}

/*
 * Set @var to @val, in a way that can be overridden by a subsequent
 * pressure-vessel-launch invocation.
 */
void
pv_environ_set_env_overridable (PvEnviron *self,
                                const char *var,
                                const char *val)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (var != NULL);
  /* val may be NULL, to lock it to be unset */

  g_hash_table_replace (self->values, g_strdup (var), g_strdup (val));
  g_hash_table_remove (self->locked, var);
}

static int
generic_strcmp (gconstpointer a,
                gconstpointer b)
{
  return strcmp (a, b);
}

/*
 * Returns: (transfer container): The variables that are set or forced
 *  to be unset, but not the variables that are locked to be inherited
 */
GList *
pv_environ_get_vars (PvEnviron *self)
{
  g_return_val_if_fail (self != NULL, NULL);

  return g_list_sort (g_hash_table_get_keys (self->values), generic_strcmp);
}

/*
 * Returns: (transfer container): The variables that are locked in some way
 */
GList *
pv_environ_get_locked (PvEnviron *self)
{
  g_return_val_if_fail (self != NULL, NULL);

  return g_list_sort (g_hash_table_get_keys (self->locked), generic_strcmp);
}

/*
 * Returns: %TRUE if @var is locked
 */
gboolean
pv_environ_is_locked (PvEnviron *self,
                      const char *var)
{
  g_return_val_if_fail (self != NULL, FALSE);

  return g_hash_table_contains (self->locked, var);
}

/*
 * Returns: (nullable): The value of @var, or %NULL if @var is either
 *  locked-to-be-unset, locked-to-be-inherited or unspecified
 */
const char *
pv_environ_getenv (PvEnviron *self,
                   const char *var)
{
  g_return_val_if_fail (self != NULL, NULL);

  return g_hash_table_lookup (self->values, var);
}