Merge pull request #10152 from yuwata/udev-use-extract

udev: small cleanups
This commit is contained in:
Lennart Poettering 2018-10-05 17:11:43 +02:00 committed by GitHub
commit 083d27b654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 94 deletions

View File

@ -128,7 +128,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) {
}
/* Split a string into words. */
const char* split(const char **state, size_t *l, const char *separator, bool quoted) {
const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) {
const char *current;
current = *state;
@ -144,20 +144,24 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo
return NULL;
}
if (quoted && strchr("\'\"", *current)) {
if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) {
char quotechars[2] = {*current, '\0'};
*l = strcspn_escaped(current + 1, quotechars);
if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
(current[*l + 2] && !strchr(separator, current[*l + 2]))) {
/* right quote missing or garbage at the end */
if (flags & SPLIT_RELAX) {
*state = current + *l + 1 + (current[*l + 1] != '\0');
return current + 1;
}
*state = current;
return NULL;
}
*state = current++ + *l + 2;
} else if (quoted) {
} else if (flags & SPLIT_QUOTES) {
*l = strcspn_escaped(current, separator);
if (current[*l] && !strchr(separator, current[*l])) {
if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) {
/* unfinished escape */
*state = current;
return NULL;

View File

@ -81,16 +81,21 @@ char *endswith_no_case(const char *s, const char *postfix) _pure_;
char *first_word(const char *s, const char *word) _pure_;
const char* split(const char **state, size_t *l, const char *separator, bool quoted);
typedef enum SplitFlags {
SPLIT_QUOTES = 0x01 << 0,
SPLIT_RELAX = 0x01 << 1,
} SplitFlags;
const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags);
#define FOREACH_WORD(word, length, s, state) \
_FOREACH_WORD(word, length, s, WHITESPACE, false, state)
_FOREACH_WORD(word, length, s, WHITESPACE, 0, state)
#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
_FOREACH_WORD(word, length, s, separator, false, state)
_FOREACH_WORD(word, length, s, separator, 0, state)
#define _FOREACH_WORD(word, length, s, separator, quoted, state) \
for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted)))
#define _FOREACH_WORD(word, length, s, separator, flags, state) \
for ((state) = (s), (word) = split(&(state), &(length), (separator), (flags)); (word); (word) = split(&(state), &(length), (separator), (flags)))
char *strappend(const char *s, const char *suffix);
char *strnappend(const char *s, const char *suffix, size_t length);

View File

