1ff636
From 32b4631e74262e02094c6402d39d1e4264442037 Mon Sep 17 00:00:00 2001
1ff636
From: Lennart Poettering <lennart@poettering.net>
1ff636
Date: Thu, 9 Apr 2015 18:32:21 +0200
1ff636
Subject: [PATCH] util: add shell_maybe_quote() call for preparing a string for
1ff636
 shell cmdline inclusion
1ff636
1ff636
If necessary the passed string is enclosed in "", and all special
1ff636
characters escapes.
1ff636
1ff636
This also ports over usage in bus-util.c and job.c to use this, instead
1ff636
of a incorrect local implementation that forgets to properly escape.
1ff636
1ff636
Conflicts:
1ff636
	src/shared/util.c
1ff636
	src/shared/util.h
1ff636
1ff636
Cherry-picked from: 019c7fba
1ff636
Resolves: #1016680
1ff636
---
1ff636
 src/core/job.c                   |  8 +++----
1ff636
 src/libsystemd/sd-bus/bus-util.c | 15 +++++++-------
1ff636
 src/shared/util.c                | 45 ++++++++++++++++++++++++++++++++++++++--
1ff636
 src/shared/util.h                |  2 ++
1ff636
 src/test/test-util.c             | 19 +++++++++++++++++
1ff636
 5 files changed, 74 insertions(+), 15 deletions(-)
1ff636
1ff636
diff --git a/src/core/job.c b/src/core/job.c
1ff636
index 4740ff1..7416386 100644
1ff636
--- a/src/core/job.c
1ff636
+++ b/src/core/job.c
1ff636
@@ -679,15 +679,13 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
1ff636
                         break;
1ff636
 
1ff636
                 case JOB_FAILED: {
1ff636
-                        bool quotes;
1ff636
+                        _cleanup_free_ char *quoted = NULL;
1ff636
 
1ff636
-                        quotes = chars_intersect(u->id, SHELL_NEED_QUOTES);
1ff636
+                        quoted = shell_maybe_quote(u->id);
1ff636
 
1ff636
                         manager_flip_auto_status(u->manager, true);
1ff636
                         unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format);
1ff636
-                        manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL,
1ff636
-                                              "See \"systemctl status %s%s%s\" for details.",
1ff636
-                                              quotes ? "'" : "", u->id, quotes ? "'" : "");
1ff636
+                        manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted));
1ff636
                         break;
1ff636
                 }
1ff636
 
1ff636
diff --git a/src/libsystemd/sd-bus/bus-util.c b/src/libsystemd/sd-bus/bus-util.c
1ff636
index 52d4ebe..b0a5a75 100644
1ff636
--- a/src/libsystemd/sd-bus/bus-util.c
1ff636
+++ b/src/libsystemd/sd-bus/bus-util.c
1ff636
@@ -1709,16 +1709,15 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) {
1ff636
                 else if (streq(d->result, "unsupported"))
1ff636
                         log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
1ff636
                 else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
1ff636
-                        if (d->name) {
1ff636
-                                bool quotes;
1ff636
+                        _cleanup_free_ char *quoted = NULL;
1ff636
 
1ff636
-                                quotes = chars_intersect(d->name, SHELL_NEED_QUOTES);
1ff636
+                        if (d->name)
1ff636
+                                quoted = shell_maybe_quote(d->name);
1ff636
 
1ff636
-                                log_error("Job for %s failed. See \"systemctl status %s%s%s\" and \"journalctl -xe\" for details.",
1ff636
-                                          d->name,
1ff636
-                                          quotes ? "'" : "", d->name, quotes ? "'" : "");
1ff636
-                        } else
1ff636
-                                log_error("Job failed. See \"journalctl -xe\" for details.");
1ff636
+                        if (quoted)
1ff636
+                                log_error("Job for %s failed. See 'systemctl status %s' and 'journalctl -xe' for details.", d->name, quoted);
1ff636
+                        else
1ff636
+                                log_error("Job failed. See 'journalctl -xe' for details.");
1ff636
                 }
1ff636
         }
1ff636
 
