From: dongshengyuan <545258830@qq.com> Date: Fri, 24 Jul 2026 06:39:35 +0000 (+0800) Subject: escape: add --stdin input mode X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=47536f5aac0a8cfd4d41cba91e99b399fdb96b00;p=thirdparty%2Fsystemd.git escape: add --stdin input mode systemd-escape currently only processes strings passed as command line arguments. This is awkward for callers that already have a generated list of strings, because they need to loop around the tool or use xargs and carefully preserve whitespace and other special characters. Add --stdin to read one string per line from standard input and write one escaped result per output line. Keep command line strings mutually exclusive with --stdin so the input source remains unambiguous. Use an explicit option instead of treating '-' specially, since '-' is itself a valid string to escape. The existing escape, unescape, mangle, path, suffix, and template rules are reused unchanged. --- diff --git a/man/systemd-escape.xml b/man/systemd-escape.xml index df3511536bf..7a0fd062cf7 100644 --- a/man/systemd-escape.xml +++ b/man/systemd-escape.xml @@ -38,7 +38,9 @@ The command takes any number of strings on the command line, and will process them individually, one after another. It will - output them separated by spaces to stdout. + output them separated by spaces to stdout. If is specified, strings are + instead read from standard input, one per line, and the result for each input line is written on a + separate output line. By default, this command will escape the strings passed, unless is passed which results in the @@ -86,6 +88,16 @@ + + + + Read strings from standard input, one per line, instead of from the command line. + The line ending is not included in the string to escape or unescape, and the result for each line + is written on a separate output line. This option may not be combined with command line strings. + + + + @@ -170,6 +182,11 @@ tmp-waldi-foobar.mount $ systemd-escape --template=systemd-nspawn@.service 'My Container 1' 'containerb' 'container/III' systemd-nspawn@My\x20Container\x201.service systemd-nspawn@containerb.service systemd-nspawn@container-III.service + To generate instance names from standard input: + $ printf '%s\n' 'My Container 1' 'container/III' | systemd-escape --stdin --template=systemd-nspawn@.service +systemd-nspawn@My\x20Container\x201.service +systemd-nspawn@container-III.service + To extract the instance part of an instantiated unit: $ systemd-escape -u --instance 'systemd-nspawn@My\x20Container\x201.service' My Container 1 diff --git a/src/escape/escape-tool.c b/src/escape/escape-tool.c index 09e0338c348..faeeb02dd82 100644 --- a/src/escape/escape-tool.c +++ b/src/escape/escape-tool.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "build.h" +#include "fileio.h" #include "format-table.h" #include "log.h" #include "main-func.h" @@ -24,6 +25,7 @@ static const char *arg_suffix = NULL; static const char *arg_template = NULL; static bool arg_path = false; static bool arg_instance = false; +static bool arg_stdin = false; static int help(void) { _cleanup_free_ char *link = NULL; @@ -84,6 +86,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_template = opts.arg; break; + OPTION_LONG("stdin", NULL, "Read strings from standard input"): + arg_stdin = true; + break; + OPTION_LONG("instance", NULL, "With --unescape, show just the instance part"): arg_instance = true; break; @@ -102,7 +108,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - if (option_parser_get_n_args(&opts) == 0) + if (arg_stdin && option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--stdin may not be combined with arguments."); + + if (!arg_stdin && option_parser_get_n_args(&opts) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough arguments."); @@ -134,6 +144,115 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return 1; } +static int process_one(const char *input, char **ret) { + _cleanup_free_ char *e = NULL; + int r; + + assert(input); + assert(ret); + + switch (arg_action) { + + case ACTION_ESCAPE: + if (arg_path) { + r = unit_name_path_escape(input, &e); + if (r == -EINVAL) { + /* If escaping failed because the string was invalid, let's print a + * friendly message about it. Catch these specific error cases + * explicitly. */ + + if (!path_is_valid(input)) + return log_error_errno(r, "Input '%s' is not a valid file system path, failed to escape.", input); + if (!path_is_absolute(input)) + return log_error_errno(r, "Input '%s' is not an absolute file system path, failed to escape.", input); + if (!path_is_normalized(input)) + return log_error_errno(r, "Input '%s' is not a normalized file system path, failed to escape.", input); + } + if (r < 0) + /* All other error cases. */ + return log_error_errno(r, "Failed to escape string: %m"); + + /* If the escaping worked, then still warn if the path is not like we'd like + * it. Because that means escaping is not necessarily reversible. */ + + if (!path_is_valid(input)) + log_warning("Input '%s' is not a valid file system path, escaping is likely not going to be reversible.", input); + else if (!path_is_absolute(input)) + log_warning("Input '%s' is not an absolute file system path, escaping is likely not going to be reversible.", input); + + /* Note that we don't complain about paths not being normalized here, because + * some forms of non-normalization is actually OK, such as a series // and + * unit_name_path_escape() will clean those up silently, and the reversal is + * "close enough" to be OK. */ + } else { + e = unit_name_escape(input); + if (!e) + return log_oom(); + } + + if (arg_template) { + char *x; + + r = unit_name_replace_instance(arg_template, e, &x); + if (r < 0) + return log_error_errno(r, "Failed to replace instance: %m"); + + free_and_replace(e, x); + } else if (arg_suffix) { + if (!strextend(&e, ".", arg_suffix)) + return log_oom(); + } + + break; + + case ACTION_UNESCAPE: { + _cleanup_free_ char *name = NULL; + + if (arg_template || arg_instance) { + _cleanup_free_ char *template = NULL; + + r = unit_name_to_instance(input, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract instance: %m"); + if (isempty(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unit %s is missing the instance name.", input); + + r = unit_name_template(input, &template); + if (r < 0) + return log_error_errno(r, "Failed to extract template: %m"); + if (arg_template && !streq(arg_template, template)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unit %s template %s does not match specified template %s.", + input, template, arg_template); + } else { + name = strdup(input); + if (!name) + return log_oom(); + } + + if (arg_path) + r = unit_name_path_unescape(name, &e); + else + r = unit_name_unescape(name, &e); + if (r < 0) + return log_error_errno(r, "Failed to unescape string: %m"); + + break; + } + + case ACTION_MANGLE: + r = unit_name_mangle(input, 0, &e); + if (r < 0) + return log_error_errno(r, "Failed to mangle name: %m"); + + break; + } + + *ret = TAKE_PTR(e); + return 0; +} + static int run(int argc, char *argv[]) { int r; @@ -144,106 +263,32 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - STRV_FOREACH(i, args) { - _cleanup_free_ char *e = NULL; - - switch (arg_action) { - - case ACTION_ESCAPE: - if (arg_path) { - r = unit_name_path_escape(*i, &e); - if (r == -EINVAL) { - /* If escaping failed because the string was invalid, let's print a - * friendly message about it. Catch these specific error cases - * explicitly. */ - - if (!path_is_valid(*i)) - return log_error_errno(r, "Input '%s' is not a valid file system path, failed to escape.", *i); - if (!path_is_absolute(*i)) - return log_error_errno(r, "Input '%s' is not an absolute file system path, failed to escape.", *i); - if (!path_is_normalized(*i)) - return log_error_errno(r, "Input '%s' is not a normalized file system path, failed to escape.", *i); - } - if (r < 0) - /* All other error cases. */ - return log_error_errno(r, "Failed to escape string: %m"); - - /* If the escaping worked, then still warn if the path is not like we'd like - * it. Because that means escaping is not necessarily reversible. */ - - if (!path_is_valid(*i)) - log_warning("Input '%s' is not a valid file system path, escaping is likely not going to be reversible.", *i); - else if (!path_is_absolute(*i)) - log_warning("Input '%s' is not an absolute file system path, escaping is likely not going to be reversible.", *i); - - /* Note that we don't complain about paths not being normalized here, because - * some forms of non-normalization is actually OK, such as a series // and - * unit_name_path_escape() will clean those up silently, and the reversal is - * "close enough" to be OK. */ - } else { - e = unit_name_escape(*i); - if (!e) - return log_oom(); - } - - if (arg_template) { - char *x; - - r = unit_name_replace_instance(arg_template, e, &x); - if (r < 0) - return log_error_errno(r, "Failed to replace instance: %m"); + if (arg_stdin) { + for (;;) { + _cleanup_free_ char *e = NULL, *line = NULL; - free_and_replace(e, x); - } else if (arg_suffix) { - if (!strextend(&e, ".", arg_suffix)) - return log_oom(); - } - - break; - - case ACTION_UNESCAPE: { - _cleanup_free_ char *name = NULL; - - if (arg_template || arg_instance) { - _cleanup_free_ char *template = NULL; - - r = unit_name_to_instance(*i, &name); - if (r < 0) - return log_error_errno(r, "Failed to extract instance: %m"); - if (isempty(name)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unit %s is missing the instance name.", *i); - - r = unit_name_template(*i, &template); - if (r < 0) - return log_error_errno(r, "Failed to extract template: %m"); - if (arg_template && !streq(arg_template, template)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unit %s template %s does not match specified template %s.", - *i, template, arg_template); - } else { - name = strdup(*i); - if (!name) - return log_oom(); - } + r = read_line(stdin, LONG_LINE_MAX, &line); + if (r == -ENOBUFS) + return log_error_errno(r, "Input line from standard input is too long, refusing."); + if (r < 0) + return log_error_errno(r, "Failed to read from standard input: %m"); + if (r == 0) + return 0; - if (arg_path) - r = unit_name_path_unescape(name, &e); - else - r = unit_name_unescape(name, &e); + r = process_one(line, &e); if (r < 0) - return log_error_errno(r, "Failed to unescape string: %m"); + return r; - break; + puts(e); } + } - case ACTION_MANGLE: - r = unit_name_mangle(*i, 0, &e); - if (r < 0) - return log_error_errno(r, "Failed to mangle name: %m"); + STRV_FOREACH(i, args) { + _cleanup_free_ char *e = NULL; - break; - } + r = process_one(*i, &e); + if (r < 0) + return r; if (i != args) fputc(' ', stdout); diff --git a/test/units/TEST-74-AUX-UTILS.escape.sh b/test/units/TEST-74-AUX-UTILS.escape.sh index e398d4010b6..90ef77af42c 100755 --- a/test/units/TEST-74-AUX-UTILS.escape.sh +++ b/test/units/TEST-74-AUX-UTILS.escape.sh @@ -35,6 +35,31 @@ assert_eq "$(systemd-escape 'hello-world' '/dev/loop1' 'template@🐍')" \ assert_eq "$(systemd-escape --unescape -- 'hello\x2dworld' '-dev-loop1' 'template\x40\xf0\x9f\x90\x8d')" \ 'hello-world /dev/loop1 template@🐍' +# Multiple strings from stdin to escape/unescape +assert_eq "$(systemd-escape --stdin <