Blame shared/nm-glib-aux/nm-keyfile-aux.c

Packit Service b23acc
// SPDX-License-Identifier: LGPL-2.1+
Packit Service b23acc
/*
Packit Service b23acc
 * Copyright (C) 2019 Red Hat, Inc.
Packit Service b23acc
 */
Packit Service b23acc
Packit Service b23acc
#include "nm-default.h"
Packit Service b23acc
Packit Service b23acc
#include "nm-keyfile-aux.h"
Packit Service b23acc
Packit Service b23acc
#include <syslog.h>
Packit Service b23acc
#include <sys/stat.h>
Packit Service b23acc
#include <fcntl.h>
Packit Service b23acc
Packit Service b23acc
#include "nm-io-utils.h"
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
struct _NMKeyFileDB {
Packit Service b23acc
	NMKeyFileDBLogFcn log_fcn;
Packit Service b23acc
	NMKeyFileDBGotDirtyFcn got_dirty_fcn;
Packit Service b23acc
	gpointer user_data;
Packit Service b23acc
	const char *group_name;
Packit Service b23acc
	GKeyFile *kf;
Packit Service b23acc
	guint ref_count;
Packit Service b23acc
Packit Service b23acc
	bool is_started:1;
Packit Service b23acc
	bool dirty:1;
Packit Service b23acc
	bool destroyed:1;
Packit Service b23acc
Packit Service b23acc
	char filename[];
Packit Service b23acc
};
Packit Service b23acc
Packit Service b23acc
#define _NMLOG(self, \
Packit Service b23acc
               syslog_level, \
Packit Service b23acc
               fmt, \
Packit Service b23acc
               ...) \