1ff636
diff --git a/src/shared/util.c b/src/shared/util.c
1ff636
index 649344d..778c2b0 100644
1ff636
--- a/src/shared/util.c
1ff636
+++ b/src/shared/util.c
1ff636
@@ -1329,7 +1329,8 @@ char *cescape(const char *s) {
1ff636
 
1ff636
         assert(s);
1ff636
 
1ff636
-        /* Does C style string escaping. */
1ff636
+        /* Does C style string escaping. May be be reversed with
1ff636
+         * cunescape(). */
1ff636
 
1ff636
         r = new(char, strlen(s)*4 + 1);
1ff636
         if (!r)
1ff636
@@ -1493,7 +1494,7 @@ char *xescape(const char *s, const char *bad) {
1ff636
 
1ff636
         /* Escapes all chars in bad, in addition to \ and all special
1ff636
          * chars, in \xFF style escaping. May be reversed with
1ff636
-         * cunescape. */
1ff636
+         * cunescape(). */
1ff636
 
1ff636
         r = new(char, strlen(s) * 4 + 1);
1ff636
         if (!r)
1ff636
@@ -8101,3 +8102,43 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
1ff636
 
1ff636
         return -1;
1ff636
 }
1ff636
+
1ff636
+char *shell_maybe_quote(const char *s) {
1ff636
+        const char *p;
1ff636
+        char *r, *t;
1ff636
+
1ff636
+        assert(s);
1ff636
+
1ff636
+        /* Encloses a string in double quotes if necessary to make it
1ff636
+         * OK as shell string. */
1ff636
+
1ff636
+        for (p = s; *p; p++)
1ff636
+                if (*p <= ' ' ||
1ff636
+                    *p >= 127 ||
1ff636
+                    strchr(SHELL_NEED_QUOTES, *p))
1ff636
+                        break;
1ff636
+
1ff636
+        if (!*p)
1ff636
+                return strdup(s);
1ff636
+
1ff636
+        r = new(char, 1+strlen(s)*2+1+1);
1ff636
+        if (!r)
1ff636
+                return NULL;
1ff636
+
1ff636
+        t = r;
1ff636
+        *(t++) = '"';
1ff636
+        t = mempcpy(t, s, p - s);
1ff636
+
1ff636
+        for (; *p; p++) {
1ff636
+
1ff636
+                if (strchr(SHELL_NEED_ESCAPE, *p))
1ff636
+                        *(t++) = '\\';
1ff636
+
1ff636
+                *(t++) = *p;
1ff636
+        }
1ff636
+
1ff636
+        *(t++)= '"';
1ff636
+        *t = 0;
1ff636
+
1ff636
+        return r;
1ff636
+}
1ff636
diff --git a/src/shared/util.h b/src/shared/util.h
1ff636
index a83b588..7ecfd85 100644
1ff636
--- a/src/shared/util.h
1ff636
+++ b/src/shared/util.h
1ff636
@@ -1078,3 +1078,5 @@ void sigkill_wait(pid_t *pid);
1ff636
 #define _cleanup_sigkill_wait_ _cleanup_(sigkill_wait)
1ff636
 
1ff636
 int syslog_parse_priority(const char **p, int *priority, bool with_facility);
1ff636
+
1ff636
+char *shell_maybe_quote(const char *s);
1ff636
diff --git a/src/test/test-util.c b/src/test/test-util.c
1ff636
index 9515a8c..9ae347b 100644
1ff636
--- a/src/test/test-util.c
1ff636
+++ b/src/test/test-util.c
1ff636
@@ -1512,6 +1512,24 @@ static void test_sparse_write(void) {
1ff636
         test_sparse_write_one(fd, test_e, sizeof(test_e));
1ff636
 }
1ff636
 
1ff636
+static void test_shell_maybe_quote_one(const char *s, const char *expected) {
1ff636
+        _cleanup_free_ char *r;
1ff636
+
1ff636
+        assert_se(r = shell_maybe_quote(s));
1ff636
+        assert_se(streq(r, expected));
1ff636
+}
1ff636
+
1ff636
+static void test_shell_maybe_quote(void) {
1ff636
+
1ff636
+        test_shell_maybe_quote_one("", "");
1ff636
+        test_shell_maybe_quote_one("\\", "\"\\\\\"");
1ff636
+        test_shell_maybe_quote_one("\"", "\"\\\"\"");
1ff636
+        test_shell_maybe_quote_one("foobar", "foobar");
1ff636
+        test_shell_maybe_quote_one("foo bar", "\"foo bar\"");
1ff636
+        test_shell_maybe_quote_one("foo \"bar\" waldo", "\"foo \\\"bar\\\" waldo\"");
1ff636
+        test_shell_maybe_quote_one("foo$bar", "\"foo\\$bar\"");
1ff636
+}
1ff636
+
1ff636
 int main(int argc, char *argv[]) {
1ff636
         log_parse_environment();
1ff636
         log_open();
1ff636
@@ -1589,6 +1607,7 @@ int main(int argc, char *argv[]) {
1ff636
         test_same_fd();
1ff636
         test_uid_ptr();
1ff636
         test_sparse_write();
1ff636
+        test_shell_maybe_quote();
1ff636
 
1ff636
         return 0;
1ff636
 }