@ -245,7 +245,7 @@ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
return 0;
}
char **strv_split(const char *s, const char *separator) {
char **strv_split_full(const char *s, const char *separator, SplitFlags flags) {
const char *word, *state;
size_t l;
size_t n, i;
@ -253,12 +253,15 @@ char **strv_split(const char *s, const char *separator) {
assert(s);
if (!separator)
separator = WHITESPACE;
s += strspn(s, separator);
if (isempty(s))
return new0(char*, 1);
n = 0;
FOREACH_WORD_SEPARATOR(word, l, s, separator, state)
_FOREACH_WORD(word, l, s, separator, flags, state)
n++;
r = new(char*, n+1);
@ -266,7 +269,7 @@ char **strv_split(const char *s, const char *separator) {
return NULL;
i = 0;
FOREACH_WORD_SEPARATOR(word, l, s, separator, state) {
_FOREACH_WORD(word, l, s, separator, flags, state) {
r[i] = strndup(word, l);
if (!r[i]) {
strv_free(r);

View File

@ -9,6 +9,7 @@
#include "alloc-util.h"
#include "extract-word.h"
#include "macro.h"
#include "string-util.h"
#include "util.h"
char *strv_find(char **l, const char *name) _pure_;
@ -66,7 +67,10 @@ static inline bool strv_isempty(char * const *l) {
return !l || !*l;
}
char **strv_split(const char *s, const char *separator);
char **strv_split_full(const char *s, const char *separator, SplitFlags flags);
static inline char **strv_split(const char *s, const char *separator) {
return strv_split_full(s, separator, 0);
}
char **strv_split_newlines(const char *s);
int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags);

View File

@ -632,6 +632,18 @@ tests += [
libacl],
'', 'manual', '-DLOG_REALM=LOG_REALM_UDEV'],
[['src/test/test-udev-build-argv.c'],
[libudev_core,
libudev_static,
libsystemd_network,
libshared],
[threads,
librt,
libblkid,
libkmod,
libacl],
'', '', '-DLOG_REALM=LOG_REALM_UDEV'],
[['src/test/test-id128.c'],
[],
[]],

View File

@ -63,6 +63,13 @@ static const char* const input_table_multiple[] = {
NULL,
};
static const char* const input_table_quoted[] = {
"one",
" two\t three ",
" four five",
NULL,
};
static const char* const input_table_one[] = {
"one",
NULL,
@ -206,23 +213,64 @@ static void test_invalid_unquote(const char *quoted) {
}
static void test_strv_split(void) {
char **s;
unsigned i = 0;
_cleanup_strv_free_ char **l = NULL;
const char str[] = "one,two,three";
l = strv_split(str, ",");
assert_se(l);
STRV_FOREACH(s, l)
assert_se(streq(*s, input_table_multiple[i++]));
assert_se(strv_equal(l, (char**) input_table_multiple));
i = 0;
strv_free(l);
l = strv_split(" one two\t three", WHITESPACE);
assert_se(l);
STRV_FOREACH(s, l)
assert_se(streq(*s, input_table_multiple[i++]));
assert_se(strv_equal(l, (char**) input_table_multiple));
strv_free(l);
/* Setting NULL for separator is equivalent to WHITESPACE */
l = strv_split(" one two\t three", NULL);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_multiple));
strv_free(l);
l = strv_split_full(" one two\t three", NULL, 0);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_multiple));
strv_free(l);
l = strv_split_full(" 'one' \" two\t three \" ' four five'", NULL, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_quoted));
strv_free(l);
/* missing last quote ignores the last element. */
l = strv_split_full(" 'one' \" two\t three \" ' four five' ' ignored element ", NULL, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_quoted));
strv_free(l);
/* missing last quote, but the last element is _not_ ignored with SPLIT_RELAX. */
l = strv_split_full(" 'one' \" two\t three \" ' four five", NULL, SPLIT_QUOTES | SPLIT_RELAX);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_quoted));
strv_free(l);
/* missing separator between */
l = strv_split_full(" 'one' \" two\t three \"' four five'", NULL, SPLIT_QUOTES | SPLIT_RELAX);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_quoted));
strv_free(l);
l = strv_split_full(" 'one' \" two\t three \"' four five", NULL, SPLIT_QUOTES | SPLIT_RELAX);
assert_se(l);
assert_se(strv_equal(l, (char**) input_table_quoted));
}
static void test_strv_split_empty(void) {
@ -232,11 +280,60 @@ static void test_strv_split_empty(void) {
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split("", NULL);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full("", NULL, 0);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full("", NULL, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full("", WHITESPACE, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full("", WHITESPACE, SPLIT_QUOTES | SPLIT_RELAX);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split(" ", WHITESPACE);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split(" ", NULL);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full(" ", NULL, 0);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full(" ", WHITESPACE, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full(" ", NULL, SPLIT_QUOTES);
assert_se(l);
assert_se(strv_isempty(l));
strv_free(l);
l = strv_split_full(" ", NULL, SPLIT_QUOTES | SPLIT_RELAX);
assert_se(l);
assert_se(strv_isempty(l));
}
static void test_strv_split_extract(void) {

View File

@ -0,0 +1,78 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
#include "udev.h"
static void test_udev_build_argv_one(const char *c) {
_cleanup_strv_free_ char **a = NULL;
_cleanup_free_ char *arg = NULL;
char *argv[128], **p;
int argc;
size_t i;
assert_se(a = strv_split_full(c, NULL, SPLIT_QUOTES | SPLIT_RELAX));
assert_se(arg = strdup(c));
assert_se(udev_build_argv(arg, &argc, argv) >= 0);
log_info("command: %s", c);
i = 0;
log_info("strv_split:");
STRV_FOREACH(p, a)
log_info("argv[%zu] = '%s'", i++, *p);
i = 0;
log_info("udev_build_argv:");
STRV_FOREACH(p, argv)
log_info("argv[%zu] = '%s'", i++, *p);
assert_se(strv_equal(argv, a));
assert_se(argc == (int) strv_length(a));
}
static void test_udev_build_argv(void) {
test_udev_build_argv_one("one two three");
test_udev_build_argv_one("one 'two three ' \" four five \" 'aaa bbb ");
test_udev_build_argv_one("/bin/echo -e \\101");
test_udev_build_argv_one("/bin/echo -n special-device");
test_udev_build_argv_one("/bin/echo -n special-device");
test_udev_build_argv_one("/bin/echo test");
test_udev_build_argv_one("/bin/echo -n test-%b");
test_udev_build_argv_one("/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9");
test_udev_build_argv_one("/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'");
test_udev_build_argv_one("/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'");
test_udev_build_argv_one("/bin/sh -c 'printf %%s \\\"foo1 foo2\\\" | grep \\\"foo1 foo2\\\"'");
test_udev_build_argv_one("/bin/sh -c \\\"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\\\"");
test_udev_build_argv_one("/bin/sh -c 'printf \\\"%%s %%s\\\" \\\"foo1 foo2\\\" \\\"foo3\\\"| grep \\\"foo1 foo2\\\"'");
test_udev_build_argv_one("/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9");
test_udev_build_argv_one("/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9");
test_udev_build_argv_one("/bin/echo -n foo");
test_udev_build_argv_one("/bin/echo -n usb-%b");
test_udev_build_argv_one("/bin/echo -n scsi-%b");
test_udev_build_argv_one("/bin/echo -n foo-%b");
test_udev_build_argv_one("/bin/echo test");
test_udev_build_argv_one("/bin/echo symlink test this");
test_udev_build_argv_one("/bin/echo symlink test this");
test_udev_build_argv_one("/bin/echo link test this");
test_udev_build_argv_one("/bin/echo -n node link1 link2");
test_udev_build_argv_one("/bin/echo -n node link1 link2 link3 link4");
test_udev_build_argv_one("/usr/bin/test -b %N");
test_udev_build_argv_one("/bin/echo -e name; (/usr/bin/badprogram)");
test_udev_build_argv_one("/bin/echo -e \\xc3\\xbcber");
test_udev_build_argv_one("/bin/echo -e \\xef\\xe8garbage");
test_udev_build_argv_one("/bin/echo 1 1 0400");
test_udev_build_argv_one("/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890");
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
test_udev_build_argv();
return 0;
}

View File

@ -102,18 +102,18 @@ enum udev_builtin_cmd udev_builtin_lookup(const char *command) {
}
int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test) {
char arg[UTIL_PATH_SIZE];
int argc;
char *argv[128];
_cleanup_strv_free_ char **argv = NULL;
if (!builtins[cmd])
return -EOPNOTSUPP;
argv = strv_split_full(command, NULL, SPLIT_QUOTES | SPLIT_RELAX);
if (!argv)
return -ENOMEM;
/* we need '0' here to reset the internal state */
optind = 0;
strscpy(arg, sizeof(arg), command);
udev_build_argv(arg, &argc, argv);
return builtins[cmd]->cmd(dev, argc, argv, test);
return builtins[cmd]->cmd(dev, strv_length(argv), argv, test);
}
int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val) {

View File

@ -19,6 +19,7 @@
#include "fd-util.h"
#include "format-util.h"
#include "netlink-util.h"
#include "path-util.h"
#include "process-util.h"
#include "signal-util.h"
#include "string-util.h"
@ -737,47 +738,44 @@ int udev_event_spawn(struct udev_event *event,
bool accept_failure,
const char *cmd,
char *result, size_t ressize) {
int outpipe[2] = {-1, -1};
int errpipe[2] = {-1, -1};
_cleanup_close_pair_ int outpipe[2] = {-1, -1}, errpipe[2] = {-1, -1};
_cleanup_strv_free_ char **argv = NULL;
pid_t pid;
int err = 0;
int r;
/* pipes from child to parent */
if (result != NULL || log_get_max_level() >= LOG_INFO) {
if (pipe2(outpipe, O_NONBLOCK) != 0) {
err = log_error_errno(errno, "pipe failed: %m");
goto out;
}
}
if (log_get_max_level() >= LOG_INFO) {
if (pipe2(errpipe, O_NONBLOCK) != 0) {
err = log_error_errno(errno, "pipe failed: %m");
goto out;
}
if (!result || log_get_max_level() >= LOG_INFO)
if (pipe2(outpipe, O_NONBLOCK) != 0)
return log_error_errno(errno, "Failed to create pipe for command '%s': %m", cmd);
if (log_get_max_level() >= LOG_INFO)
if (pipe2(errpipe, O_NONBLOCK) != 0)
return log_error_errno(errno, "Failed to create pipe for command '%s': %m", cmd);
argv = strv_split_full(cmd, NULL, SPLIT_QUOTES|SPLIT_RELAX);
if (!argv)
return log_oom();
/* allow programs in /usr/lib/udev/ to be called without the path */
if (!path_is_absolute(argv[0])) {
char *program;
program = path_join(NULL, UDEVLIBEXECDIR, argv[0]);
if (!program)
return log_oom();
free_and_replace(argv[0], program);
}
err = safe_fork("(spawn)", FORK_RESET_SIGNALS|FORK_LOG, &pid);
if (err < 0)
goto out;
if (err == 0) {
char arg[UTIL_PATH_SIZE];
char *argv[128];
char program[UTIL_PATH_SIZE];
r = safe_fork("(spawn)", FORK_RESET_SIGNALS|FORK_LOG, &pid);
if (r < 0)
return log_error_errno(r, "Failed to fork() to execute command '%s': %m", cmd);
if (r == 0) {
/* child closes parent's ends of pipes */
outpipe[READ_END] = safe_close(outpipe[READ_END]);
errpipe[READ_END] = safe_close(errpipe[READ_END]);
strscpy(arg, sizeof(arg), cmd);
udev_build_argv(arg, NULL, argv);
/* allow programs in /usr/lib/udev/ to be called without the path */
if (argv[0][0] != '/') {
strscpyl(program, sizeof(program), UDEVLIBEXECDIR "/", argv[0], NULL);
argv[0] = program;
}
log_debug("starting '%s'", cmd);
log_debug("Starting '%s'", cmd);
spawn_exec(event, cmd, argv, udev_device_get_properties_envp(event->dev),
outpipe[WRITE_END], errpipe[WRITE_END]);
@ -795,18 +793,11 @@ int udev_event_spawn(struct udev_event *event,
outpipe[READ_END], errpipe[READ_END],
result, ressize);
err = spawn_wait(event, timeout_usec, timeout_warn_usec, cmd, pid, accept_failure);
r = spawn_wait(event, timeout_usec, timeout_warn_usec, cmd, pid, accept_failure);
if (r < 0)
return log_error_errno(r, "Failed to wait spawned command '%s': %m", cmd);
out:
if (outpipe[READ_END] >= 0)
close(outpipe[READ_END]);
if (outpipe[WRITE_END] >= 0)
close(outpipe[WRITE_END]);
if (errpipe[READ_END] >= 0)
close(errpipe[READ_END]);
if (errpipe[WRITE_END] >= 0)
close(errpipe[WRITE_END]);
return err;
return r;
}
static int rename_netif(struct udev_event *event) {

View File

@ -20,34 +20,19 @@
#include "udev.h"
static int node_symlink(struct udev_device *dev, const char *node, const char *slink) {
struct stat stats;
char target[UTIL_PATH_SIZE];
char *s;
size_t l;
_cleanup_free_ char *slink_dirname = NULL, *target = NULL;
char slink_tmp[UTIL_PATH_SIZE + 32];
int i = 0;
int tail = 0;
int err = 0;
struct stat stats;
int r, err = 0;
slink_dirname = dirname_malloc(slink);
if (!slink_dirname)
return log_oom();
/* use relative link */
target[0] = '\0';
while (node[i] && (node[i] == slink[i])) {
if (node[i] == '/')
tail = i+1;
i++;
}
s = target;
l = sizeof(target);
while (slink[i] != '\0') {
if (slink[i] == '/')
l = strpcpy(&s, l, "../");
i++;
}
l = strscpy(s, l, &node[tail]);
if (l == 0) {
err = -EINVAL;
goto exit;
}
r = path_make_relative(slink_dirname, node, &target);
if (r < 0)
return log_error_errno(r, "Failed to get relative path from '%s' to '%s': %m", slink, node);
/* preserve link with correct target, do not replace node of other device */
if (lstat(slink, &stats) == 0) {