Packit Service b23acc
	G_STMT_START { \
Packit Service b23acc
		NMKeyFileDB *_self = (self); \
Packit Service b23acc
		\
Packit Service b23acc
		nm_assert (_self); \
Packit Service b23acc
		nm_assert (!_self->destroyed); \
Packit Service b23acc
		\
Packit Service b23acc
		if (_self->log_fcn) { \
Packit Service b23acc
			_self->log_fcn (_self, \
Packit Service b23acc
			                (syslog_level), \
Packit Service b23acc
			                _self->user_data, \
Packit Service b23acc
			                ""fmt"", \
Packit Service b23acc
			                ##__VA_ARGS__); \
Packit Service b23acc
		}; \
Packit Service b23acc
	} G_STMT_END
Packit Service b23acc
Packit Service b23acc
#define _LOGD(...) _NMLOG (self, LOG_DEBUG, __VA_ARGS__)
Packit Service b23acc
Packit Service b23acc
static gboolean
Packit Service b23acc
_IS_KEY_FILE_DB (NMKeyFileDB *self, gboolean require_is_started, gboolean allow_destroyed)
Packit Service b23acc
{
Packit Service b23acc
	if (self == NULL)
Packit Service b23acc
		return FALSE;
Packit Service b23acc
	if (self->ref_count <= 0) {
Packit Service b23acc
		nm_assert_not_reached ();
Packit Service b23acc
		return FALSE;
Packit Service b23acc
	}
Packit Service b23acc
	if (   require_is_started
Packit Service b23acc
	    && !self->is_started)
Packit Service b23acc
		return FALSE;
Packit Service b23acc
	if (   !allow_destroyed
Packit Service b23acc
	    && self->destroyed)
Packit Service b23acc
		return FALSE;
Packit Service b23acc
	return TRUE;
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
NMKeyFileDB *
Packit Service b23acc
nm_key_file_db_new (const char *filename,
Packit Service b23acc
                    const char *group_name,
Packit Service b23acc
                    NMKeyFileDBLogFcn log_fcn,
Packit Service b23acc
                    NMKeyFileDBGotDirtyFcn got_dirty_fcn,
Packit Service b23acc
                    gpointer user_data)
Packit Service b23acc
{
Packit Service b23acc
	NMKeyFileDB *self;
Packit Service b23acc
	gsize l_filename;
Packit Service b23acc
	gsize l_group;
Packit Service b23acc
Packit Service b23acc
	g_return_val_if_fail (filename && filename[0], NULL);
Packit Service b23acc
	g_return_val_if_fail (group_name && group_name[0], NULL);
Packit Service b23acc
Packit Service b23acc
	l_filename = strlen (filename);
Packit Service b23acc
	l_group = strlen (group_name);
Packit Service b23acc
Packit Service b23acc
	self = g_malloc0 (sizeof (NMKeyFileDB) + l_filename + 1 + l_group + 1);
Packit Service b23acc
	self->ref_count = 1;
Packit Service b23acc
	self->log_fcn = log_fcn;
Packit Service b23acc
	self->got_dirty_fcn = got_dirty_fcn;
Packit Service b23acc
	self->user_data = user_data;
Packit Service b23acc
	self->kf = g_key_file_new ();
Packit Service b23acc
	g_key_file_set_list_separator (self->kf, ',');
Packit Service b23acc
	memcpy (self->filename, filename, l_filename + 1);
Packit Service b23acc
	self->group_name = &self->filename[l_filename + 1];
Packit Service b23acc
	memcpy ((char *) self->group_name, group_name, l_group + 1);
Packit Service b23acc
Packit Service b23acc
	return self;
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
NMKeyFileDB *
Packit Service b23acc
nm_key_file_db_ref (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	if (!self)
Packit Service b23acc
		return NULL;
Packit Service b23acc
Packit Service b23acc
	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL);
Packit Service b23acc
Packit Service b23acc
	nm_assert (self->ref_count < G_MAXUINT);
Packit Service b23acc
	self->ref_count++;
Packit Service b23acc
	return self;
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_unref (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	if (!self)
Packit Service b23acc
		return;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE));
Packit Service b23acc
Packit Service b23acc
	if (--self->ref_count > 0)
Packit Service b23acc
		return;
Packit Service b23acc
Packit Service b23acc
	g_key_file_unref (self->kf);
Packit Service b23acc
Packit Service b23acc
	g_free (self);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/* destroy() is like unref, but it also makes the instance unusable.
Packit Service b23acc
 * All changes afterwards fail with an assertion.
Packit Service b23acc
 *
Packit Service b23acc
 * The point is that NMKeyFileDB is ref-counted in principle. But there
Packit Service b23acc
 * is a primary owner who also provides the log_fcn().
Packit Service b23acc
 *
Packit Service b23acc
 * When the primary owner goes out of scope and gives up the reference, it does
Packit Service b23acc
 * not want to receive any log notifications anymore.
Packit Service b23acc
 *
Packit Service b23acc
 * The way NMKeyFileDB is intended to be used is in a very strict context:
Packit Service b23acc
 * NMSettings owns the NMKeyFileDB instance and receives logging notifications.
Packit Service b23acc
 * It's also the last one to persist the data to disk. Afterwards, no other user
Packit Service b23acc
 * is supposed to be around and do anything with NMKeyFileDB. But since NMKeyFileDB
Packit Service b23acc
 * is ref-counted it's hard to ensure that this is truly honored. So we start
Packit Service b23acc
 * asserting at that point.
Packit Service b23acc
 */
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_destroy (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	if (!self)
Packit Service b23acc
		return;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE));
Packit Service b23acc
	g_return_if_fail (!self->destroyed);
Packit Service b23acc
Packit Service b23acc
	self->destroyed = TRUE;
Packit Service b23acc
	nm_key_file_db_unref (self);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
/* nm_key_file_db_start() is supposed to be called right away, after creating the
Packit Service b23acc
 * instance.
Packit Service b23acc
 *
Packit Service b23acc
 * It's not done as separate step after nm_key_file_db_new(), because we want to log,
Packit Service b23acc
 * and the log_fcn returns the self pointer (which we should not expose before
Packit Service b23acc
 * nm_key_file_db_new() returns. */
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_start (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	gs_free char *contents = NULL;
Packit Service b23acc
	gsize contents_len;
Packit Service b23acc
	gs_free_error GError *error = NULL;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE));
Packit Service b23acc
	g_return_if_fail (!self->is_started);
Packit Service b23acc
Packit Service b23acc
	self->is_started = TRUE;
