/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Tom Parker <palfrey@tevp.net>
* Copyright (C) 2004 Tom Parker
*/
#include "nm-default.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);
}