chiark / gitweb /
string-util: tweak cellescape() a bit
authorLennart Poettering <lennart@poettering.net>
Fri, 1 Jun 2018 19:45:23 +0000 (21:45 +0200)
committerSven Eden <yamakuzure@gmx.net>
Fri, 24 Aug 2018 14:47:08 +0000 (16:47 +0200)
For short buffer sizes cellescape() was a bit wasteful, as it might
suffice to to drop a single character to find enough place for the full
four byte ellipsis, if that one character was a four character escape.
With this rework we'll guarantee to drop the minimum number of
characters from the end to fit in the ellipsis.

If the buffers we write to are large this doesn't matter much. However,
if they are short (as they are when talking about the process comm
field) then it starts to matter that we put as much information as we
can in the space we get.

src/basic/string-util.c
src/test/test-string-util.c

index f241a33767cea049099290de8e6088700ad91be3..cb6428e8a825a4382252b0bb8c602b9a83ad0eba 100644 (file)
@@ -631,25 +631,58 @@ char *cellescape(char *buf, size_t len, const char *s) {
          * very end.
          */
 
-        size_t i;
-        const char *t = s;
+        size_t i = 0, last_char_width[4] = {}, k = 0, j;
+
+        assert(len > 0); /* at least a terminating NUL */
 
-        assert(len > 4 + 4 + 1); /* two chars and the terminator */
+        for (;;) {
+                char four[4];
+                int w;
 
-        for (i = 0; i < len - 9; t++) {
-                if (!*t)
+                if (*s == 0) /* terminating NUL detected? then we are done! */
                         goto done;
-                i += cescape_char(*t, buf + i);
+
+                w = cescape_char(*s, four);
+                if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's
+                                      * ellipsize at the previous location */
+                        break;
+
+                /* OK, there was space, let's add this escaped character to the buffer */
+                memcpy(buf + i, four, w);
+                i += w;
+
+                /* And remember its width in the ring buffer */
+                last_char_width[k] = w;
+                k = (k + 1) % 4;
+
+                s++;
         }
 
-        /* We have space for one more char and terminating nul at this point */
-        if (*t) {
-                if (*(t+1))
-                        i += write_ellipsis(buf + i, false);
-                else
-                        i += cescape_char(*t, buf + i);
+        /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4
+         * characters ideally, but the buffer is shorter than that in the first place take what we can get */
+        for (j = 0; j < ELEMENTSOF(last_char_width); j++) {
+
+                if (i + 4 <= len) /* nice, we reached our space goal */
+                        break;
+
+                k = k == 0 ? 3 : k - 1;
+                if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */
+                        break;
+
+                assert(i >= last_char_width[k]);
+                i -= last_char_width[k];
         }
 
+        if (i + 4 <= len) /* yay, enough space */
+                i += write_ellipsis(buf + i, false);
+        else if (i + 3 <= len) { /* only space for ".." */
+                buf[i++] = '.';
+                buf[i++] = '.';
+        } else if (i + 2 <= len) /* only space for a single "." */
+                buf[i++] = '.';
+        else
+                assert(i + 1 <= len);
+
  done:
         buf[i] = '\0';
         return buf;
index 8c7226e5f8f683a717539f604f290a455030c09c..4ff792c4c05c416335f8dcc20965e95b92daba6d 100644 (file)
@@ -83,21 +83,79 @@ static void test_ascii_strcasecmp_nn(void) {
 static void test_cellescape(void) {
         char buf[40];
 
-        assert_se(streq(cellescape(buf, 10, "1"), "1"));
-        assert_se(streq(cellescape(buf, 10, "12"), "12"));
-        assert_se(streq(cellescape(buf, 10, "123"), is_locale_utf8() ? "1…" : "1..."));
-
-        assert_se(streq(cellescape(buf, 10, "1\011"), "1\\t"));
-        assert_se(streq(cellescape(buf, 10, "1\020"), "1\\020"));
-        assert_se(streq(cellescape(buf, 10, "1\020x"), is_locale_utf8() ? "1…" : "1..."));
+        assert_se(streq(cellescape(buf, 1, ""), ""));
+        assert_se(streq(cellescape(buf, 1, "1"), ""));
+        assert_se(streq(cellescape(buf, 1, "12"), ""));
+
+        assert_se(streq(cellescape(buf, 2, ""), ""));
+        assert_se(streq(cellescape(buf, 2, "1"), "1"));
+        assert_se(streq(cellescape(buf, 2, "12"), "."));
+        assert_se(streq(cellescape(buf, 2, "123"), "."));
+
+        assert_se(streq(cellescape(buf, 3, ""), ""));
+        assert_se(streq(cellescape(buf, 3, "1"), "1"));
+        assert_se(streq(cellescape(buf, 3, "12"), "12"));
+        assert_se(streq(cellescape(buf, 3, "123"), ".."));
+        assert_se(streq(cellescape(buf, 3, "1234"), ".."));
+
+        assert_se(streq(cellescape(buf, 4, ""), ""));
+        assert_se(streq(cellescape(buf, 4, "1"), "1"));
+        assert_se(streq(cellescape(buf, 4, "12"), "12"));
+        assert_se(streq(cellescape(buf, 4, "123"), "123"));
+        assert_se(streq(cellescape(buf, 4, "1234"), is_locale_utf8() ? "…" : "..."));
+        assert_se(streq(cellescape(buf, 4, "12345"), is_locale_utf8() ? "…" : "..."));
+
+        assert_se(streq(cellescape(buf, 5, ""), ""));
+        assert_se(streq(cellescape(buf, 5, "1"), "1"));
+        assert_se(streq(cellescape(buf, 5, "12"), "12"));
+        assert_se(streq(cellescape(buf, 5, "123"), "123"));
+        assert_se(streq(cellescape(buf, 5, "1234"), "1234"));
+        assert_se(streq(cellescape(buf, 5, "12345"), is_locale_utf8() ? "1…" : "1..."));
+        assert_se(streq(cellescape(buf, 5, "123456"), is_locale_utf8() ? "1…" : "1..."));
+
+        assert_se(streq(cellescape(buf, 1, "\020"), ""));
+        assert_se(streq(cellescape(buf, 2, "\020"), "."));
+        assert_se(streq(cellescape(buf, 3, "\020"), ".."));
+        assert_se(streq(cellescape(buf, 4, "\020"), "…"));
+        assert_se(streq(cellescape(buf, 5, "\020"), "\\020"));
+
+        assert_se(streq(cellescape(buf, 5, "1234\020"), "1…"));
+        assert_se(streq(cellescape(buf, 6, "1234\020"), "12…"));
+        assert_se(streq(cellescape(buf, 7, "1234\020"), "123…"));
+        assert_se(streq(cellescape(buf, 8, "1234\020"), "1234…"));
+        assert_se(streq(cellescape(buf, 9, "1234\020"), "1234\\020"));
+
+        assert_se(streq(cellescape(buf, 1, "\t\n"), ""));
+        assert_se(streq(cellescape(buf, 2, "\t\n"), "."));
+        assert_se(streq(cellescape(buf, 3, "\t\n"), ".."));
+        assert_se(streq(cellescape(buf, 4, "\t\n"), "…"));
+        assert_se(streq(cellescape(buf, 5, "\t\n"), "\\t\\n"));
+
+        assert_se(streq(cellescape(buf, 5, "1234\t\n"), "1…"));
+        assert_se(streq(cellescape(buf, 6, "1234\t\n"), "12…"));
+        assert_se(streq(cellescape(buf, 7, "1234\t\n"), "123…"));
+        assert_se(streq(cellescape(buf, 8, "1234\t\n"), "1234…"));
+        assert_se(streq(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n"));
+
+        assert_se(streq(cellescape(buf, 4, "x\t\020\n"), "…"));
+        assert_se(streq(cellescape(buf, 5, "x\t\020\n"), "x…"));
+        assert_se(streq(cellescape(buf, 6, "x\t\020\n"), "x…"));
+        assert_se(streq(cellescape(buf, 7, "x\t\020\n"), "x\\t…"));
+        assert_se(streq(cellescape(buf, 8, "x\t\020\n"), "x\\t…"));
+        assert_se(streq(cellescape(buf, 9, "x\t\020\n"), "x\\t…"));
+        assert_se(streq(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n"));
+
+        assert_se(streq(cellescape(buf, 6, "1\011"), "1\\t"));
+        assert_se(streq(cellescape(buf, 6, "1\020"), "1\\020"));
+        assert_se(streq(cellescape(buf, 6, "1\020x"), is_locale_utf8() ? "1…" : "1..."));
 
         assert_se(streq(cellescape(buf, 40, "1\020"), "1\\020"));
         assert_se(streq(cellescape(buf, 40, "1\020x"), "1\\020x"));
 
         assert_se(streq(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'"));
-        assert_se(streq(cellescape(buf, 10, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."));
-        assert_se(streq(cellescape(buf, 11, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."));
-        assert_se(streq(cellescape(buf, 12, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b..."));
+        assert_se(streq(cellescape(buf, 6, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."));
+        assert_se(streq(cellescape(buf, 7, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."));
+        assert_se(streq(cellescape(buf, 8, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b..."));
 
         assert_se(streq(cellescape(buf, sizeof buf, "1\020"), "1\\020"));
         assert_se(streq(cellescape(buf, sizeof buf, "1\020x"), "1\\020x"));