summary refs log tree commit diff
path: root/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c')
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c388
1 files changed, 388 insertions, 0 deletions
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
new file mode 100644
index 00000000..a8a9c40a
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -0,0 +1,388 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Tom Parker <palfrey@tevp.net>
+ * Copyright (C) 2004 Tom Parker
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifupdown-interface-parser.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <wordexp.h>
+#include <libgen.h>
+
+#include "nm-utils.h"
+
+/*****************************************************************************/
+
+static void
+_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "ifupdown"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static void
+add_block(if_parser *parser, const char *type, const char *name)
+{
+    if_block *ifb;
+    gsize     l_type, l_name;
+
+    l_type = strlen(type) + 1;
+    l_name = strlen(name) + 1;
+
+    ifb = g_malloc(sizeof(if_block) + l_type + l_name);
+    memcpy((char *) ifb->name, name, l_name);
+    ifb->type = &ifb->name[l_name];
+    memcpy((char *) ifb->type, type, l_type);
+    c_list_init(&ifb->data_lst_head);
+    c_list_link_tail(&parser->block_lst_head, &ifb->block_lst);
+}
+
+static void
+add_data(if_parser *parser, const char *key, const char *data)
+{
+    if_block *last_block;
+    if_data * ifd;
+    char *    idx;
+    gsize     l_key, l_data;
+
+    last_block = c_list_last_entry(&parser->block_lst_head, if_block, block_lst);
+
+    /* Check if there is a block where we can attach our data */
+    if (!last_block)
+        return;
+
+    l_key  = strlen(key) + 1;
+    l_data = strlen(data) + 1;
+
+    ifd = g_malloc(sizeof(if_data) + l_key + l_data);
+    memcpy((char *) ifd->key, key, l_key);
+    ifd->data = &ifd->key[l_key];
+    memcpy((char *) ifd->data, data, l_data);
+
+    /* Normalize keys. Convert '_' to '-', as ifupdown accepts both variants.
+     * When querying keys via ifparser_getkey(), use '-'. */
+    idx = (char *) ifd->key;
+    while ((idx = strchr(idx, '_')))
+        *(idx++) = '-';
+
+    c_list_link_tail(&last_block->data_lst_head, &ifd->data_lst);
+}
+
+/* join values in src with spaces into dst;  dst needs to be large enough */
+static char *
+join_values_with_spaces(char *dst, char **src)
+{
+    if (dst != NULL) {
+        *dst = '\0';
+        if (src != NULL && *src != NULL) {
+            strcat(dst, *src);
+
+            for (src++; *src != NULL; src++) {
+                strcat(dst, " ");
+                strcat(dst, *src);
+            }
+        }
+    }
+    return (dst);
+}
+
+static void
+_recursive_ifparser(if_parser *parser, const char *eni_file, int quiet)
+{
+    FILE *inp;
+    char  line[255];
+    int   skip_to_block  = 1;
+    int   skip_long_line = 0;
+    int   offs           = 0;
+
+    /* Check if interfaces file exists and open it */
+    if (!g_file_test(eni_file, G_FILE_TEST_EXISTS)) {
+        if (!quiet)
+            _LOGW("interfaces file %s doesn't exist", eni_file);
+        return;
+    }
+    inp = fopen(eni_file, "re");
+    if (inp == NULL) {
+        if (!quiet)
+            _LOGW("Can't open %s", eni_file);
+        return;
+    }
+    if (!quiet)
+        _LOGI("      interface-parser: parsing file %s", eni_file);
+
+    while (!feof(inp)) {
+        char *token[128]; /* 255 chars can only be split into 127 tokens */
+        char  value[255]; /* large enough to join previously split tokens */
+        char *safeptr;
+        int   toknum;
+        int   len = 0;
+
+        char *ptr = fgets(line + offs, 255 - offs, inp);
+        if (ptr == NULL)
+            break;
+
+        len = strlen(line);
+        /* skip over-long lines */
+        if (!feof(inp) && len > 0 && line[len - 1] != '\n') {
+            if (!skip_long_line) {
+                if (!quiet)
+                    _LOGW("Skipping over-long-line '%s...'", line);
+            }
+            skip_long_line = 1;
+            continue;
+        }
+
+        /* trailing '\n' found: remove it & reset offset to 0 */
+        if (len > 0 && line[len - 1] == '\n') {
+            line[--len] = '\0';
+            offs        = 0;
+        }
+
+        /* if we're in long_line_skip mode, terminate it for real next line */
+        if (skip_long_line) {
+            if (len == 0 || line[len - 1] != '\\')
+                skip_long_line = 0;
+            continue;
+        }
+
+        /* unwrap wrapped lines */
+        if (len > 0 && line[len - 1] == '\\') {
+            offs = len - 1;
+            continue;
+        }
+
+#define SPACES " \t"
+        /* tokenize input; */
+        for (toknum = 0, token[toknum] = strtok_r(line, SPACES, &safeptr); token[toknum] != NULL;
+             toknum++, token[toknum]   = strtok_r(NULL, SPACES, &safeptr))
+            ;
+
+        /* ignore comments and empty lines */
+        if (toknum == 0 || *token[0] == '#')
+            continue;
+
+        if (toknum < 2) {
+            if (!quiet) {
+                _LOGW("Can't parse interface line '%s'", join_values_with_spaces(value, token));
+            }
+            skip_to_block = 1;
+            continue;
+        }
+
+        /* There are six different stanzas:
+         * iface, mapping, auto, allow-*, source, and source-directory.
+         * Create a block for each of them except source and source-directory.  */
+
+        /* iface stanza takes at least 3 parameters */
+        if (nm_streq(token[0], "iface")) {
+            if (toknum < 4) {
+                if (!quiet) {
+                    _LOGW("Can't parse iface line '%s'", join_values_with_spaces(value, token));
+                }
+                continue;
+            }
+            add_block(parser, token[0], token[1]);
+            skip_to_block = 0;
+            add_data(parser, token[2], join_values_with_spaces(value, token + 3));
+        }
+        /* auto and allow-auto stanzas are equivalent,
+         * both can take multiple interfaces as parameters: add one block for each */
+        else if (NM_IN_STRSET(token[0], "auto", "allow-auto")) {
+            int i;
+
+            for (i = 1; i < toknum; i++)
+                add_block(parser, "auto", token[i]);
+            skip_to_block = 0;
+        } else if (nm_streq(token[0], "mapping")) {
+            add_block(parser, token[0], join_values_with_spaces(value, token + 1));
+            skip_to_block = 0;
+        }
+        /* allow-* can take multiple interfaces as parameters: add one block for each */
+        else if (g_str_has_prefix(token[0], "allow-")) {
+            int i;
+            for (i = 1; i < toknum; i++)
+                add_block(parser, token[0], token[i]);
+            skip_to_block = 0;
+        }
+        /* source and source-directory stanzas take one or more paths as parameters */
+        else if (NM_IN_STRSET(token[0], "source", "source-directory")) {
+            int   i;
+            char *en_dir;
+
+            skip_to_block = 0;
+            en_dir        = g_path_get_dirname(eni_file);
+            for (i = 1; i < toknum; ++i) {
+                if (nm_streq(token[0], "source-directory"))
+                    _ifparser_source(parser, token[i], en_dir, quiet, TRUE);
+                else
+                    _ifparser_source(parser, token[i], en_dir, quiet, FALSE);
+            }
+            g_free(en_dir);
+        } else {
+            if (skip_to_block) {
+                if (!quiet) {
+                    _LOGW("ignoring out-of-block data '%s'", join_values_with_spaces(value, token));
+                }
+            } else
+                add_data(parser, token[0], join_values_with_spaces(value, token + 1));
+        }
+    }
+    fclose(inp);
+
+    if (!quiet)
+        _LOGI("      interface-parser: finished parsing file %s", eni_file);
+}
+
+static void
+_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir)
+{
+    char *      abs_path;
+    const char *item;
+    wordexp_t   we;
+    GDir *      source_dir;
+    GError *    error = NULL;
+    uint        i;
+
+    if (g_path_is_absolute(path))
+        abs_path = g_strdup(path);
+    else
+        abs_path = g_build_filename(en_dir, path, NULL);
+
+    if (!quiet)
+        _LOGI("      interface-parser: source line includes interfaces file(s) %s", abs_path);
+
+    /* ifupdown uses WRDE_NOCMD for wordexp. */
+    if (wordexp(abs_path, &we, WRDE_NOCMD)) {
+        if (!quiet)
+            _LOGW("word expansion for %s failed", abs_path);
+    } else {
+        for (i = 0; i < we.we_wordc; i++) {
+            if (dir) {
+                source_dir = g_dir_open(we.we_wordv[i], 0, &error);
+                if (!source_dir) {
+                    if (!quiet) {
+                        _LOGW("Failed to open directory %s: %s", we.we_wordv[i], error->message);
+                    }
+                    g_clear_error(&error);
+                } else {
+                    while ((item = g_dir_read_name(source_dir)))
+                        _ifparser_source(parser, item, we.we_wordv[i], quiet, FALSE);
+                    g_dir_close(source_dir);
+                }
+            } else
+                _recursive_ifparser(parser, we.we_wordv[i], quiet);
+        }
+        wordfree(&we);
+    }
+    g_free(abs_path);
+}
+
+if_parser *
+ifparser_parse(const char *eni_file, int quiet)
+{
+    if_parser *parser;
+
+    parser = g_slice_new(if_parser);
+    c_list_init(&parser->block_lst_head);
+    _recursive_ifparser(parser, eni_file, quiet);
+    return parser;
+}
+
+static void
+_destroy_data(if_data *ifd)
+{
+    c_list_unlink_stale(&ifd->data_lst);
+    g_free(ifd);
+}
+
+static void
+_destroy_block(if_block *ifb)
+{
+    if_data *ifd;
+
+    while ((ifd = c_list_first_entry(&ifb->data_lst_head, if_data, data_lst)))
+        _destroy_data(ifd);
+    c_list_unlink_stale(&ifb->block_lst);
+    g_free(ifb);
+}
+
+void
+ifparser_destroy(if_parser *parser)
+{
+    if_block *ifb;
+
+    while ((ifb = c_list_first_entry(&parser->block_lst_head, if_block, block_lst)))
+        _destroy_block(ifb);
+    g_slice_free(if_parser, parser);
+}
+
+if_block *
+ifparser_getfirst(if_parser *parser)
+{
+    return c_list_first_entry(&parser->block_lst_head, if_block, block_lst);
+}
+
+guint
+ifparser_get_num_blocks(if_parser *parser)
+{
+    return c_list_length(&parser->block_lst_head);
+}
+
+if_block *
+ifparser_getif(if_parser *parser, const char *iface)
+{
+    if_block *ifb;
+
+    c_list_for_each_entry (ifb, &parser->block_lst_head, block_lst) {
+        if (nm_streq(ifb->type, "iface") && nm_streq(ifb->name, iface))
+            return ifb;
+    }
+    return NULL;
+}
+
+static if_data *
+ifparser_findkey(if_block *iface, const char *key)
+{
+    if_data *ifd;
+
+    c_list_for_each_entry (ifd, &iface->data_lst_head, data_lst) {
+        if (nm_streq(ifd->key, key))
+            return ifd;
+    }
+    return NULL;
+}
+
+const char *
+ifparser_getkey(if_block *iface, const char *key)
+{
+    if_data *ifd;
+
+    ifd = ifparser_findkey(iface, key);
+    return ifd ? ifd->data : NULL;
+}
+
+gboolean
+ifparser_haskey(if_block *iface, const char *key)
+{
+    return !!ifparser_findkey(iface, key);
+}
+
+guint
+ifparser_get_num_info(if_block *iface)
+{
+    return c_list_length(&iface->data_lst_head);
+}