From 84ac7bea360cd369df26910e9685a7eed2327088 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 22 Oct 2015 20:12:31 +0200 Subject: [PATCH] util: split out extract_first_word() and related calls into extract-word.[ch] This is quite a lot of code these days, hence move it to its own source file. --- Makefile.am | 2 + src/basic/cgroup-util.c | 31 ++--- src/basic/cpu-set-util.c | 1 + src/basic/extract-word.c | 274 +++++++++++++++++++++++++++++++++++++++ src/basic/extract-word.h | 36 +++++ src/basic/strv.h | 3 +- src/basic/util.c | 251 +---------------------------------- src/basic/util.h | 13 +- src/shared/condition.c | 20 +-- 9 files changed, 344 insertions(+), 287 deletions(-) create mode 100644 src/basic/extract-word.c create mode 100644 src/basic/extract-word.h diff --git a/Makefile.am b/Makefile.am index 89eaf80575..06bd5d4d9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -781,6 +781,8 @@ libbasic_la_SOURCES = \ src/basic/refcnt.h \ src/basic/util.c \ src/basic/util.h \ + src/basic/extract-word.c \ + src/basic/extract-word.h \ src/basic/cpu-set-util.c \ src/basic/cpu-set-util.h \ src/basic/lockfile-util.c \ diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 95fc2b9e5d..a3ea512165 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -19,27 +19,28 @@ along with systemd; If not, see . ***/ -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include #include -#include +#include -#include "set.h" -#include "macro.h" -#include "util.h" -#include "formats-util.h" -#include "process-util.h" -#include "path-util.h" -#include "unit-name.h" +#include "extract-word.h" #include "fileio.h" -#include "special.h" -#include "mkdir.h" +#include "formats-util.h" #include "login-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "special.h" +#include "unit-name.h" +#include "util.h" #include "cgroup-util.h" int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c index 519583c167..5e064d854f 100644 --- a/src/basic/cpu-set-util.c +++ b/src/basic/cpu-set-util.c @@ -20,6 +20,7 @@ along with systemd; If not, see . ***/ +#include "extract-word.h" #include "util.h" #include "cpu-set-util.h" diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c new file mode 100644 index 0000000000..474e6fdd57 --- /dev/null +++ b/src/basic/extract-word.c @@ -0,0 +1,274 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "utf8.h" +#include "util.h" + +#include "extract-word.h" + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { + _cleanup_free_ char *s = NULL; + size_t allocated = 0, sz = 0; + int r; + + char quote = 0; /* 0 or ' or " */ + bool backslash = false; /* whether we've just seen a backslash */ + bool separator = false; /* whether we've just seen a separator */ + bool start = true; /* false means we're looking at a value */ + + assert(p); + assert(ret); + + if (!separators) + separators = WHITESPACE; + + /* Bail early if called after last value or with no input */ + if (!*p) + goto finish_force_terminate; + + /* Parses the first word of a string, and returns it in + * *ret. Removes all quotes in the process. When parsing fails + * (because of an uneven number of quotes or similar), leaves + * the pointer *p at the first invalid character. */ + + for (;;) { + char c = **p; + + if (start) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + + if (c == 0) + goto finish_force_terminate; + else if (strchr(separators, c)) { + (*p) ++; + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) + goto finish_force_next; + continue; + } + + /* We found a non-blank character, so we will always + * want to return a string (even if it is empty), + * allocate it here. */ + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + + start = false; + } + + if (backslash) { + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; + + if (c == 0) { + if ((flags & EXTRACT_CUNESCAPE_RELAX) && + (!quote || flags & EXTRACT_RELAX)) { + /* If we find an unquoted trailing backslash and we're in + * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the + * output. + * + * Unbalanced quotes will only be allowed in EXTRACT_RELAX + * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. + */ + s[sz++] = '\\'; + goto finish_force_terminate; + } + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } + + if (flags & EXTRACT_CUNESCAPE) { + uint32_t u; + + r = cunescape_one(*p, (size_t) -1, &c, &u); + if (r < 0) { + if (flags & EXTRACT_CUNESCAPE_RELAX) { + s[sz++] = '\\'; + s[sz++] = c; + goto end_escape; + } + return -EINVAL; + } + + (*p) += r - 1; + + if (c != 0) + s[sz++] = c; /* normal explicit char */ + else + sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ + } else + s[sz++] = c; + +end_escape: + backslash = false; + + } else if (quote) { /* inside either single or double quotes */ + if (c == 0) { + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } else if (c == quote) /* found the end quote */ + quote = 0; + else if (c == '\\') + backslash = true; + else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + + } else if (separator) { + if (c == 0) + goto finish_force_terminate; + if (!strchr(separators, c)) + goto finish; + + } else { + if (c == 0) + goto finish_force_terminate; + else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) + quote = c; + else if (c == '\\') + backslash = true; + else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p) ++; + goto finish_force_next; + } + separator = true; + } else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + } + + (*p) ++; + } + +finish_force_terminate: + *p = NULL; +finish: + if (!s) { + *p = NULL; + *ret = NULL; + return 0; + } + +finish_force_next: + s[sz] = 0; + *ret = s; + s = NULL; + + return 1; +} + +int extract_first_word_and_warn( + const char **p, + char **ret, + const char *separators, + ExtractFlags flags, + const char *unit, + const char *filename, + unsigned line, + const char *rvalue) { + + /* Try to unquote it, if it fails, warn about it and try again but this + * time using EXTRACT_CUNESCAPE_RELAX to keep the backslashes verbatim + * in invalid escape sequences. */ + const char *save; + int r; + + save = *p; + r = extract_first_word(p, ret, separators, flags); + if (r < 0 && !(flags & EXTRACT_CUNESCAPE_RELAX)) { + + /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ + *p = save; + r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting in command line, ignoring: \"%s\"", rvalue); + else + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid escape sequences in command line: \"%s\"", rvalue); + } + + return r; +} + +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { + va_list ap; + char **l; + int n = 0, i, c, r; + + /* Parses a number of words from a string, stripping any + * quotes if necessary. */ + + assert(p); + + /* Count how many words are expected */ + va_start(ap, flags); + for (;;) { + if (!va_arg(ap, char **)) + break; + n++; + } + va_end(ap); + + if (n <= 0) + return 0; + + /* Read all words into a temporary array */ + l = newa0(char*, n); + for (c = 0; c < n; c++) { + + r = extract_first_word(p, &l[c], separators, flags); + if (r < 0) { + int j; + + for (j = 0; j < c; j++) + free(l[j]); + + return r; + } + + if (r == 0) + break; + } + + /* If we managed to parse all words, return them in the passed + * in parameters */ + va_start(ap, flags); + for (i = 0; i < n; i++) { + char **v; + + v = va_arg(ap, char **); + assert(v); + + *v = l[i]; + } + va_end(ap); + + return c; +} diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h new file mode 100644 index 0000000000..ddc1c4f463 --- /dev/null +++ b/src/basic/extract-word.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "macro.h" + +typedef enum ExtractFlags { + EXTRACT_RELAX = 1, + EXTRACT_CUNESCAPE = 2, + EXTRACT_CUNESCAPE_RELAX = 4, + EXTRACT_QUOTES = 8, + EXTRACT_DONT_COALESCE_SEPARATORS = 16, +} ExtractFlags; + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); +int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; diff --git a/src/basic/strv.h b/src/basic/strv.h index a5dc696a87..e66794fc34 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -21,10 +21,11 @@ along with systemd; If not, see . ***/ +#include #include #include -#include +#include "extract-word.h" #include "util.h" char *strv_find(char **l, const char *name) _pure_; diff --git a/src/basic/util.c b/src/basic/util.c index 63c8abcf82..641e0b4d89 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -1587,7 +1587,7 @@ char *cescape(const char *s) { return r; } -static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) { +int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) { int r = 1; assert(p); @@ -5801,255 +5801,6 @@ int is_device_node(const char *path) { return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); } -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { - _cleanup_free_ char *s = NULL; - size_t allocated = 0, sz = 0; - int r; - - char quote = 0; /* 0 or ' or " */ - bool backslash = false; /* whether we've just seen a backslash */ - bool separator = false; /* whether we've just seen a separator */ - bool start = true; /* false means we're looking at a value */ - - assert(p); - assert(ret); - - if (!separators) - separators = WHITESPACE; - - /* Bail early if called after last value or with no input */ - if (!*p) - goto finish_force_terminate; - - /* Parses the first word of a string, and returns it in - * *ret. Removes all quotes in the process. When parsing fails - * (because of an uneven number of quotes or similar), leaves - * the pointer *p at the first invalid character. */ - - for (;;) { - char c = **p; - - if (start) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - - if (c == 0) - goto finish_force_terminate; - else if (strchr(separators, c)) { - (*p) ++; - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) - goto finish_force_next; - continue; - } - - /* We found a non-blank character, so we will always - * want to return a string (even if it is empty), - * allocate it here. */ - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - - start = false; - } - - if (backslash) { - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - - if (c == 0) { - if ((flags & EXTRACT_CUNESCAPE_RELAX) && - (!quote || flags & EXTRACT_RELAX)) { - /* If we find an unquoted trailing backslash and we're in - * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the - * output. - * - * Unbalanced quotes will only be allowed in EXTRACT_RELAX - * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. - */ - s[sz++] = '\\'; - goto finish_force_terminate; - } - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } - - if (flags & EXTRACT_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); - if (r < 0) { - if (flags & EXTRACT_CUNESCAPE_RELAX) { - s[sz++] = '\\'; - s[sz++] = c; - goto end_escape; - } - return -EINVAL; - } - - (*p) += r - 1; - - if (c != 0) - s[sz++] = c; /* normal explicit char */ - else - sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ - } else - s[sz++] = c; - -end_escape: - backslash = false; - - } else if (quote) { /* inside either single or double quotes */ - if (c == 0) { - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } else if (c == quote) /* found the end quote */ - quote = 0; - else if (c == '\\') - backslash = true; - else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - - } else if (separator) { - if (c == 0) - goto finish_force_terminate; - if (!strchr(separators, c)) - goto finish; - - } else { - if (c == 0) - goto finish_force_terminate; - else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) - quote = c; - else if (c == '\\') - backslash = true; - else if (strchr(separators, c)) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p) ++; - goto finish_force_next; - } - separator = true; - } else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - } - - (*p) ++; - } - -finish_force_terminate: - *p = NULL; -finish: - if (!s) { - *p = NULL; - *ret = NULL; - return 0; - } - -finish_force_next: - s[sz] = 0; - *ret = s; - s = NULL; - - return 1; -} - -int extract_first_word_and_warn( - const char **p, - char **ret, - const char *separators, - ExtractFlags flags, - const char *unit, - const char *filename, - unsigned line, - const char *rvalue) { - - /* Try to unquote it, if it fails, warn about it and try again but this - * time using EXTRACT_CUNESCAPE_RELAX to keep the backslashes verbatim - * in invalid escape sequences. */ - const char *save; - int r; - - save = *p; - r = extract_first_word(p, ret, separators, flags); - if (r < 0 && !(flags & EXTRACT_CUNESCAPE_RELAX)) { - - /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ - *p = save; - r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting in command line, ignoring: \"%s\"", rvalue); - else - log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid escape sequences in command line: \"%s\"", rvalue); - } - - return r; -} - -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { - va_list ap; - char **l; - int n = 0, i, c, r; - - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ - - assert(p); - - /* Count how many words are expected */ - va_start(ap, flags); - for (;;) { - if (!va_arg(ap, char **)) - break; - n++; - } - va_end(ap); - - if (n <= 0) - return 0; - - /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { - - r = extract_first_word(p, &l[c], separators, flags); - if (r < 0) { - int j; - - for (j = 0; j < c; j++) - free(l[j]); - - return r; - } - - if (r == 0) - break; - } - - /* If we managed to parse all words, return them in the passed - * in parameters */ - va_start(ap, flags); - for (i = 0; i < n; i++) { - char **v; - - v = va_arg(ap, char **); - assert(v); - - *v = l[i]; - } - va_end(ap); - - return c; -} - int free_and_strdup(char **p, const char *s) { char *t; diff --git a/src/basic/util.h b/src/basic/util.h index a3ebb987e4..132e6f862b 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -270,6 +270,7 @@ typedef enum UnescapeFlags { int cunescape(const char *s, UnescapeFlags flags, char **ret); int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); +int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode); char *xescape(const char *s, const char *bad); @@ -879,18 +880,6 @@ int is_symlink(const char *path); int is_dir(const char *path, bool follow); int is_device_node(const char *path); -typedef enum ExtractFlags { - EXTRACT_RELAX = 1, - EXTRACT_CUNESCAPE = 2, - EXTRACT_CUNESCAPE_RELAX = 4, - EXTRACT_QUOTES = 8, - EXTRACT_DONT_COALESCE_SEPARATORS = 16, -} ExtractFlags; - -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); -int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; - int free_and_strdup(char **p, const char *s); #define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) diff --git a/src/shared/condition.c b/src/shared/condition.c index 1d7dd49e04..6987cf7120 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -19,24 +19,26 @@ along with systemd; If not, see . ***/ -#include #include +#include +#include #include #include -#include #include "sd-id128.h" -#include "util.h" -#include "virt.h" -#include "path-util.h" -#include "architecture.h" -#include "smack-util.h" + #include "apparmor-util.h" -#include "ima-util.h" -#include "selinux-util.h" +#include "architecture.h" #include "audit.h" #include "cap-list.h" +#include "extract-word.h" #include "hostname-util.h" +#include "ima-util.h" +#include "path-util.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "util.h" +#include "virt.h" #include "condition.h" Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {