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.
This commit is contained in:
+1
-250
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user