From b8af2bad76c092d6e50549cbb1df5522ad1ed58f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=A4rdian?= Date: Fri, 19 Apr 2024 11:01:27 +0200 Subject: [PATCH] WIP: Use the np_state->_private->dirty_fields structure for origin-file tracking We track the list of filenames a specific (global) setting appeared in (origin-files) in a PATH like string, which separates the different files in reverse order of appearance, separated by colons. We use "__UNNAMED" as a placeholder when reading from non-files, e.g. an in memory YAML patch, provided by "netplan set". When writing back the specific setting we can look at the history of origin-files and decide if it should be written back to the latest file, to a (possibly new) fallback file (e.g. when it only appeared in __UNNAMED), or be dropped. --- src/netplan.c | 94 ++++++++++++++++++++++++++++++++++------ src/parse.c | 28 +++++++++++- src/types-internal.h | 2 + src/util-internal.h | 4 ++ src/util.c | 100 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 204 insertions(+), 24 deletions(-) diff --git a/src/netplan.c b/src/netplan.c index 980c35f7d..3ab5f6d7e 100644 --- a/src/netplan.c +++ b/src/netplan.c @@ -41,6 +41,13 @@ gchar *tmp = NULL; (_def)->_private->dirty_fields && \ g_hash_table_contains((_def)->_private->dirty_fields, _data_ref)) +/* +if (value_ptr && DIRTY(_def, value_ptr)) { \ + YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \ + YAML_SCALAR_QUOTED(event_ptr, emitter_ptr, (gchar*)g_hash_table_lookup(_def->_private->dirty_fields, &value_ptr)); \ + } \ + else +*/ #define YAML_STRING(_def, event_ptr, emitter_ptr, key, value_ptr) {\ if (value_ptr) { \ YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \ @@ -1039,24 +1046,55 @@ netplan_netdef_list_write_yaml(const NetplanState* np_state, GList* netdefs, int YAML_SCALAR_PLAIN(event, emitter, "network"); YAML_MAPPING_OPEN(event, emitter); /* We support version 2 only, currently */ + //FIXME: only write "version" stanza if dirty YAML_NONNULL_STRING_PLAIN(event, emitter, "version", "2"); /* fallback to default global handling, if renderer was not set for this file */ NetplanBackend renderer = netplan_state_get_backend(np_state); - /* Try to find a file specific (global) renderer. - * If this is the fallback file (70-netplan-set.yaml or .yaml), - * a renderer parsed from a YAML patch takes precedence. */ - if (out_fname && np_state->global_renderer) { - gpointer value; - renderer = GPOINTER_TO_INT(g_hash_table_lookup(np_state->global_renderer, out_fname)); - /* A renderer parsed from an (anonymous) YAML patch takes precendence - * (e.g. "netplan set ..."). Such data does not have any filename - * associated to it in the global_renderer map (i.e. empty string). */ - if (is_fallback && g_hash_table_lookup_extended(np_state->global_renderer, "", NULL, &value)) - renderer = GPOINTER_TO_INT(value); + //printf("STATE BACKEND OUT %p\n", &np_state->backend); + //printf("out_fname %s %d\n", out_fname, is_fallback); + if (np_state->_private && np_state->_private->dirty_fields) { + char* filepath = g_hash_table_lookup(np_state->_private->dirty_fields, &np_state->backend); + //printf("filepath <%s>\n", filepath); + // is set iff this field/setting was read somewhere in the hierarcy (i.e. is "dirty") + if (filepath) { + if ( (is_fallback && !out_fname) // dumping everything + || (is_fallback && g_str_has_prefix(filepath, __UNNAMED)) // new global settings (from an __UNNAMED file) are written to fallback path + || g_str_has_prefix(filepath, out_fname) // originates from this very file + ) { + YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", netplan_backend_name(renderer)); + } else if (g_strrstr(filepath, out_fname) && !g_str_has_prefix(filepath, out_fname)) { // keep the original value in lower priority file + /* + //XXX: parse just the single file and get the global renderer (original value) + NetplanParser *npp = netplan_parser_new(); + NetplanState *np_state = netplan_state_new(); + printf("res %d\n", netplan_parser_load_yaml(npp, out_fname, NULL)); + printf("res %d\n", netplan_state_import_parser_results(np_state, npp, NULL)); + NetplanBackend original_renderer = netplan_state_get_backend(np_state); + printf("ORIGINAL %d\n", original_renderer); + printf("netdefs %d\n", netplan_state_get_netdefs_size(np_state)); + FILE *fd = fopen(out_fname, "r"); + char file_buffer[1024] = {0}; + fread(file_buffer, 1, 1024, fd); + printf("list_write: out_fname: %s\n%s\n", out_fname, file_buffer); + */ + NetplanBackend original_renderer = 0; + gchar **split = g_strsplit(filepath, ":", 0); + for (unsigned i = 0; split[i]; ++i) { + //printf("SPLIT %s\n", split[i]); + char* value_ptr = g_strrstr(split[i], "/"); + if (value_ptr) + original_renderer = atoi(value_ptr+1); + + } + g_strfreev(split); + //printf("original: %s\n", netplan_backend_name(original_renderer)); + YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", netplan_backend_name(original_renderer)); + } else { + //printf("renderer ERROR: filepath: %s, out_fname: %s, is_fallback: %d, renderer: %s\n", filepath, out_fname, is_fallback, netplan_backend_name(renderer)); + } + } } - if (renderer == NETPLAN_BACKEND_NM || renderer == NETPLAN_BACKEND_NETWORKD) - YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", netplan_backend_name(renderer)); /* Do not write any netdefs, if we're just setting/updating some globals, * e.g.: netplan set "network.renderer=NetworkManager" */ @@ -1201,7 +1239,32 @@ netplan_state_update_yaml_hierarchy(const NetplanState* np_state, const char* de default_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", default_filename, NULL); int out_fd = -1; - /* Dump global conf to the default path */ + if (np_state->_private && np_state->_private->dirty_fields) { + char *renderer_filepath = g_hash_table_lookup(np_state->_private->dirty_fields, &np_state->backend); + //printf("RENDERER_FILEPATH %s\n", renderer_filepath); //FIXME filepath/value handling + if (renderer_filepath) { + gchar **split = g_strsplit(renderer_filepath, ":", 0); + for (unsigned i = 0; split[i]; ++i) { + //printf("SPLIT %s\n", split[i]); + // ignore __UNNAMED in update_yaml_hierarchy() and only edit files in /etc/netplan/ + if (g_strrstr(split[i], "/etc/netplan/")) { + g_hash_table_insert(perfile_netdefs, g_strdup(split[i]), NULL); + break; + } + } + g_strfreev(split); + } + } + + /* Dump global conf (renderer) to the default path, if dirty from __UNNAMED source */ + if (np_state->_private && np_state->_private->dirty_fields) { + const char* filepath = g_hash_table_lookup(np_state->_private->dirty_fields, &np_state->backend); + if (filepath && g_str_has_prefix(filepath, __UNNAMED)) { + g_hash_table_insert(perfile_netdefs, default_path, NULL); + } + } + + /* if (!np_state->netdefs || g_hash_table_size(np_state->netdefs) == 0) { if ( has_openvswitch(&np_state->ovs_settings, NETPLAN_BACKEND_NONE, NULL) || (np_state->backend != NETPLAN_BACKEND_NONE && np_state->global_renderer && @@ -1210,6 +1273,8 @@ netplan_state_update_yaml_hierarchy(const NetplanState* np_state, const char* de g_hash_table_insert(perfile_netdefs, default_path, NULL); } } else { + */ + if (np_state->netdefs && g_hash_table_size(np_state->netdefs) > 0) { GList* iter = np_state->netdefs_ordered; while (iter) { NetplanNetDefinition* netdef = iter->data; @@ -1245,6 +1310,7 @@ netplan_state_update_yaml_hierarchy(const NetplanState* np_state, const char* de out_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (out_fd < 0) goto file_error; + //printf("PERDEF %s %d\n", filename, is_fallback); if (!netplan_netdef_list_write_yaml(np_state, netdefs, out_fd, filename, is_fallback, error)) goto cleanup; // LCOV_EXCL_LINE close(out_fd); diff --git a/src/parse.c b/src/parse.c index 426d1673e..46d8cc190 100644 --- a/src/parse.c +++ b/src/parse.c @@ -279,6 +279,7 @@ get_handler(const mapping_entry_handler* handlers, const char* key) STATIC gboolean process_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const mapping_entry_handler* handlers, GList** out_values, GError** error) { + //fprintf(stderr, "PROCESS MAPPING %s\n", npp->current.filepath); yaml_node_pair_t* entry; assert_type(npp, node, YAML_MAPPING_NODE); @@ -294,6 +295,7 @@ process_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, c key = yaml_document_get_node(&npp->doc, entry->key); value = yaml_document_get_node(&npp->doc, entry->value); assert_type(npp, key, YAML_SCALAR_NODE); + //fprintf(stderr, "NODE %s\n", scalar(key)); if (npp->null_fields && key_prefix) { full_key = g_strdup_printf("%s\t%s", key_prefix, scalar(key)); if (g_hash_table_contains(npp->null_fields, full_key)) @@ -1212,19 +1214,22 @@ static const mapping_entry_handler wifi_access_point_handlers[] = { STATIC gboolean parse_renderer(NetplanParser* npp, yaml_node_t* node, NetplanBackend* backend, GError** error) { + //TODO: mark globals dirty if (strcmp(scalar(node), "networkd") == 0) *backend = NETPLAN_BACKEND_NETWORKD; else if (strcmp(scalar(node), "NetworkManager") == 0) *backend = NETPLAN_BACKEND_NM; else return yaml_error(npp, node, error, "unknown renderer '%s'", scalar(node)); - mark_data_as_dirty(npp, backend); + //mark_data_as_dirty(npp, backend); + mark_data_as_dirty_global(npp, backend, *backend); return TRUE; } STATIC gboolean handle_netdef_renderer(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error) { + //TODO: mark globals dirty if (npp->current.netdef->type == NETPLAN_DEF_TYPE_VLAN) { if (strcmp(scalar(node), "sriov") == 0) { npp->current.netdef->sriov_vlan_filter = TRUE; @@ -3066,6 +3071,8 @@ STATIC gboolean handle_network_renderer(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error) { gboolean res = parse_renderer(npp, node, &npp->global_backend, error); + //printf("### RENDER global_backend %p %s\n", &npp->global_backend, npp->current.filepath); fflush(stdout); + mark_data_as_dirty(npp, &npp->global_backend); if (!npp->global_renderer) npp->global_renderer = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); char* key = npp->current.filepath ? g_strdup(npp->current.filepath) : g_strdup(""); @@ -3327,6 +3334,7 @@ handle_network_type(NetplanParser* npp, yaml_node_t* node, const char* key_prefi set_str_if_null(npp->current.netdef->match.original_name, npp->current.netdef->id); } npp->current.backend = NETPLAN_BACKEND_NONE; + npp->current.netdef = NULL; return TRUE; } @@ -3489,6 +3497,8 @@ process_document(NetplanParser* npp, GError** error) STATIC gboolean _netplan_parser_load_single_file(NetplanParser* npp, const char *opt_filepath, yaml_document_t *doc, GError** error) { + //fprintf(stderr, "\n======================================\n"); + //fprintf(stderr, "PARSER: load_single_file %s\n", opt_filepath); int ret = FALSE; if (opt_filepath) { @@ -3615,7 +3625,20 @@ netplan_state_import_parser_results(NetplanState* np_state, NetplanParser* npp, } np_state->netdefs_ordered = g_list_concat(np_state->netdefs_ordered, npp->ordered); np_state->ovs_settings = npp->global_ovs_settings; - np_state->backend = npp->global_backend; + np_state->backend = npp->global_backend; //XXX: copy value vs transfer pointer for _private->dirty_fields + //printf("GLOBAL BACKEND: state %p, parser %p\n", &np_state->backend, &npp->global_backend); + // Handle global data on a per state scope ("renderer" for now) + if (!np_state->_private) + np_state->_private = g_new0(struct private_netdef_data, 1); + if (!np_state->_private->dirty_fields) + np_state->_private->dirty_fields = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + if (npp->_private && npp->_private->dirty_fields) { + //g_hash_table_insert(np_state->_private->dirty_fields, (void*)data_ptr, g_strdup(npp->current.filepath)); + char* filepath = g_hash_table_lookup(npp->_private->dirty_fields, &npp->global_backend); + g_hash_table_replace(np_state->_private->dirty_fields, &np_state->backend, filepath); + //printf("STATE BACKEND: %p, %s\n", &np_state->backend, filepath); + } + if (npp->sources) { if (!np_state->sources) @@ -3856,3 +3879,4 @@ netplan_parser_load_nullable_overrides( yaml_document_delete(&doc); return TRUE; } + //TODO: mark globals dirty diff --git a/src/types-internal.h b/src/types-internal.h index 691efd412..4bb3d2719 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -203,6 +203,7 @@ struct netplan_state { * char*) and is initialized with g_hash_table_new_full to avoid leaks. */ GHashTable* sources; GHashTable* global_renderer; + struct private_netdef_data* _private; }; struct netplan_parser { @@ -263,6 +264,7 @@ struct netplan_parser { GHashTable* null_fields; GHashTable* null_overrides; GHashTable* global_renderer; + struct private_netdef_data* _private; }; struct netplan_state_iterator { diff --git a/src/util-internal.h b/src/util-internal.h index e9994d0ff..f2a022b2a 100644 --- a/src/util-internal.h +++ b/src/util-internal.h @@ -24,6 +24,7 @@ #include #include "netplan.h" +#define __UNNAMED "__UNNAMED" #define SET_OPT_OUT_PTR(ptr,val) { if (ptr) *ptr = val; } #define __unused __attribute__((unused)) @@ -66,6 +67,9 @@ systemd_escape(char* string); void mark_data_as_dirty(NetplanParser* npp, const void* data_ptr); +void +mark_data_as_dirty_global(NetplanParser* npp, const void* data_ptr, int value); + const char* tunnel_mode_to_string(NetplanTunnelMode mode); diff --git a/src/util.c b/src/util.c index 659a990c3..f5e51cbfc 100644 --- a/src/util.c +++ b/src/util.c @@ -899,14 +899,98 @@ has_openvswitch(const NetplanOVSSettings* ovs, NetplanBackend backend, GHashTabl void mark_data_as_dirty(NetplanParser* npp, const void* data_ptr) { - // We don't support dirty tracking for globals yet. - if (!npp->current.netdef) - return; - if (!npp->current.netdef->_private) - npp->current.netdef->_private = g_new0(struct private_netdef_data, 1); - if (!npp->current.netdef->_private->dirty_fields) - npp->current.netdef->_private->dirty_fields = g_hash_table_new(g_direct_hash, g_direct_equal); - g_hash_table_insert(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, (void*)data_ptr); + //fprintf(stderr, "MARK_DIRTY %p\n", data_ptr); + if (!npp->current.netdef) { + // Handle global data on a per parser scope ("renderer" for now) + if (!npp->_private) + npp->_private = g_new0(struct private_netdef_data, 1); + if (!npp->_private->dirty_fields) + npp->_private->dirty_fields = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + char *old_filepath = (char*)g_hash_table_lookup(npp->_private->dirty_fields, (void*)data_ptr); + const char *filepath = npp->current.filepath ?: __UNNAMED; + //fprintf(stderr, "OLD FILEPATH %s\n", old_filepath); + if (!old_filepath) { + //fprintf(stderr, "XX INSERT %s\n", filepath); + g_hash_table_insert(npp->_private->dirty_fields, (void*)data_ptr, g_strdup(filepath)); + } else if (g_strrstr(old_filepath, filepath) != old_filepath) { //FIXME: why the duplication? + char *fpath = g_strdup_printf("%s:%s", filepath, old_filepath); + //fprintf(stderr, "XX PREPEND %s\n", fpath); + g_hash_table_replace(npp->_private->dirty_fields, (void*)data_ptr, fpath); + } + } else { + // Handle per netdef data + if (!npp->current.netdef->_private) + npp->current.netdef->_private = g_new0(struct private_netdef_data, 1); + if (!npp->current.netdef->_private->dirty_fields) + npp->current.netdef->_private->dirty_fields = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + + char *old_filepath = (char*)g_hash_table_lookup(npp->current.netdef->_private->dirty_fields, (void*)data_ptr); + const char *filepath = npp->current.filepath ?: __UNNAMED;; + //fprintf(stderr, "OLD FILEPATH %s\n", old_filepath); + if (!old_filepath) { + //fprintf(stderr, "YY INSERT %s\n", filepath); + g_hash_table_insert(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, g_strdup(filepath)); + //filepath = g_strdup_printf("%s:%s", old_filepath, __UNNAMED); + //printf("ADD3 %s\n", filepath); + //g_hash_table_replace(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, filepath); + } else if (g_strrstr(old_filepath, filepath) != old_filepath) { //FIXME: why the duplication? + char *fpath = g_strdup_printf("%s:%s", filepath, old_filepath); + //fprintf(stderr, "YY PREPEND %s\n", fpath); + g_hash_table_replace(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, fpath); + //filepath = g_strdup(npp->current.filepath ?: __UNNAMED); + //printf("ADD4 %s\n", filepath); + } + //g_hash_table_insert(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, g_strdup(npp->current.filepath ?: __UNNAMED)); // XXX: What are the side-effects here? + } +} + +//FIXME: no additional _global function +void +mark_data_as_dirty_global(NetplanParser* npp, const void* data_ptr, int value) +{ + //fprintf(stderr, "MARK_DIRTY %p %d\n", data_ptr, value); + if (!npp->current.netdef) { + // Handle global data on a per parser scope ("renderer" for now) + if (!npp->_private) + npp->_private = g_new0(struct private_netdef_data, 1); + if (!npp->_private->dirty_fields) + npp->_private->dirty_fields = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + char *old_filepath = (char*)g_hash_table_lookup(npp->_private->dirty_fields, (void*)data_ptr); + const char *filepath = npp->current.filepath ?: __UNNAMED; + //fprintf(stderr, "OLD FILEPATH %s\n", old_filepath); + if (!old_filepath) { + //fprintf(stderr, "XX INSERT %s\n", filepath); + g_hash_table_insert(npp->_private->dirty_fields, (void*)data_ptr, g_strdup_printf("%s/%d", filepath, value)); + } else if (g_strrstr(old_filepath, filepath) != old_filepath) { //FIXME: why the duplication? + char *fpath = g_strdup_printf("%s/%d:%s", filepath, value, old_filepath); + //fprintf(stderr, "XX PREPEND %s\n", fpath); + g_hash_table_replace(npp->_private->dirty_fields, (void*)data_ptr, fpath); + } + } else { + // Handle per netdef data + if (!npp->current.netdef->_private) + npp->current.netdef->_private = g_new0(struct private_netdef_data, 1); + if (!npp->current.netdef->_private->dirty_fields) + npp->current.netdef->_private->dirty_fields = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + + char *old_filepath = (char*)g_hash_table_lookup(npp->current.netdef->_private->dirty_fields, (void*)data_ptr); + const char *filepath = npp->current.filepath ?: __UNNAMED;; + //fprintf(stderr, "OLD FILEPATH %s\n", old_filepath); + if (!old_filepath) { + //fprintf(stderr, "YY INSERT %s\n", filepath); + g_hash_table_insert(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, g_strdup(filepath)); + //filepath = g_strdup_printf("%s:%s", old_filepath, __UNNAMED); + //printf("ADD3 %s\n", filepath); + //g_hash_table_replace(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, filepath); + } else if (g_strrstr(old_filepath, filepath) != old_filepath) { //FIXME: why the duplication? + char *fpath = g_strdup_printf("%s:%s", filepath, old_filepath); + //fprintf(stderr, "YY PREPEND %s\n", fpath); + g_hash_table_replace(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, fpath); + //filepath = g_strdup(npp->current.filepath ?: __UNNAMED); + //printf("ADD4 %s\n", filepath); + } + //g_hash_table_insert(npp->current.netdef->_private->dirty_fields, (void*)data_ptr, g_strdup(npp->current.filepath ?: __UNNAMED)); // XXX: What are the side-effects here? + } } gboolean