Packit Service b23acc
Packit Service b23acc
	if (!nm_utils_file_get_contents (-1,
Packit Service b23acc
	                                 self->filename,
Packit Service b23acc
	                                 20*1024*1024,
Packit Service b23acc
	                                 NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
Packit Service b23acc
	                                 &contents,
Packit Service b23acc
	                                 &contents_len,
Packit Service b23acc
	                                 NULL,
Packit Service b23acc
	                                 &error)) {
Packit Service b23acc
		_LOGD ("failed to read \"%s\": %s", self->filename, error->message);
Packit Service b23acc
		return;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (!g_key_file_load_from_data (self->kf,
Packit Service b23acc
	                                contents,
Packit Service b23acc
	                                contents_len,
Packit Service b23acc
	                                G_KEY_FILE_KEEP_COMMENTS,
Packit Service b23acc
	                                &error)) {
Packit Service b23acc
		_LOGD ("failed to load keyfile \"%s\": %s", self->filename, error->message);
Packit Service b23acc
		return;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	_LOGD ("loaded keyfile-db for \"%s\"", self->filename);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
const char *
Packit Service b23acc
nm_key_file_db_get_filename (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL);
Packit Service b23acc
Packit Service b23acc
	return self->filename;
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
gboolean
Packit Service b23acc
nm_key_file_db_is_dirty (NMKeyFileDB *self)
Packit Service b23acc
{
Packit Service b23acc
	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), FALSE);
Packit Service b23acc
Packit Service b23acc
	return self->dirty;
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
char *
Packit Service b23acc
nm_key_file_db_get_value (NMKeyFileDB *self,
Packit Service b23acc
                          const char *key)
Packit Service b23acc
{
Packit Service b23acc
	g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL);
Packit Service b23acc
Packit Service b23acc
	return g_key_file_get_value (self->kf, self->group_name, key, NULL);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
char **
Packit Service b23acc
nm_key_file_db_get_string_list (NMKeyFileDB *self,
Packit Service b23acc
                                const char *key,
Packit Service b23acc
                                gsize *out_len)
Packit Service b23acc
{
Packit Service b23acc
	g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL);
Packit Service b23acc
Packit Service b23acc
	return g_key_file_get_string_list (self->kf, self->group_name, key, out_len, NULL);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
static void
Packit Service b23acc
_got_dirty (NMKeyFileDB *self,
Packit Service b23acc
            const char *key)
Packit Service b23acc
{
Packit Service b23acc
	nm_assert (_IS_KEY_FILE_DB (self, TRUE, FALSE));
Packit Service b23acc
	nm_assert (!self->dirty);
Packit Service b23acc
Packit Service b23acc
	_LOGD ("updated entry for %s.%s", self->group_name, key);
Packit Service b23acc
Packit Service b23acc
	self->dirty = TRUE;
Packit Service b23acc
	if (self->got_dirty_fcn)
Packit Service b23acc
		self->got_dirty_fcn (self, self->user_data);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_remove_key (NMKeyFileDB *self,
Packit Service b23acc
                           const char *key)
Packit Service b23acc
{
Packit Service b23acc
	gboolean got_dirty = FALSE;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
Packit Service b23acc
Packit Service b23acc
	if (!key)
Packit Service b23acc
		return;
Packit Service b23acc
Packit Service b23acc
	if (!self->dirty) {
Packit Service b23acc
		gs_free_error GError *error = NULL;
Packit Service b23acc
Packit Service b23acc
		g_key_file_has_key (self->kf, self->group_name, key, &error);
Packit Service b23acc
		got_dirty = (error != NULL);
Packit Service b23acc
	}
Packit Service b23acc
	g_key_file_remove_key (self->kf, self->group_name, key, NULL);
Packit Service b23acc
Packit Service b23acc
	if (got_dirty)
Packit Service b23acc
		_got_dirty (self, key);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_set_value (NMKeyFileDB *self,
Packit Service b23acc
                          const char *key,
Packit Service b23acc
                          const char *value)
Packit Service b23acc
{
Packit Service b23acc
	gs_free char *old_value = NULL;
Packit Service b23acc
	gboolean got_dirty = FALSE;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
Packit Service b23acc
	g_return_if_fail (key);
Packit Service b23acc
Packit Service b23acc
	if (!value) {
Packit Service b23acc
		nm_key_file_db_remove_key (self, key);
Packit Service b23acc
		return;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (!self->dirty) {
Packit Service b23acc
		gs_free_error GError *error = NULL;
Packit Service b23acc
Packit Service b23acc
		old_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
Packit Service b23acc
		if (error)
Packit Service b23acc
			got_dirty = TRUE;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	g_key_file_set_value (self->kf, self->group_name, key, value);
Packit Service b23acc
Packit Service b23acc
	if (   !self->dirty
Packit Service b23acc
	    && !got_dirty) {
Packit Service b23acc
		gs_free_error GError *error = NULL;
Packit Service b23acc
		gs_free char *new_value = NULL;
Packit Service b23acc
Packit Service b23acc
		new_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
Packit Service b23acc
		if (   error
Packit Service b23acc
		    || !new_value
Packit Service b23acc
		    || !nm_streq0 (old_value, new_value))
Packit Service b23acc
			got_dirty = TRUE;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (got_dirty)
Packit Service b23acc
		_got_dirty (self, key);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_set_string_list (NMKeyFileDB *self,
Packit Service b23acc
                                const char *key,
Packit Service b23acc
                                const char *const*value,
Packit Service b23acc
                                gssize len)
Packit Service b23acc
{
Packit Service b23acc
	gs_free char *old_value = NULL;
Packit Service b23acc
	gboolean got_dirty = FALSE;;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
Packit Service b23acc
	g_return_if_fail (key);
Packit Service b23acc
Packit Service b23acc
	if (!value) {
Packit Service b23acc
		nm_key_file_db_remove_key (self, key);
Packit Service b23acc
		return;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (!self->dirty) {
Packit Service b23acc
		gs_free_error GError *error = NULL;
Packit Service b23acc
Packit Service b23acc
		old_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
Packit Service b23acc
		if (error)
Packit Service b23acc
			got_dirty = TRUE;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (len < 0)
Packit Service b23acc
		len = NM_PTRARRAY_LEN (value);
Packit Service b23acc
Packit Service b23acc
	g_key_file_set_string_list (self->kf, self->group_name, key, value, len);
Packit Service b23acc
Packit Service b23acc
	if (   !self->dirty
Packit Service b23acc
	    && !got_dirty) {
Packit Service b23acc
		gs_free_error GError *error = NULL;
Packit Service b23acc
		gs_free char *new_value = NULL;
Packit Service b23acc
Packit Service b23acc
		new_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
Packit Service b23acc
		if (   error
Packit Service b23acc
		    || !new_value
Packit Service b23acc
		    || !nm_streq0 (old_value, new_value))
Packit Service b23acc
			got_dirty = TRUE;
Packit Service b23acc
	}
Packit Service b23acc
Packit Service b23acc
	if (got_dirty)
Packit Service b23acc
		_got_dirty (self, key);
Packit Service b23acc
}
Packit Service b23acc
Packit Service b23acc
/*****************************************************************************/
Packit Service b23acc
Packit Service b23acc
void
Packit Service b23acc
nm_key_file_db_to_file (NMKeyFileDB *self,
Packit Service b23acc
                        gboolean force)
Packit Service b23acc
{
Packit Service b23acc
	gs_free_error GError *error = NULL;
Packit Service b23acc
Packit Service b23acc
	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
Packit Service b23acc
Packit Service b23acc
	if (   !force
Packit Service b23acc
	    && !self->dirty)
Packit Service b23acc
		return;
Packit Service b23acc
Packit Service b23acc
	self->dirty = FALSE;
Packit Service b23acc
Packit Service b23acc
	if (!g_key_file_save_to_file (self->kf,
Packit Service b23acc
	                              self->filename,
Packit Service b23acc
	                              &error)) {
Packit Service b23acc
		_LOGD ("failure to write keyfile \"%s\": %s", self->filename, error->message);
Packit Service b23acc
	} else
Packit Service b23acc
		_LOGD ("write keyfile: \"%s\"", self->filename);
Packit Service b23acc
}