diff options
| author | Michael Biebl <biebl@debian.org> | 2020-10-05 22:27:18 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-10-05 22:27:18 +0200 |
| commit | aafc1dbe4712c86189bbc1d4d54ad8cb4c69be7e (patch) | |
| tree | a2a9bb4d007339a0b1304540388230ccedac32bd /src/settings/plugins/ifcfg-rh/shvar.c | |
| parent | e7b44ef4c80907346ec7492a09c45277459924fc (diff) | |
New upstream version 1.27.90 upstream/1.27.90
Diffstat (limited to 'src/settings/plugins/ifcfg-rh/shvar.c')
| -rw-r--r-- | src/settings/plugins/ifcfg-rh/shvar.c | 2409 |
1 files changed, 1220 insertions, 1189 deletions
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index cd63661d..8f5be2cc 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 1999, 2000 Red Hat, Inc. */ @@ -24,69 +24,68 @@ /*****************************************************************************/ struct _shvarLine { - - const char *key; - - CList lst; - - /* We index variables by their key in shvarFile.lst_idx. One shell variable might - * occur multiple times in a file (in which case the last occurrence wins). - * Hence, we need to keep a list of all the same keys. - * - * This is a pointer to the next shadowed line. */ - struct _shvarLine *prev_shadowed; - - /* There are three cases: - * - * 1) the line is not a valid variable assignment (that is, it doesn't - * start with a "FOO=" with possible whitespace prefix). - * In that case, @key and @key_with_prefix are %NULL, and the entire - * original line is in @line. Such entries are ignored for the most part. - * - * 2) if the line can be parsed with a "FOO=" assignment, then @line contains - * the part after '=', @key_with_prefix contains the key "FOO" with possible - * whitespace prefix, and @key points into @key_with_prefix skipping over the - * whitespace. - * - * 3) like 2, but if the value was deleted via svSetValue(), the entry is not removed, - * but only marked for deletion. That is done by clearing @line but preserving - * @key/@key_with_prefix. - * */ - char *line; - char *key_with_prefix; - - /* svSetValue() will clear the dirty flag. */ - bool dirty:1; + const char *key; + + CList lst; + + /* We index variables by their key in shvarFile.lst_idx. One shell variable might + * occur multiple times in a file (in which case the last occurrence wins). + * Hence, we need to keep a list of all the same keys. + * + * This is a pointer to the next shadowed line. */ + struct _shvarLine *prev_shadowed; + + /* There are three cases: + * + * 1) the line is not a valid variable assignment (that is, it doesn't + * start with a "FOO=" with possible whitespace prefix). + * In that case, @key and @key_with_prefix are %NULL, and the entire + * original line is in @line. Such entries are ignored for the most part. + * + * 2) if the line can be parsed with a "FOO=" assignment, then @line contains + * the part after '=', @key_with_prefix contains the key "FOO" with possible + * whitespace prefix, and @key points into @key_with_prefix skipping over the + * whitespace. + * + * 3) like 2, but if the value was deleted via svSetValue(), the entry is not removed, + * but only marked for deletion. That is done by clearing @line but preserving + * @key/@key_with_prefix. + * */ + char *line; + char *key_with_prefix; + + /* svSetValue() will clear the dirty flag. */ + bool dirty : 1; }; typedef struct _shvarLine shvarLine; struct _shvarFile { - char *fileName; - CList lst_head; - GHashTable *lst_idx; - int fd; - bool modified:1; + char * fileName; + CList lst_head; + GHashTable *lst_idx; + int fd; + bool modified : 1; }; /*****************************************************************************/ -static void _line_link_parse (shvarFile *s, const char *value, gsize len); +static void _line_link_parse(shvarFile *s, const char *value, gsize len); /*****************************************************************************/ -#define ASSERT_key_is_well_known(key) \ - nm_assert ( ({ \ - const char *_key = (key); \ - gboolean _is_wellknown = TRUE; \ - \ - if (!nms_ifcfg_rh_utils_is_well_known_key (_key)) { \ - _is_wellknown = FALSE; \ - g_critical ("ifcfg-rh key \"%s\" is not well-known", _key); \ - } \ - \ - _is_wellknown; \ - }) ) +#define ASSERT_key_is_well_known(key) \ + nm_assert(({ \ + const char *_key = (key); \ + gboolean _is_wellknown = TRUE; \ + \ + if (!nms_ifcfg_rh_utils_is_well_known_key(_key)) { \ + _is_wellknown = FALSE; \ + g_critical("ifcfg-rh key \"%s\" is not well-known", _key); \ + } \ + \ + _is_wellknown; \ + })) /** * svParseBoolean: @@ -100,52 +99,46 @@ static void _line_link_parse (shvarFile *s, const char *value, gsize len); * Returns: the parsed boolean value or @fallback. */ int -svParseBoolean (const char *value, int fallback) +svParseBoolean(const char *value, int fallback) { - if (!value) - return fallback; - - if ( !g_ascii_strcasecmp ("yes", value) - || !g_ascii_strcasecmp ("true", value) - || !g_ascii_strcasecmp ("t", value) - || !g_ascii_strcasecmp ("y", value) - || !g_ascii_strcasecmp ("1", value)) - return TRUE; - else if ( !g_ascii_strcasecmp ("no", value) - || !g_ascii_strcasecmp ("false", value) - || !g_ascii_strcasecmp ("f", value) - || !g_ascii_strcasecmp ("n", value) - || !g_ascii_strcasecmp ("0", value)) - return FALSE; - - return fallback; + if (!value) + return fallback; + + if (!g_ascii_strcasecmp("yes", value) || !g_ascii_strcasecmp("true", value) + || !g_ascii_strcasecmp("t", value) || !g_ascii_strcasecmp("y", value) + || !g_ascii_strcasecmp("1", value)) + return TRUE; + else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value) + || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value) + || !g_ascii_strcasecmp("0", value)) + return FALSE; + + return fallback; } /*****************************************************************************/ static gboolean -_shell_is_name (const char *key, gssize len) +_shell_is_name(const char *key, gssize len) { - gssize i; - - /* whether @key is a valid identifier (name). */ - if (!key || len == 0) - return FALSE; - if ( !g_ascii_isalpha (key[0]) - && key[0] != '_') - return FALSE; - for (i = 1; TRUE; i++) { - if (len < 0) { - if (!key[i]) - return TRUE; - } else { - if (i >= len) - return TRUE; - } - if ( !g_ascii_isalnum (key[i]) - && key[i] != '_') - return FALSE; - } + gssize i; + + /* whether @key is a valid identifier (name). */ + if (!key || len == 0) + return FALSE; + if (!g_ascii_isalpha(key[0]) && key[0] != '_') + return FALSE; + for (i = 1; TRUE; i++) { + if (len < 0) { + if (!key[i]) + return TRUE; + } else { + if (i >= len) + return TRUE; + } + if (!g_ascii_isalnum(key[i]) && key[i] != '_') + return FALSE; + } } /*****************************************************************************/ @@ -154,959 +147,964 @@ _shell_is_name (const char *key, gssize len) * * While at it, add $''. */ static char * -_escape_ansic (const char *source) +_escape_ansic(const char *source) { - const char *p; - char *dest; - char *q; - - nm_assert (source); - - p = (const char *) source; - /* Each source byte needs maximally four destination chars (\777) */ - q = dest = g_malloc (strlen (source) * 4 + 1 + 3); - - *q++ = '$'; - *q++ = '\''; - - while (*p) { - switch (*p) { - case '\b': - *q++ = '\\'; - *q++ = 'b'; - break; - case '\f': - *q++ = '\\'; - *q++ = 'f'; - break; - case '\n': - *q++ = '\\'; - *q++ = 'n'; - break; - case '\r': - *q++ = '\\'; - *q++ = 'r'; - break; - case '\t': - *q++ = '\\'; - *q++ = 't'; - break; - case '\v': - *q++ = '\\'; - *q++ = 'v'; - break; - case '\\': - case '"': - case '\'': - *q++ = '\\'; - *q++ = *p; - break; - default: - if ((*p < ' ') || (*p >= 0177)) { - *q++ = '\\'; - *q++ = '0' + (((*p) >> 6) & 07); - *q++ = '0' + (((*p) >> 3) & 07); - *q++ = '0' + ((*p) & 07); - } else - *q++ = *p; - break; - } - p++; - } - *q++ = '\''; - *q++ = '\0'; - - nm_assert (q - dest <= strlen (source) * 4 + 1 + 3); - - return dest; + const char *p; + char * dest; + char * q; + + nm_assert(source); + + p = (const char *) source; + /* Each source byte needs maximally four destination chars (\777) */ + q = dest = g_malloc(strlen(source) * 4 + 1 + 3); + + *q++ = '$'; + *q++ = '\''; + + while (*p) { + switch (*p) { + case '\b': + *q++ = '\\'; + *q++ = 'b'; + break; + case '\f': + *q++ = '\\'; + *q++ = 'f'; + break; + case '\n': + *q++ = '\\'; + *q++ = 'n'; + break; + case '\r': + *q++ = '\\'; + *q++ = 'r'; + break; + case '\t': + *q++ = '\\'; + *q++ = 't'; + break; + case '\v': + *q++ = '\\'; + *q++ = 'v'; + break; + case '\\': + case '"': + case '\'': + *q++ = '\\'; + *q++ = *p; + break; + default: + if ((*p < ' ') || (*p >= 0177)) { + *q++ = '\\'; + *q++ = '0' + (((*p) >> 6) & 07); + *q++ = '0' + (((*p) >> 3) & 07); + *q++ = '0' + ((*p) & 07); + } else + *q++ = *p; + break; + } + p++; + } + *q++ = '\''; + *q++ = '\0'; + + nm_assert(q - dest <= strlen(source) * 4 + 1 + 3); + + return dest; } /*****************************************************************************/ -#define _char_req_escape(ch) NM_IN_SET (ch, '"', '\\', '$', '`') -#define _char_req_escape_old(ch) NM_IN_SET (ch, '"', '\\', '\'', '$', '`', '~') -#define _char_req_quotes(ch) NM_IN_SET (ch, ' ', '\'', '~', '\t', '|', '&', ';', '(', ')', '<', '>') +#define _char_req_escape(ch) NM_IN_SET(ch, '"', '\\', '$', '`') +#define _char_req_escape_old(ch) NM_IN_SET(ch, '"', '\\', '\'', '$', '`', '~') +#define _char_req_quotes(ch) NM_IN_SET(ch, ' ', '\'', '~', '\t', '|', '&', ';', '(', ')', '<', '>') const char * -svEscape (const char *s, char **to_free) +svEscape(const char *s, char **to_free) { - char *new; - gsize mangle = 0; - gboolean requires_quotes = FALSE; - int newlen; - size_t i, j, slen; - - for (slen = 0; s[slen]; slen++) { - if (_char_req_escape (s[slen])) - mangle++; - else if (_char_req_quotes (s[slen])) - requires_quotes = TRUE; - else if (s[slen] < ' ') { - /* if the string contains newline we can only express it using ANSI C quotation - * (as we don't support line continuation). - * Additionally, ANSI control characters look odd with regular quotation, so handle - * them too. */ - return (*to_free = _escape_ansic (s)); - } - } - if (!mangle && !requires_quotes) { - *to_free = NULL; - return s; - } - - newlen = slen + mangle + 3; /* 3 is extra ""\0 */ - new = g_malloc (newlen); - - j = 0; - new[j++] = '"'; - for (i = 0; i < slen; i++) { - if (_char_req_escape (s[i])) - new[j++] = '\\'; - new[j++] = s[i]; - } - new[j++] = '"'; - new[j++] = '\0'; - - nm_assert (j == slen + mangle + 3); - - *to_free = new; - return new; + char *new; + gsize mangle = 0; + gboolean requires_quotes = FALSE; + int newlen; + size_t i, j, slen; + + for (slen = 0; s[slen]; slen++) { + if (_char_req_escape(s[slen])) + mangle++; + else if (_char_req_quotes(s[slen])) + requires_quotes = TRUE; + else if (s[slen] < ' ') { + /* if the string contains newline we can only express it using ANSI C quotation + * (as we don't support line continuation). + * Additionally, ANSI control characters look odd with regular quotation, so handle + * them too. */ + return (*to_free = _escape_ansic(s)); + } + } + if (!mangle && !requires_quotes) { + *to_free = NULL; + return s; + } + + newlen = slen + mangle + 3; /* 3 is extra ""\0 */ + new = g_malloc(newlen); + + j = 0; + new[j++] = '"'; + for (i = 0; i < slen; i++) { + if (_char_req_escape(s[i])) + new[j++] = '\\'; + new[j++] = s[i]; + } + new[j++] = '"'; + new[j++] = '\0'; + + nm_assert(j == slen + mangle + 3); + + *to_free = new; + return new; } static gboolean -_looks_like_old_svescaped (const char *value) +_looks_like_old_svescaped(const char *value) { - gsize k; - - if (value[0] != '"') - return FALSE; - - for (k = 1; ; k++) { - if (value[k] == '\0') - return FALSE; - if (!_char_req_escape_old (value[k])) - continue; - - if (value[k] == '"') - return (value[k + 1] == '\0'); - else if (value[k] == '\\') { - k++; - if (!_char_req_escape_old (value[k])) - return FALSE; - } else - return FALSE; - } + gsize k; + + if (value[0] != '"') + return FALSE; + + for (k = 1;; k++) { + if (value[k] == '\0') + return FALSE; + if (!_char_req_escape_old(value[k])) + continue; + + if (value[k] == '"') + return (value[k + 1] == '\0'); + else if (value[k] == '\\') { + k++; + if (!_char_req_escape_old(value[k])) + return FALSE; + } else + return FALSE; + } } static gboolean -_ch_octal_is (char ch) +_ch_octal_is(char ch) { - return ch >= '0' && ch < '8'; + return ch >= '0' && ch < '8'; } static guint8 -_ch_octal_get (char ch) +_ch_octal_get(char ch) { - nm_assert (_ch_octal_is (ch)); - return (ch - '0'); + nm_assert(_ch_octal_is(ch)); + return (ch - '0'); } static gboolean -_ch_hex_is (char ch) +_ch_hex_is(char ch) { - return g_ascii_isxdigit (ch); + return g_ascii_isxdigit(ch); } static guint8 -_ch_hex_get (char ch) +_ch_hex_get(char ch) { - nm_assert (_ch_hex_is (ch)); - return ch <= '9' ? ch - '0' : (ch & 0x4F) - 'A' + 10; + nm_assert(_ch_hex_is(ch)); + return ch <= '9' ? ch - '0' : (ch & 0x4F) - 'A' + 10; } static void -_gstr_init (GString **str, const char *value, gsize i) +_gstr_init(GString **str, const char *value, gsize i) { - nm_assert (str); - nm_assert (value); - - if (!(*str)) { - /* if @str is not yet initialized, it allocates - * a new GString and copies @i characters from - * @value over. - * - * Unescaping usually does not extend the length of a string, - * so we might be tempted to allocate a fixed buffer of length - * (strlen(value)+CONST). - * However, due to $'\Ux' escapes, the maximum length is some - * (FACTOR*strlen(value) + CONST), which is non trivial to get - * right in all cases. Also, we would have to provision for the - * very unlikely extreme case. - * Instead, use a GString buffer which can grow as needed. But for an - * initial guess, strlen(value) is a good start */ - *str = g_string_new_len (NULL, strlen (value) + 3); - if (i) - g_string_append_len (*str, value, i); - } + nm_assert(str); + nm_assert(value); + + if (!(*str)) { + /* if @str is not yet initialized, it allocates + * a new GString and copies @i characters from + * @value over. + * + * Unescaping usually does not extend the length of a string, + * so we might be tempted to allocate a fixed buffer of length + * (strlen(value)+CONST). + * However, due to $'\Ux' escapes, the maximum length is some + * (FACTOR*strlen(value) + CONST), which is non trivial to get + * right in all cases. Also, we would have to provision for the + * very unlikely extreme case. + * Instead, use a GString buffer which can grow as needed. But for an + * initial guess, strlen(value) is a good start */ + *str = g_string_new_len(NULL, strlen(value) + 3); + if (i) + g_string_append_len(*str, value, i); + } } const char * -svUnescape (const char *value, char **to_free) +svUnescape(const char *value, char **to_free) { - gsize i, j; - GString *str = NULL; - int looks_like_old_svescaped = -1; - - /* we handle bash syntax here (note that ifup has #!/bin/bash. - * Thus, see https://www.gnu.org/software/bash/manual/html_node/Quoting.html#Quoting */ - - /* @value shall start with the first character after "FOO=" */ - - nm_assert (value); - nm_assert (to_free); - - /* we don't expect any newlines. They must be filtered out before-hand. - * We also don't support line continuation. */ - nm_assert (!NM_STRCHAR_ANY (value, ch, ch == '\n')); - - i = 0; - while (TRUE) { - - if (value[i] == '\0') - goto out_value; - - if ( g_ascii_isspace (value[i]) - || value[i] == ';') { - gboolean has_semicolon = (value[i] == ';'); - - /* starting with space is only allowed, if the entire - * string consists of spaces (possibly terminated by a comment). - * This disallows for example - * LANG=C ls -1 - * LANG= ls -1 - * but allows - * LANG= #comment - * - * As a special case, we also allow one trailing semicolon, as long - * it is only followed by whitespace or a #-comment. - * FOO=; - * FOO=a; - * FOO=b ; #hallo - */ - j = i + 1; - while ( g_ascii_isspace (value[j]) - || ( !has_semicolon - && (has_semicolon = (value[j] == ';')))) - j++; - if (!NM_IN_SET (value[j], '\0', '#')) - goto out_error; - goto out_value; - } - - if (value[i] == '\\') { - /* backslash escape */ - _gstr_init (&str, value, i); - i++; - if (G_UNLIKELY (value[i] == '\0')) { - /* we don't support line continuation */ - goto out_error; - } - g_string_append_c (str, value[i]); - i++; - goto loop1_next; - } - - if (value[i] == '\'') { - /* single quotes */ - _gstr_init (&str, value, i); - i++; - j = i; - while (TRUE) { - if (value[j] == '\0') { - /* unterminated single quote. We don't support line continuation */ - goto out_error; - } - if (value[j] == '\'') - break; - j++; - } - g_string_append_len (str, &value[i], j - i); - i = j + 1; - goto loop1_next; - } - - if (value[i] == '"') { - /* double quotes */ - _gstr_init (&str, value, i); - i++; - while (TRUE) { - if (value[i] == '"') { - i++; - break; - } - if (value[i] == '\0') { - /* unterminated double quote. We don't support line continuation. */ - goto out_error; - } - if (NM_IN_SET (value[i], '`', '$')) { - /* we don't support shell expansion. */ - goto out_error; - } - if (value[i] == '\\') { - i++; - if (value[i] == '\0') { - /* we don't support line continuation */ - goto out_error; - } - if (NM_IN_SET (value[i], '$', '`', '"', '\\')) { - /* Drop the backslash. */ - } else if (NM_IN_SET (value[i], '\'', '~')) { - /* '\'' and '~' in double quotes are not handled special by shell. - * However, old versions of svEscape() would wrongly use double-quoting - * with backslash escaping for these characters (expecting svUnescape() - * to remove the backslash). - * - * In order to preserve previous behavior, we continue to read such - * strings different then shell does. */ - - /* Actually, we can relax this. Old svEscape() escaped the entire value - * in a particular way with double quotes. - * If the value doesn't exactly look like something as created by svEscape(), - * don't do the compat hack and preserve the backslash. */ - if (looks_like_old_svescaped < 0) - looks_like_old_svescaped = _looks_like_old_svescaped (value); - if (!looks_like_old_svescaped) - g_string_append_c (str, '\\'); - } else - g_string_append_c (str, '\\'); - } - g_string_append_c (str, value[i]); - i++; - } - goto loop1_next; - } - - if ( value[i] == '$' - && value[i + 1] == '\'') { - /* ANSI-C Quoting */ - _gstr_init (&str, value, i); - i += 2; - while (TRUE) { - char ch; - - if (value[i] == '\'') { - i++; - break; - } - if (value[i] == '\0') { - /* unterminated double quote. We don't support line continuation. */ - goto out_error; - } - if (value[i] == '\\') { - - i++; - if (value[i] == '\0') { - /* we don't support line continuation */ - goto out_error; - } - switch (value[i]) { - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'e': ch = '\e'; break; - case 'E': ch = '\E'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - case '?': ch = '\?'; break; - case '"': ch = '"'; break; - case '\\': ch = '\\'; break; - case '\'': ch = '\''; break; - default: - if (_ch_octal_is (value[i])) { - guint v; - - v = _ch_octal_get (value[i]); - i++; - if (_ch_octal_is (value[i])) { - v = (v * 8) + _ch_octal_get (value[i]); - i++; - if (_ch_octal_is (value[i])) { - v = (v * 8) + _ch_octal_get (value[i]); - i++; - } - } - /* like bash, we cut too large numbers off. E.g. A=$'\772' becomes 0xfa */ - g_string_append_c (str, (guint8) v); - } else if (NM_IN_SET (value[i], 'x', 'u', 'U')) { - const char escape_type = value[i]; - int max_digits = escape_type == 'x' ? 2 : escape_type == 'u' ? 4 : 8; - guint64 v; - - i++; - if (!_ch_hex_is (value[i])) { - /* missing hex value after "\x" escape. This is treated like no escaping. */ - g_string_append_c (str, '\\'); - g_string_append_c (str, escape_type); - } else { - v = _ch_hex_get (value[i]); - i++; - - while (--max_digits > 0) { - if (!_ch_hex_is (value[i])) - break; - v = v * 16 + _ch_hex_get (value[i]); - i++; - } - if (escape_type == 'x') - g_string_append_c (str, v); - else { - /* we treat the unicode escapes as utf-8 encoded values. */ - g_string_append_unichar (str, v); - } - } - } else { - g_string_append_c (str, '\\'); - g_string_append_c (str, value[i]); - i++; - } - goto loop_ansic_next; - } - } else - ch = value[i]; - g_string_append_c (str, ch); - i++; -loop_ansic_next: ; - } - goto loop1_next; - } - - if (NM_IN_SET (value[i], '|', '&', '(', ')', '<', '>')) { - /* shell metacharacters are not supported without quoting. - * Note that ';' is already handled above. */ - goto out_error; - } - - /* an unquoted, regular character. Just consume it directly. */ - if (str) - g_string_append_c (str, value[i]); - i++; - -loop1_next: ; - } - - nm_assert_not_reached (); + gsize i, j; + GString *str = NULL; + int looks_like_old_svescaped = -1; + + /* we handle bash syntax here (note that ifup has #!/bin/bash. + * Thus, see https://www.gnu.org/software/bash/manual/html_node/Quoting.html#Quoting */ + + /* @value shall start with the first character after "FOO=" */ + + nm_assert(value); + nm_assert(to_free); + + /* we don't expect any newlines. They must be filtered out before-hand. + * We also don't support line continuation. */ + nm_assert(!NM_STRCHAR_ANY(value, ch, ch == '\n')); + + i = 0; + while (TRUE) { + if (value[i] == '\0') + goto out_value; + + if (g_ascii_isspace(value[i]) || value[i] == ';') { + gboolean has_semicolon = (value[i] == ';'); + + /* starting with space is only allowed, if the entire + * string consists of spaces (possibly terminated by a comment). + * This disallows for example + * LANG=C ls -1 + * LANG= ls -1 + * but allows + * LANG= #comment + * + * As a special case, we also allow one trailing semicolon, as long + * it is only followed by whitespace or a #-comment. + * FOO=; + * FOO=a; + * FOO=b ; #hallo + */ + j = i + 1; + while (g_ascii_isspace(value[j]) + || (!has_semicolon && (has_semicolon = (value[j] == ';')))) + j++; + if (!NM_IN_SET(value[j], '\0', '#')) + goto out_error; + goto out_value; + } + + if (value[i] == '\\') { + /* backslash escape */ + _gstr_init(&str, value, i); + i++; + if (G_UNLIKELY(value[i] == '\0')) { + /* we don't support line continuation */ + goto out_error; + } + g_string_append_c(str, value[i]); + i++; + goto loop1_next; + } + + if (value[i] == '\'') { + /* single quotes */ + _gstr_init(&str, value, i); + i++; + j = i; + while (TRUE) { + if (value[j] == '\0') { + /* unterminated single quote. We don't support line continuation */ + goto out_error; + } + if (value[j] == '\'') + break; + j++; + } + g_string_append_len(str, &value[i], j - i); + i = j + 1; + goto loop1_next; + } + + if (value[i] == '"') { + /* double quotes */ + _gstr_init(&str, value, i); + i++; + while (TRUE) { + if (value[i] == '"') { + i++; + break; + } + if (value[i] == '\0') { + /* unterminated double quote. We don't support line continuation. */ + goto out_error; + } + if (NM_IN_SET(value[i], '`', '$')) { + /* we don't support shell expansion. */ + goto out_error; + } + if (value[i] == '\\') { + i++; + if (value[i] == '\0') { + /* we don't support line continuation */ + goto out_error; + } + if (NM_IN_SET(value[i], '$', '`', '"', '\\')) { + /* Drop the backslash. */ + } else if (NM_IN_SET(value[i], '\'', '~')) { + /* '\'' and '~' in double quotes are not handled special by shell. + * However, old versions of svEscape() would wrongly use double-quoting + * with backslash escaping for these characters (expecting svUnescape() + * to remove the backslash). + * + * In order to preserve previous behavior, we continue to read such + * strings different then shell does. */ + + /* Actually, we can relax this. Old svEscape() escaped the entire value + * in a particular way with double quotes. + * If the value doesn't exactly look like something as created by svEscape(), + * don't do the compat hack and preserve the backslash. */ + if (looks_like_old_svescaped < 0) + looks_like_old_svescaped = _looks_like_old_svescaped(value); + if (!looks_like_old_svescaped) + g_string_append_c(str, '\\'); + } else + g_string_append_c(str, '\\'); + } + g_string_append_c(str, value[i]); + i++; + } + goto loop1_next; + } + + if (value[i] == '$' && value[i + 1] == '\'') { + /* ANSI-C Quoting */ + _gstr_init(&str, value, i); + i += 2; + while (TRUE) { + char ch; + + if (value[i] == '\'') { + i++; + break; + } + if (value[i] == '\0') { + /* unterminated double quote. We don't support line continuation. */ + goto out_error; + } + if (value[i] == '\\') { + i++; + if (value[i] == '\0') { + /* we don't support line continuation */ + goto out_error; + } + switch (value[i]) { + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'e': + ch = '\e'; + break; + case 'E': + ch = '\E'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + case '?': + ch = '\?'; + break; + case '"': + ch = '"'; + break; + case '\\': + ch = '\\'; + break; + case '\'': + ch = '\''; + break; + default: + if (_ch_octal_is(value[i])) { + guint v; + + v = _ch_octal_get(value[i]); + i++; + if (_ch_octal_is(value[i])) { + v = (v * 8) + _ch_octal_get(value[i]); + i++; + if (_ch_octal_is(value[i])) { + v = (v * 8) + _ch_octal_get(value[i]); + i++; + } + } + /* like bash, we cut too large numbers off. E.g. A=$'\772' becomes 0xfa */ + g_string_append_c(str, (guint8) v); + } else if (NM_IN_SET(value[i], 'x', 'u', 'U')) { + const char escape_type = value[i]; + int max_digits = escape_type == 'x' ? 2 : escape_type == 'u' ? 4 : 8; + guint64 v; + + i++; + if (!_ch_hex_is(value[i])) { + /* missing hex value after "\x" escape. This is treated like no escaping. */ + g_string_append_c(str, '\\'); + g_string_append_c(str, escape_type); + } else { + v = _ch_hex_get(value[i]); + i++; + + while (--max_digits > 0) { + if (!_ch_hex_is(value[i])) + break; + v = v * 16 + _ch_hex_get(value[i]); + i++; + } + if (escape_type == 'x') + g_string_append_c(str, v); + else { + /* we treat the unicode escapes as utf-8 encoded values. */ + g_string_append_unichar(str, v); + } + } + } else { + g_string_append_c(str, '\\'); + g_string_append_c(str, value[i]); + i++; + } + goto loop_ansic_next; + } + } else + ch = value[i]; + g_string_append_c(str, ch); + i++; +loop_ansic_next:; + } + goto loop1_next; + } + + if (NM_IN_SET(value[i], '|', '&', '(', ')', '<', '>')) { + /* shell metacharacters are not supported without quoting. + * Note that ';' is already handled above. */ + goto out_error; + } + + /* an unquoted, regular character. Just consume it directly. */ + if (str) + g_string_append_c(str, value[i]); + i++; + +loop1_next:; + } + + nm_assert_not_reached(); out_value: - if (i == 0) { - nm_assert (!str); - *to_free = NULL; - return ""; - } - - if (str) { - if (str->len == 0 || str->str[0] == '\0') { - g_string_free (str, TRUE); - *to_free = NULL; - return ""; - } else { - *to_free = g_string_free (str, FALSE); - return *to_free; - } - } - - if (value[i] != '\0') { - *to_free = g_strndup (value, i); - return *to_free; - } - - *to_free = NULL; - return value; + if (i == 0) { + nm_assert(!str); + *to_free = NULL; + return ""; + } + + if (str) { + if (str->len == 0 || str->str[0] == '\0') { + g_string_free(str, TRUE); + *to_free = NULL; + return ""; + } else { + *to_free = g_string_free(str, FALSE); + return *to_free; + } + } + + if (value[i] != '\0') { + *to_free = g_strndup(value, i); + return *to_free; + } + + *to_free = NULL; + return value; out_error: - if (str) - g_string_free (str, TRUE); - *to_free = NULL; - return NULL; + if (str) + g_string_free(str, TRUE); + *to_free = NULL; + return NULL; } /*****************************************************************************/ shvarFile * -svFile_new (const char *name, - int fd, - const char *content) +svFile_new(const char *name, int fd, const char *content) { - shvarFile *s; - const char *p; - const char *q; - - nm_assert (name); - nm_assert (fd >= -1); - - s = g_slice_new (shvarFile); - *s = (shvarFile) { - .fileName = g_strdup (name), - .fd = fd, - .lst_head = C_LIST_INIT (s->lst_head), - .lst_idx = g_hash_table_new (nm_pstr_hash, nm_pstr_equal), - }; - - if (content) { - for (p = content; (q = strchr (p, '\n')) != NULL; p = q + 1) - _line_link_parse (s, p, q - p); - if (p[0]) - _line_link_parse (s, p, strlen (p)); - } - - return s; + shvarFile * s; + const char *p; + const char *q; + + nm_assert(name); + nm_assert(fd >= -1); + + s = g_slice_new(shvarFile); + *s = (shvarFile){ + .fileName = g_strdup(name), + .fd = fd, + .lst_head = C_LIST_INIT(s->lst_head), + .lst_idx = g_hash_table_new(nm_pstr_hash, nm_pstr_equal), + }; + + if (content) { + for (p = content; (q = strchr(p, '\n')) != NULL; p = q + 1) + _line_link_parse(s, p, q - p); + if (p[0]) + _line_link_parse(s, p, strlen(p)); + } + + return s; } const char * -svFileGetName (const shvarFile *s) +svFileGetName(const shvarFile *s) { - nm_assert (s); + nm_assert(s); - return s->fileName; + return s->fileName; } void -_nmtst_svFileSetName (shvarFile *s, const char *fileName) +_nmtst_svFileSetName(shvarFile *s, const char *fileName) { - /* changing the file name is not supported for regular - * operation. Only allowed to use in tests, otherwise, - * the filename is immutable. */ - g_free (s->fileName); - s->fileName = g_strdup (fileName); + /* changing the file name is not supported for regular + * operation. Only allowed to use in tests, otherwise, + * the filename is immutable. */ + g_free(s->fileName); + s->fileName = g_strdup(fileName); } void -_nmtst_svFileSetModified (shvarFile *s) +_nmtst_svFileSetModified(shvarFile *s) { - /* marking a file as modified is only for testing. */ - s->modified = TRUE; + /* marking a file as modified is only for testing. */ + s->modified = TRUE; } /*****************************************************************************/ static void -ASSERT_shvarLine (const shvarLine *line) +ASSERT_shvarLine(const shvarLine *line) { #if NM_MORE_ASSERTS > 5 - const char *s, *s2; - - nm_assert (line); - if (!line->key) { - nm_assert (line->line); - nm_assert (!line->key_with_prefix); - s = nm_str_skip_leading_spaces (line->line); - s2 = strchr (s, '='); - nm_assert (!s2 || !_shell_is_name (s, s2 - s)); - } else { - nm_assert (line->key_with_prefix); - nm_assert (line->key == nm_str_skip_leading_spaces (line->key_with_prefix)); - nm_assert (_shell_is_name (line->key, -1)); - } + const char *s, *s2; + + nm_assert(line); + if (!line->key) { + nm_assert(line->line); + nm_assert(!line->key_with_prefix); + s = nm_str_skip_leading_spaces(line->line); + s2 = strchr(s, '='); + nm_assert(!s2 || !_shell_is_name(s, s2 - s)); + } else { + nm_assert(line->key_with_prefix); + nm_assert(line->key == nm_str_skip_leading_spaces(line->key_with_prefix)); + nm_assert(_shell_is_name(line->key, -1)); + } #endif } static shvarLine * -line_new_parse (const char *value, gsize len) +line_new_parse(const char *value, gsize len) { - shvarLine *line; - gsize k, e; - - nm_assert (value); - - line = g_slice_new (shvarLine); - *line = (shvarLine) { - .lst = C_LIST_INIT (line->lst), - .dirty = TRUE, - }; - - for (k = 0; k < len; k++) { - if (g_ascii_isspace (value[k])) - continue; - - if ( g_ascii_isalpha (value[k]) - || value[k] == '_') { - for (e = k + 1; e < len; e++) { - if (value[e] == '=') { - nm_assert (_shell_is_name (&value[k], e - k)); - line->line = g_strndup (&value[e + 1], len - e - 1); - line->key_with_prefix = g_strndup (value, e); - line->key = &line->key_with_prefix[k]; - ASSERT_shvarLine (line); - return line; - } - if ( !g_ascii_isalnum (value[e]) - && value[e] != '_') - break; - } - } - break; - } - line->line = g_strndup (value, len); - ASSERT_shvarLine (line); - return line; + shvarLine *line; + gsize k, e; + + nm_assert(value); + + line = g_slice_new(shvarLine); + *line = (shvarLine){ + .lst = C_LIST_INIT(line->lst), + .dirty = TRUE, + }; + + for (k = 0; k < len; k++) { + if (g_ascii_isspace(value[k])) + continue; + + if (g_ascii_isalpha(value[k]) || value[k] == '_') { + for (e = k + 1; e < len; e++) { + if (value[e] == '=') { + nm_assert(_shell_is_name(&value[k], e - k)); + line->line = g_strndup(&value[e + 1], len - e - 1); + line->key_with_prefix = g_strndup(value, e); + line->key = &line->key_with_prefix[k]; + ASSERT_shvarLine(line); + return line; + } + if (!g_ascii_isalnum(value[e]) && value[e] != '_') + break; + } + } + break; + } + line->line = g_strndup(value, len); + ASSERT_shvarLine(line); + return line; } static shvarLine * -line_new_build (const char *key, const char *value) +line_new_build(const char *key, const char *value) { - char *value_escaped = NULL; - shvarLine *line; - char *new_key; - - value = svEscape (value, &value_escaped); - - line = g_slice_new (shvarLine); - new_key = g_strdup (key), - *line = (shvarLine) { - .lst = C_LIST_INIT (line->lst), - .line = value_escaped ?: g_strdup (value), - .key_with_prefix = new_key, - .key = new_key, - .dirty = FALSE, - }; - ASSERT_shvarLine (line); - return line; + char * value_escaped = NULL; + shvarLine *line; + char * new_key; + + value = svEscape(value, &value_escaped); + + line = g_slice_new(shvarLine); + new_key = g_strdup(key), *line = (shvarLine){ + .lst = C_LIST_INIT(line->lst), + .line = value_escaped ?: g_strdup(value), + .key_with_prefix = new_key, + .key = new_key, + .dirty = FALSE, + }; + ASSERT_shvarLine(line); + return line; } static gboolean -line_set (shvarLine *line, const char *value) +line_set(shvarLine *line, const char *value) { - char *value_escaped = NULL; - gboolean changed = FALSE; - - ASSERT_shvarLine (line); - nm_assert (line->key); - - line->dirty = FALSE; - - if (line->key != line->key_with_prefix) { - memmove (line->key_with_prefix, line->key, strlen (line->key) + 1); - line->key = line->key_with_prefix; - changed = TRUE; - ASSERT_shvarLine (line); - } - - value = svEscape (value, &value_escaped); - - if (line->line) { - if (nm_streq (value, line->line)) { - g_free (value_escaped); - return changed; - } - g_free (line->line); - } - - line->line = value_escaped ?: g_strdup (value); - ASSERT_shvarLine (line); - return TRUE; + char * value_escaped = NULL; + gboolean changed = FALSE; + + ASSERT_shvarLine(line); + nm_assert(line->key); + + line->dirty = FALSE; + + if (line->key != line->key_with_prefix) { + memmove(line->key_with_prefix, line->key, strlen(line->key) + 1); + line->key = line->key_with_prefix; + changed = TRUE; + ASSERT_shvarLine(line); + } + + value = svEscape(value, &value_escaped); + + if (line->line) { + if (nm_streq(value, line->line)) { + g_free(value_escaped); + return changed; + } + g_free(line->line); + } + + line->line = value_escaped ?: g_strdup(value); + ASSERT_shvarLine(line); + return TRUE; } static void -line_free (shvarLine *line) +line_free(shvarLine *line) { - ASSERT_shvarLine (line); - c_list_unlink_stale (&line->lst); - g_free (line->line); - g_free (line->key_with_prefix); - g_slice_free (shvarLine, line); + ASSERT_shvarLine(line); + c_list_unlink_stale(&line->lst); + g_free(line->line); + g_free(line->key_with_prefix); + g_slice_free(shvarLine, line); } /*****************************************************************************/ static void -_line_link_parse (shvarFile *s, const char *value, gsize len) +_line_link_parse(shvarFile *s, const char *value, gsize len) { - shvarLine *line; + shvarLine *line; - line = line_new_parse (value, len); - if (!line->key) - goto do_link; + line = line_new_parse(value, len); + if (!line->key) + goto do_link; - if (G_UNLIKELY (!g_hash_table_insert (s->lst_idx, line, line))) { - shvarLine *existing_key; - shvarLine *existing_val; + if (G_UNLIKELY(!g_hash_table_insert(s->lst_idx, line, line))) { + shvarLine *existing_key; + shvarLine *existing_val; - /* Slow-path: we have duplicate keys. Fix the mess we created. - * Unfortunately, g_hash_table_insert() now had to allocate an extra - * array to track the keys/values differently. I wish there was an - * GHashTable API to add a key only if it does not exist yet. */ + /* Slow-path: we have duplicate keys. Fix the mess we created. + * Unfortunately, g_hash_table_insert() now had to allocate an extra + * array to track the keys/values differently. I wish there was an + * GHashTable API to add a key only if it does not exist yet. */ - if (!g_hash_table_lookup_extended (s->lst_idx, line, (gpointer *) &existing_key, (gpointer *) &existing_val)) - nm_assert_not_reached (); + if (!g_hash_table_lookup_extended(s->lst_idx, + line, + (gpointer *) &existing_key, + (gpointer *) &existing_val)) + nm_assert_not_reached(); - nm_assert (existing_val == line); - nm_assert (existing_key != line); - line->prev_shadowed = existing_key; - g_hash_table_replace (s->lst_idx, line, line); - } + nm_assert(existing_val == line); + nm_assert(existing_key != line); + line->prev_shadowed = existing_key; + g_hash_table_replace(s->lst_idx, line, line); + } do_link: - c_list_link_tail (&s->lst_head, &line->lst); + c_list_link_tail(&s->lst_head, &line->lst); } /*****************************************************************************/ - /* Open the file <name>, returning a shvarFile on success and NULL on failure. * Add a wrinkle to let the caller specify whether or not to create the file * (actually, return a structure anyway) if it doesn't exist. */ static shvarFile * -svOpenFileInternal (const char *name, gboolean create, GError **error) +svOpenFileInternal(const char *name, gboolean create, GError **error) { - gboolean closefd = FALSE; - int errsv = 0; - gs_free char *content = NULL; - gs_free_error GError *local = NULL; - nm_auto_close int fd = -1; - - if (create) - fd = open (name, O_RDWR | O_CLOEXEC); /* NOT O_CREAT */ - if (fd < 0) { - /* try read-only */ - fd = open (name, O_RDONLY | O_CLOEXEC); /* NOT O_CREAT */ - if (fd < 0) - errsv = errno; - else - closefd = TRUE; - } - - if (fd < 0) { - if (create) - return svFile_new (name, -1, NULL); - - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), - "Could not read file '%s': %s", - name, nm_strerror_native (errsv)); - return NULL; - } - - if (!nm_utils_fd_get_contents (closefd ? nm_steal_fd (&fd) : fd, - closefd, - 10 * 1024 * 1024, - NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, - &content, - NULL, - NULL, - &local)) { - if (create) - return svFile_new (name, -1, NULL); - - g_set_error (error, G_FILE_ERROR, - local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED, - "Could not read file '%s': %s", - name, local->message); - return NULL; - } - - /* closefd is set if we opened the file read-only, so go ahead and - * close it, because we can't write to it anyway */ - nm_assert (closefd || fd >= 0); - return svFile_new (name, - !closefd - ? nm_steal_fd (&fd) - : -1, - content); + gboolean closefd = FALSE; + int errsv = 0; + gs_free char *content = NULL; + gs_free_error GError *local = NULL; + nm_auto_close int fd = -1; + + if (create) + fd = open(name, O_RDWR | O_CLOEXEC); /* NOT O_CREAT */ + if (fd < 0) { + /* try read-only */ + fd = open(name, O_RDONLY | O_CLOEXEC); /* NOT O_CREAT */ + if (fd < 0) + errsv = errno; + else + closefd = TRUE; + } + + if (fd < 0) { + if (create) + return svFile_new(name, -1, NULL); + + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not read file '%s': %s", + name, + nm_strerror_native(errsv)); + return NULL; + } + + if (!nm_utils_fd_get_contents(closefd ? nm_steal_fd(&fd) : fd, + closefd, + 10 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &content, + NULL, + NULL, + &local)) { + if (create) + return svFile_new(name, -1, NULL); + + g_set_error(error, + G_FILE_ERROR, + local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED, + "Could not read file '%s': %s", + name, + local->message); + return NULL; + } + + /* closefd is set if we opened the file read-only, so go ahead and + * close it, because we can't write to it anyway */ + nm_assert(closefd || fd >= 0); + return svFile_new(name, !closefd ? nm_steal_fd(&fd) : -1, content); } /* Open the file <name>, return shvarFile on success, NULL on failure */ shvarFile * -svOpenFile (const char *name, GError **error) +svOpenFile(const char *name, GError **error) { - return svOpenFileInternal (name, FALSE, error); + return svOpenFileInternal(name, FALSE, error); } /* Create a new file structure, returning actual data if the file exists, * and a suitable starting point if it doesn't. */ shvarFile * -svCreateFile (const char *name) +svCreateFile(const char *name) { - return svOpenFileInternal (name, TRUE, NULL); + return svOpenFileInternal(name, TRUE, NULL); } /*****************************************************************************/ static gboolean -_svKeyMatchesType (const char *key, SvKeyType match_key_type) +_svKeyMatchesType(const char *key, SvKeyType match_key_type) { - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ANY)) - return TRUE; - -#define _IS_NUMBERED(key, tag) \ - ({ \ - gint64 _idx; \ - \ - NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, tag, &_idx) \ - && _idx >= 0; \ - }) - - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) { - if ( _IS_NUMBERED (key, "ADDRESS") - || _IS_NUMBERED (key, "NETMASK") - || _IS_NUMBERED (key, "GATEWAY") - || _IS_NUMBERED (key, "METRIC") - || _IS_NUMBERED (key, "OPTIONS")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) { - if ( _IS_NUMBERED (key, "IPADDR") - || _IS_NUMBERED (key, "PREFIX") - || _IS_NUMBERED (key, "NETMASK") - || _IS_NUMBERED (key, "GATEWAY")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_USER)) { - if (g_str_has_prefix (key, "NM_USER_")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_TC)) { - if ( _IS_NUMBERED (key, "QDISC") - || _IS_NUMBERED (key, "FILTER")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_SRIOV_VF)) { - if (_IS_NUMBERED (key, "SRIOV_VF")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) { - if (_IS_NUMBERED (key, "ROUTING_RULE_")) - return TRUE; - } - if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) { - if (_IS_NUMBERED (key, "ROUTING_RULE6_")) - return TRUE; - } - - return FALSE; + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ANY)) + return TRUE; + +#define _IS_NUMBERED(key, tag) \ + ({ \ + gint64 _idx; \ + \ + NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, &_idx) \ + &&_idx >= 0; \ + }) + + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) { + if (_IS_NUMBERED(key, "ADDRESS") || _IS_NUMBERED(key, "NETMASK") + || _IS_NUMBERED(key, "GATEWAY") || _IS_NUMBERED(key, "METRIC") + || _IS_NUMBERED(key, "OPTIONS")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) { + if (_IS_NUMBERED(key, "IPADDR") || _IS_NUMBERED(key, "PREFIX") + || _IS_NUMBERED(key, "NETMASK") || _IS_NUMBERED(key, "GATEWAY")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_USER)) { + if (g_str_has_prefix(key, "NM_USER_")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_TC)) { + if (_IS_NUMBERED(key, "QDISC") || _IS_NUMBERED(key, "FILTER")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_SRIOV_VF)) { + if (_IS_NUMBERED(key, "SRIOV_VF")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) { + if (_IS_NUMBERED(key, "ROUTING_RULE_")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) { + if (_IS_NUMBERED(key, "ROUTING_RULE6_")) + return TRUE; + } + + return FALSE; } gint64 -svNumberedParseKey (const char *key) +svNumberedParseKey(const char *key) { - gint64 idx; + gint64 idx; - if ( NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, "ROUTING_RULE_", &idx) - || NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, "ROUTING_RULE6_", &idx)) - return idx; - return -1; + if (NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE_", &idx) + || NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE6_", &idx)) + return idx; + return -1; } GHashTable * -svGetKeys (shvarFile *s, SvKeyType match_key_type) +svGetKeys(shvarFile *s, SvKeyType match_key_type) { - GHashTable *keys = NULL; - CList *current; - const shvarLine *line; - - nm_assert (s); - - c_list_for_each (current, &s->lst_head) { - line = c_list_entry (current, shvarLine, lst); - if ( line->key - && line->line - && _svKeyMatchesType (line->key, match_key_type)) { - /* we don't clone the keys. The keys are only valid - * until @s gets modified. */ - if (!keys) - keys = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL); - g_hash_table_add (keys, (gpointer) line->key); - } - } - return keys; + GHashTable * keys = NULL; + CList * current; + const shvarLine *line; + + nm_assert(s); + + c_list_for_each (current, &s->lst_head) { + line = c_list_entry(current, shvarLine, lst); + if (line->key && line->line && _svKeyMatchesType(line->key, match_key_type)) { + /* we don't clone the keys. The keys are only valid + * until @s gets modified. */ + if (!keys) + keys = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL); + g_hash_table_add(keys, (gpointer) line->key); + } + } + return keys; } static int -_get_keys_sorted_cmp (gconstpointer a, - gconstpointer b, - gpointer user_data) +_get_keys_sorted_cmp(gconstpointer a, gconstpointer b, gpointer user_data) { - const char *k_a = *((const char *const*) a); - const char *k_b = *((const char *const*) b); - gint64 n_a; - gint64 n_b; - - n_a = svNumberedParseKey (k_a); - n_b = svNumberedParseKey (k_b); - NM_CMP_DIRECT (n_a, n_b); - NM_CMP_RETURN (strcmp (k_a, k_b)); - nm_assert_not_reached (); - return 0; + const char *k_a = *((const char *const *) a); + const char *k_b = *((const char *const *) b); + gint64 n_a; + gint64 n_b; + + n_a = svNumberedParseKey(k_a); + n_b = svNumberedParseKey(k_b); + NM_CMP_DIRECT(n_a, n_b); + NM_CMP_RETURN(strcmp(k_a, k_b)); + nm_assert_not_reached(); + return 0; } const char ** -svGetKeysSorted (shvarFile *s, - SvKeyType match_key_type, - guint *out_len) +svGetKeysSorted(shvarFile *s, SvKeyType match_key_type, guint *out_len) { - gs_unref_hashtable GHashTable *keys_hash = NULL; - - keys_hash = svGetKeys (s, match_key_type); - if (!keys_hash) { - NM_SET_OUT (out_len, 0); - return NULL; - } - return (const char **) nm_utils_hash_keys_to_array (keys_hash, - _get_keys_sorted_cmp, - NULL, - out_len); + gs_unref_hashtable GHashTable *keys_hash = NULL; + + keys_hash = svGetKeys(s, match_key_type); + if (!keys_hash) { + NM_SET_OUT(out_len, 0); + return NULL; + } + return ( + const char **) nm_utils_hash_keys_to_array(keys_hash, _get_keys_sorted_cmp, NULL, out_len); } /*****************************************************************************/ const char * -svFindFirstNumberedKey (shvarFile *s, const char *key_prefix) +svFindFirstNumberedKey(shvarFile *s, const char *key_prefix) { - const shvarLine *line; + const shvarLine *line; - g_return_val_if_fail (s, NULL); - g_return_val_if_fail (key_prefix, NULL); + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key_prefix, NULL); - c_list_for_each_entry (line, &s->lst_head, lst) { - if ( line->key - && line->line - && nms_ifcfg_rh_utils_is_numbered_tag (line->key, key_prefix, NULL)) - return line->key; - } + c_list_for_each_entry (line, &s->lst_head, lst) { + if (line->key && line->line + && nms_ifcfg_rh_utils_is_numbered_tag(line->key, key_prefix, NULL)) + return line->key; + } - return NULL; + return NULL; } /*****************************************************************************/ static const char * -_svGetValue (shvarFile *s, const char *key, char **to_free) +_svGetValue(shvarFile *s, const char *key, char **to_free) { - const shvarLine *line; - const char *v; - - nm_assert (s); - nm_assert (_shell_is_name (key, -1)); - nm_assert (to_free); - - ASSERT_key_is_well_known (key); - - line = g_hash_table_lookup (s->lst_idx, &key); - - if (line && line->line) { - v = svUnescape (line->line, to_free); - if (!v) { - /* a wrongly quoted value is treated like the empty string. - * See also svWriteFile(), which handles unparsable values - * that way. */ - nm_assert (!*to_free); - return ""; - } - return v; - } - *to_free = NULL; - return NULL; + const shvarLine *line; + const char * v; + + nm_assert(s); + nm_assert(_shell_is_name(key, -1)); + nm_assert(to_free); + + ASSERT_key_is_well_known(key); + + line = g_hash_table_lookup(s->lst_idx, &key); + + if (line && line->line) { + v = svUnescape(line->line, to_free); + if (!v) { + /* a wrongly quoted value is treated like the empty string. + * See also svWriteFile(), which handles unparsable values + * that way. */ + nm_assert(!*to_free); + return ""; + } + return v; + } + *to_free = NULL; + return NULL; } /* Returns the value for key. The value is either owned by @s @@ -1116,13 +1114,13 @@ _svGetValue (shvarFile *s, const char *key, char **to_free) * - like svGetValueStr(), but does not ignore empty string values. */ const char * -svGetValue (shvarFile *s, const char *key, char **to_free) +svGetValue(shvarFile *s, const char *key, char **to_free) { - g_return_val_if_fail (s, NULL); - g_return_val_if_fail (key, NULL); - g_return_val_if_fail (to_free, NULL); + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(to_free, NULL); - return _svGetValue (s, key, to_free); + return _svGetValue(s, key, to_free); } /* Returns the value for key. The value is either owned by @s @@ -1132,20 +1130,20 @@ svGetValue (shvarFile *s, const char *key, char **to_free) * - like svGetValueStr_cp(), but avoids cloning the value if possible. */ const char * -svGetValueStr (shvarFile *s, const char *key, char **to_free) +svGetValueStr(shvarFile *s, const char *key, char **to_free) { - const char *value; - - g_return_val_if_fail (s, NULL); - g_return_val_if_fail (key, NULL); - g_return_val_if_fail (to_free, NULL); - - value = _svGetValue (s, key, to_free); - if (!value || !value[0]) { - nm_assert (!*to_free); - return NULL; - } - return value; + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(to_free, NULL); + + value = _svGetValue(s, key, to_free); + if (!value || !value[0]) { + nm_assert(!*to_free); + return NULL; + } + return value; } /* Returns the value for key. The returned value must be freed @@ -1155,20 +1153,20 @@ svGetValueStr (shvarFile *s, const char *key, char **to_free) * - like svGetValueStr_cp(), but does not ignore an empty string. */ char * -svGetValue_cp (shvarFile *s, const char *key) +svGetValue_cp(shvarFile *s, const char *key) { - char *to_free; - const char *value; - - g_return_val_if_fail (s, NULL); - g_return_val_if_fail (key, NULL); - - value = _svGetValue (s, key, &to_free); - if (!value) { - nm_assert (!to_free); - return NULL; - } - return to_free ?: g_strdup (value); + char * to_free; + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + + value = _svGetValue(s, key, &to_free); + if (!value) { + nm_assert(!to_free); + return NULL; + } + return to_free ?: g_strdup(value); } /* Returns the value for key. The returned value must be freed @@ -1179,20 +1177,20 @@ svGetValue_cp (shvarFile *s, const char *key) * - like svGetValue_cp(), but returns NULL instead of an empty string. */ char * -svGetValueStr_cp (shvarFile *s, const char *key) +svGetValueStr_cp(shvarFile *s, const char *key) { - char *to_free; - const char *value; - - g_return_val_if_fail (s, NULL); - g_return_val_if_fail (key, NULL); - - value = _svGetValue (s, key, &to_free); - if (!value || !value[0]) { - nm_assert (!to_free); - return NULL; - } - return to_free ?: g_strdup (value); + char * to_free; + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + + value = _svGetValue(s, key, &to_free); + if (!value || !value[0]) { + nm_assert(!to_free); + return NULL; + } + return to_free ?: g_strdup(value); } /* svGetValueBoolean: @@ -1205,13 +1203,27 @@ svGetValueStr_cp (shvarFile *s, const char *key) * Returns: the parsed boolean value or @fallback. */ int -svGetValueBoolean (shvarFile *s, const char *key, int fallback) +svGetValueBoolean(shvarFile *s, const char *key, int fallback) { - gs_free char *to_free = NULL; - const char *value; + gs_free char *to_free = NULL; + const char * value; - value = _svGetValue (s, key, &to_free); - return svParseBoolean (value, fallback); + value = _svGetValue(s, key, &to_free); + return svParseBoolean(value, fallback); +} + +/* svGetValueTernary: + * @s: fhe file + * @key: the name of the key to read + * + * Reads a value @key and converts it to a NMTernary value. + * + * Returns: the parsed NMTernary + */ +NMTernary +svGetValueTernary(shvarFile *s, const char *key) +{ + return svGetValueBoolean(s, key, NM_TERNARY_DEFAULT); } /* svGetValueInt64: @@ -1225,173 +1237,171 @@ svGetValueBoolean (shvarFile *s, const char *key, int fallback) * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64(). * In case of error, @errno will be set and @fallback returned. */ gint64 -svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback) +svGetValueInt64(shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback) { - char *to_free; - const char *value; - gint64 result; - int errsv; - - value = _svGetValue (s, key, &to_free); - if (!value) { - nm_assert (!to_free); - /* indicate that the key does not exist (or has a syntax error - * and svUnescape() failed). */ - errno = ENOKEY; - return fallback; - } - - result = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback); - if (to_free) { - errsv = errno; - g_free (to_free); - errno = errsv; - } - return result; + char * to_free; + const char *value; + gint64 result; + int errsv; + + value = _svGetValue(s, key, &to_free); + if (!value) { + nm_assert(!to_free); + /* indicate that the key does not exist (or has a syntax error + * and svUnescape() failed). */ + errno = ENOKEY; + return fallback; + } + + result = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback); + if (to_free) { + errsv = errno; + g_free(to_free); + errno = errsv; + } + return result; } gboolean -svGetValueEnum (shvarFile *s, const char *key, - GType gtype, int *out_value, - GError **error) +svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GError **error) { - gs_free char *to_free = NULL; - const char *svalue; - gs_free char *err_token = NULL; - int value; - - svalue = _svGetValue (s, key, &to_free); - if (!svalue) { - /* don't touch out_value. The caller is supposed - * to initialize it with the default value. */ - return TRUE; - } - - if (!nm_utils_enum_from_str (gtype, svalue, &value, &err_token)) { - g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Invalid token \"%s\" in \"%s\" for %s", - err_token, svalue, key); - return FALSE; - } - - NM_SET_OUT (out_value, value); - return TRUE; + gs_free char *to_free = NULL; + const char * svalue; + gs_free char *err_token = NULL; + int value; + + svalue = _svGetValue(s, key, &to_free); + if (!svalue) { + /* don't touch out_value. The caller is supposed + * to initialize it with the default value. */ + return TRUE; + } + + if (!nm_utils_enum_from_str(gtype, svalue, &value, &err_token)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Invalid token \"%s\" in \"%s\" for %s", + err_token, + svalue, + key); + return FALSE; + } + + NM_SET_OUT(out_value, value); + return TRUE; } /*****************************************************************************/ gboolean -svUnsetAll (shvarFile *s, SvKeyType match_key_type) +svUnsetAll(shvarFile *s, SvKeyType match_key_type) { - shvarLine *line; - gboolean changed = FALSE; - - g_return_val_if_fail (s, FALSE); - - c_list_for_each_entry (line, &s->lst_head, lst) { - ASSERT_shvarLine (line); - if ( line->key - && _svKeyMatchesType (line->key, match_key_type)) { - if (nm_clear_g_free (&line->line)) { - ASSERT_shvarLine (line); - changed = TRUE; - } - } - } - - if (changed) - s->modified = TRUE; - return changed; + shvarLine *line; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + + c_list_for_each_entry (line, &s->lst_head, lst) { + ASSERT_shvarLine(line); + if (line->key && _svKeyMatchesType(line->key, match_key_type)) { + if (nm_clear_g_free(&line->line)) { + ASSERT_shvarLine(line); + changed = TRUE; + } + } + } + + if (changed) + s->modified = TRUE; + return changed; } gboolean -svUnsetDirtyWellknown (shvarFile *s, NMTernary new_dirty_value) +svUnsetDirtyWellknown(shvarFile *s, NMTernary new_dirty_value) { - shvarLine *line; - gboolean changed = FALSE; - - g_return_val_if_fail (s, FALSE); - - c_list_for_each_entry (line, &s->lst_head, lst) { - const NMSIfcfgKeyTypeInfo *ti; - - ASSERT_shvarLine (line); - - if ( line->dirty - && line->key - && line->line - && (ti = nms_ifcfg_rh_utils_is_well_known_key (line->key)) - && !NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY)) { - if (nm_clear_g_free (&line->line)) { - ASSERT_shvarLine (line); - changed = TRUE; - } - } - - if (new_dirty_value != NM_TERNARY_DEFAULT) - line->dirty = (new_dirty_value != NM_TERNARY_FALSE); - } - - if (changed) - s->modified = TRUE; - return changed; + shvarLine *line; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + + c_list_for_each_entry (line, &s->lst_head, lst) { + const NMSIfcfgKeyTypeInfo *ti; + + ASSERT_shvarLine(line); + + if (line->dirty && line->key && line->line + && (ti = nms_ifcfg_rh_utils_is_well_known_key(line->key)) + && !NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY)) { + if (nm_clear_g_free(&line->line)) { + ASSERT_shvarLine(line); + changed = TRUE; + } + } + + if (new_dirty_value != NM_TERNARY_DEFAULT) + line->dirty = (new_dirty_value != NM_TERNARY_FALSE); + } + + if (changed) + s->modified = TRUE; + return changed; } /* Same as svSetValueStr() but it preserves empty @value -- contrary to * svSetValueStr() for which "" effectively means to remove the value. */ gboolean -svSetValue (shvarFile *s, const char *key, const char *value) +svSetValue(shvarFile *s, const char *key, const char *value) { - shvarLine *line; - shvarLine *l_shadowed; - gboolean changed = FALSE; - - g_return_val_if_fail (s, FALSE); - g_return_val_if_fail (key, FALSE); - - nm_assert (_shell_is_name (key, -1)); - - ASSERT_key_is_well_known (key); - - line = g_hash_table_lookup (s->lst_idx, &key); - if ( line - && (l_shadowed = line->prev_shadowed)) { - /* if we find multiple entries for the same key, we can - * delete the shadowed ones. */ - line->prev_shadowed = NULL; - changed = TRUE; - do { - shvarLine *l = l_shadowed; - - l_shadowed = l_shadowed->prev_shadowed; - line_free (l); - } while (l_shadowed); - } - - if (!value) { - if (line) { - /* We only clear the value, but leave the line entry. This way, if we - * happen to re-add the value, we write it to the same line again. */ - if (nm_clear_g_free (&line->line)) { - changed = TRUE; - } - } - } else { - if (!line) { - line = line_new_build (key, value); - if (!g_hash_table_add (s->lst_idx, line)) - nm_assert_not_reached (); - c_list_link_tail (&s->lst_head, &line->lst); - changed = TRUE; - } else { - if (line_set (line, value)) - changed = TRUE; - } - } - - if (changed) - s->modified = TRUE; - return changed; + shvarLine *line; + shvarLine *l_shadowed; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + g_return_val_if_fail(key, FALSE); + + nm_assert(_shell_is_name(key, -1)); + + ASSERT_key_is_well_known(key); + + line = g_hash_table_lookup(s->lst_idx, &key); + if (line && (l_shadowed = line->prev_shadowed)) { + /* if we find multiple entries for the same key, we can + * delete the shadowed ones. */ + line->prev_shadowed = NULL; + changed = TRUE; + do { + shvarLine *l = l_shadowed; + + l_shadowed = l_shadowed->prev_shadowed; + line_free(l); + } while (l_shadowed); + } + + if (!value) { + if (line) { + /* We only clear the value, but leave the line entry. This way, if we + * happen to re-add the value, we write it to the same line again. */ + if (nm_clear_g_free(&line->line)) { + changed = TRUE; + } + } + } else { + if (!line) { + line = line_new_build(key, value); + if (!g_hash_table_add(s->lst_idx, line)) + nm_assert_not_reached(); + c_list_link_tail(&s->lst_head, &line->lst); + changed = TRUE; + } else { + if (line_set(line, value)) + changed = TRUE; + } + } + + if (changed) + s->modified = TRUE; + return changed; } /* Set the variable <key> equal to the value <value>. @@ -1400,53 +1410,62 @@ svSetValue (shvarFile *s, const char *key, const char *value) * to the bottom of the file. */ gboolean -svSetValueStr (shvarFile *s, const char *key, const char *value) +svSetValueStr(shvarFile *s, const char *key, const char *value) { - return svSetValue (s, key, value && value[0] ? value : NULL); + return svSetValue(s, key, value && value[0] ? value : NULL); } gboolean -svSetValueInt64 (shvarFile *s, const char *key, gint64 value) +svSetValueInt64(shvarFile *s, const char *key, gint64 value) { - char buf[NM_DECIMAL_STR_MAX (value)]; + char buf[NM_DECIMAL_STR_MAX(value)]; + + return svSetValue(s, key, nm_sprintf_buf(buf, "%" G_GINT64_FORMAT, value)); +} - return svSetValue (s, key, nm_sprintf_buf (buf, "%"G_GINT64_FORMAT, value)); +gboolean +svSetValueInt64_cond(shvarFile *s, const char *key, gboolean do_set, gint64 value) +{ + if (do_set) + return svSetValueInt64(s, key, value); + else + return svUnsetValue(s, key); } gboolean -svSetValueInt64_cond (shvarFile *s, const char *key, gboolean do_set, gint64 value) +svSetValueBoolean(shvarFile *s, const char *key, gboolean value) { - if (do_set) - return svSetValueInt64 (s, key, value); - else - return svUnsetValue (s, key); + return svSetValue(s, key, value ? "yes" : "no"); } gboolean -svSetValueBoolean (shvarFile *s, const char *key, gboolean value) +svSetValueTernary(shvarFile *s, const char *key, NMTernary value) { - return svSetValue (s, key, value ? "yes" : "no"); + if (NM_IN_SET(value, NM_TERNARY_TRUE, NM_TERNARY_FALSE)) + return svSetValueBoolean(s, key, (gboolean) value); + else + return svUnsetValue(s, key); } gboolean -svSetValueBoolean_cond_true (shvarFile *s, const char *key, gboolean value) +svSetValueBoolean_cond_true(shvarFile *s, const char *key, gboolean value) { - return svSetValue (s, key, value ? "yes" : NULL); + return svSetValue(s, key, value ? "yes" : NULL); } gboolean -svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value) +svSetValueEnum(shvarFile *s, const char *key, GType gtype, int value) { - gs_free char *v = NULL; + gs_free char *v = NULL; - v = _nm_utils_enum_to_str_full (gtype, value, " ", NULL); - return svSetValueStr (s, key, v); + v = _nm_utils_enum_to_str_full(gtype, value, " ", NULL); + return svSetValueStr(s, key, v); } gboolean -svUnsetValue (shvarFile *s, const char *key) +svUnsetValue(shvarFile *s, const char *key) { - return svSetValue (s, key, NULL); + return svSetValue(s, key, NULL); } /*****************************************************************************/ @@ -1458,97 +1477,109 @@ svUnsetValue (shvarFile *s, const char *key) * open() syscall. */ gboolean -svWriteFile (shvarFile *s, int mode, GError **error) +svWriteFile(shvarFile *s, int mode, GError **error) { - FILE *f; - int tmpfd; - CList *current; - int errsv; - - if (s->modified) { - if (s->fd == -1) - s->fd = open (s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode); - if (s->fd == -1) { - errsv = errno; - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), - "Could not open file '%s' for writing: %s", - s->fileName, nm_strerror_native (errsv)); - return FALSE; - } - if (ftruncate (s->fd, 0) < 0) { - errsv = errno; - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), - "Could not overwrite file '%s': %s", - s->fileName, nm_strerror_native (errsv)); - return FALSE; - } - - tmpfd = fcntl (s->fd, F_DUPFD_CLOEXEC, 0); - if (tmpfd == -1) { - errsv = errno; - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), - "Internal error writing file '%s': %s", - s->fileName, nm_strerror_native (errsv)); - return FALSE; - } - f = fdopen (tmpfd, "w"); - if (!f) { - errsv = errno; - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), - "Internal error writing file '%s': %s", - s->fileName, nm_strerror_native (errsv)); - return FALSE; - } - fseek (f, 0, SEEK_SET); - c_list_for_each (current, &s->lst_head) { - const shvarLine *line = c_list_entry (current, shvarLine, lst); - const char *str; - char *s_tmp; - gboolean valid_value; - - ASSERT_shvarLine (line); - - if (!line->key) { - str = nm_str_skip_leading_spaces (line->line); - if (NM_IN_SET (str[0], '\0', '#')) - fprintf (f, "%s\n", line->line); - else - fprintf (f, "#NM: %s\n", line->line); - continue; - } - - if (!line->line) - continue; - - /* we check that the assignment can be properly unescaped. */ - valid_value = !!svUnescape (line->line, &s_tmp); - g_free (s_tmp); - - if (valid_value) - fprintf (f, "%s=%s\n", line->key_with_prefix, line->line); - else { - fprintf (f, "%s=\n", line->key); - fprintf (f, "#NM: %s=%s\n", line->key_with_prefix, line->line); - } - } - fclose (f); - } - - return TRUE; + FILE * f; + int tmpfd; + CList *current; + int errsv; + + if (s->modified) { + if (s->fd == -1) + s->fd = open(s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode); + if (s->fd == -1) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not open file '%s' for writing: %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + if (ftruncate(s->fd, 0) < 0) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not overwrite file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + + tmpfd = fcntl(s->fd, F_DUPFD_CLOEXEC, 0); + if (tmpfd == -1) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Internal error writing file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + f = fdopen(tmpfd, "w"); + if (!f) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Internal error writing file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + fseek(f, 0, SEEK_SET); + c_list_for_each (current, &s->lst_head) { + const shvarLine *line = c_list_entry(current, shvarLine, lst); + const char * str; + char * s_tmp; + gboolean valid_value; + + ASSERT_shvarLine(line); + + if (!line->key) { + str = nm_str_skip_leading_spaces(line->line); + if (NM_IN_SET(str[0], '\0', '#')) + fprintf(f, "%s\n", line->line); + else + fprintf(f, "#NM: %s\n", line->line); + continue; + } + + if (!line->line) + continue; + + /* we check that the assignment can be properly unescaped. */ + valid_value = !!svUnescape(line->line, &s_tmp); + g_free(s_tmp); + + if (valid_value) + fprintf(f, "%s=%s\n", line->key_with_prefix, line->line); + else { + fprintf(f, "%s=\n", line->key); + fprintf(f, "#NM: %s=%s\n", line->key_with_prefix, line->line); + } + } + fclose(f); + } + + return TRUE; } void -svCloseFile (shvarFile *s) +svCloseFile(shvarFile *s) { - shvarLine *line; + shvarLine *line; - g_return_if_fail (s != NULL); + g_return_if_fail(s != NULL); - if (s->fd >= 0) - nm_close (s->fd); - g_free (s->fileName); - g_hash_table_destroy (s->lst_idx); - while ((line = c_list_first_entry (&s->lst_head, shvarLine, lst))) - line_free (line); - g_slice_free (shvarFile, s); + if (s->fd >= 0) + nm_close(s->fd); + g_free(s->fileName); + g_hash_table_destroy(s->lst_idx); + while ((line = c_list_first_entry(&s->lst_head, shvarLine, lst))) + line_free(line); + g_slice_free(shvarFile, s); } |