• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 27728979565

17 Jun 2026 12:11PM UTC coverage: 72.586% (-0.4%) from 73.001%
27728979565

push

github

web-flow
mkosi: fix suse build (#42637)

No longer exists since latest spec changes, as the binaries have moved
to another package so it doesn't get autogenerated anymore

Follow-up for db9d7265f

335884 of 462738 relevant lines covered (72.59%)

1397781.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.42
/src/basic/escape.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdlib.h>
4
#include <string.h>
5

6
#include "alloc-util.h"
7
#include "escape.h"
8
#include "hexdecoct.h"
9
#include "string-util.h"
10
#include "strv.h"
11
#include "utf8.h"
12

13
int cescape_char(char c, char *buf) {
1,614,108✔
14
        char *buf_old = buf;
1,614,108✔
15

16
        /* Needs space for 4 characters in the buffer */
17

18
        switch (c) {
1,614,108✔
19

20
                case '\a':
7✔
21
                        *(buf++) = '\\';
7✔
22
                        *(buf++) = 'a';
7✔
23
                        break;
7✔
24
                case '\b':
9✔
25
                        *(buf++) = '\\';
9✔
26
                        *(buf++) = 'b';
9✔
27
                        break;
9✔
28
                case '\f':
7✔
29
                        *(buf++) = '\\';
7✔
30
                        *(buf++) = 'f';
7✔
31
                        break;
7✔
32
                case '\n':
16,566✔
33
                        *(buf++) = '\\';
16,566✔
34
                        *(buf++) = 'n';
16,566✔
35
                        break;
16,566✔
36
                case '\r':
8✔
37
                        *(buf++) = '\\';
8✔
38
                        *(buf++) = 'r';
8✔
39
                        break;
8✔
40
                case '\t':
53✔
41
                        *(buf++) = '\\';
53✔
42
                        *(buf++) = 't';
53✔
43
                        break;
53✔
44
                case '\v':
2✔
45
                        *(buf++) = '\\';
2✔
46
                        *(buf++) = 'v';
2✔
47
                        break;
2✔
48
                case '\\':
109✔
49
                        *(buf++) = '\\';
109✔
50
                        *(buf++) = '\\';
109✔
51
                        break;
109✔
52
                case '"':
3,604✔
53
                        *(buf++) = '\\';
3,604✔
54
                        *(buf++) = '"';
3,604✔
55
                        break;
3,604✔
56
                case '\'':
19✔
57
                        *(buf++) = '\\';
19✔
58
                        *(buf++) = '\'';
19✔
59
                        break;
19✔
60

61
                default:
1,593,724✔
62
                        /* For special chars we prefer octal over
63
                         * hexadecimal encoding, simply because glib's
64
                         * g_strescape() does the same */
65
                        if ((c < ' ') || (c >= 127)) {
1,593,724✔
66
                                *(buf++) = '\\';
11,903✔
67
                                *(buf++) = octchar((unsigned char) c >> 6);
11,903✔
68
                                *(buf++) = octchar((unsigned char) c >> 3);
11,903✔
69
                                *(buf++) = octchar((unsigned char) c);
11,903✔
70
                        } else
71
                                *(buf++) = c;
1,581,821✔
72
                        break;
73
        }
74

75
        return buf - buf_old;
1,614,108✔
76
}
77

78
char* cescape_length(const char *s, size_t n) {
40,993✔
79
        const char *f;
40,993✔
80
        char *r, *t;
40,993✔
81

82
        /* Does C style string escaping. May be reversed with cunescape(). */
83

84
        assert(s || n == 0);
40,993✔
85

86
        if (n == SIZE_MAX)
40,993✔
87
                n = strlen(s);
25,781✔
88

89
        if (n > (SIZE_MAX - 1) / 4)
40,993✔
90
                return NULL;
91

92
        r = new(char, n*4 + 1);
40,993✔
93
        if (!r)
40,993✔
94
                return NULL;
95

96
        for (f = s, t = r; f < s + n; f++)
1,070,426✔
97
                t += cescape_char(*f, t);
1,029,433✔
98

99
        *t = 0;
40,993✔
100

101
        return r;
40,993✔
102
}
103

104
int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) {
10,766✔
105
        int r = 1;
10,766✔
106

107
        assert(p);
10,766✔
108
        assert(ret);
10,766✔
109
        assert(eight_bit);
10,766✔
110

111
        /* Unescapes C style. Returns the unescaped character in ret.
112
         * Sets *eight_bit to true if the escaped sequence either fits in
113
         * one byte in UTF-8 or is a non-unicode literal byte and should
114
         * instead be copied directly.
115
         */
116

117
        if (length != SIZE_MAX && length < 1)
10,766✔
118
                return -EINVAL;
119

120
        switch (p[0]) {
10,766✔
121

122
        case 'a':
4✔
123
                *ret = '\a';
4✔
124
                break;
4✔
125
        case 'b':
8✔
126
                *ret = '\b';
8✔
127
                break;
8✔
128
        case 'f':
3✔
129
                *ret = '\f';
3✔
130
                break;
3✔
131
        case 'n':
22✔
132
                *ret = '\n';
22✔
133
                break;
22✔
134
        case 'r':
4✔
135
                *ret = '\r';
4✔
136
                break;
4✔
137
        case 't':
6✔
138
                *ret = '\t';
6✔
139
                break;
6✔
140
        case 'v':
3✔
141
                *ret = '\v';
3✔
142
                break;
3✔
143
        case '\\':
43✔
144
                *ret = '\\';
43✔
145
                break;
43✔
146
        case '"':
13✔
147
                *ret = '"';
13✔
148
                break;
13✔
149
        case '\'':
1✔
150
                *ret = '\'';
1✔
151
                break;
1✔
152

153
        case 's':
3✔
154
                /* This is an extension of the XDG syntax files */
155
                *ret = ' ';
3✔
156
                break;
3✔
157

158
        case 'x': {
10,574✔
159
                /* hexadecimal encoding */
160
                int a, b;
10,574✔
161

162
                if (length != SIZE_MAX && length < 3)
10,574✔
163
                        return -EINVAL;
164

165
                a = unhexchar(p[1]);
10,570✔
166
                if (a < 0)
10,570✔
167
                        return -EINVAL;
168

169
                b = unhexchar(p[2]);
10,568✔
170
                if (b < 0)
10,568✔
171
                        return -EINVAL;
172

173
                /* Don't allow NUL bytes */
174
                if (a == 0 && b == 0 && !accept_nul)
10,568✔
175
                        return -EINVAL;
176

177
                *ret = (a << 4U) | b;
10,567✔
178
                *eight_bit = true;
10,567✔
179
                r = 3;
10,567✔
180
                break;
10,567✔
181
        }
182

183
        case 'u': {
19✔
184
                /* C++11 style 16-bit unicode */
185

186
                int a[4];
19✔
187
                size_t i;
19✔
188
                uint32_t c;
19✔
189

190
                if (length != SIZE_MAX && length < 5)
19✔
191
                        return -EINVAL;
1✔
192

193
                for (i = 0; i < 4; i++) {
95✔
194
                        a[i] = unhexchar(p[1 + i]);
76✔
195
                        if (a[i] < 0)
76✔
196
                                return a[i];
197
                }
198

199
                c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
19✔
200

201
                /* Don't allow 0 chars */
202
                if (c == 0 && !accept_nul)
19✔
203
                        return -EINVAL;
204

205
                *ret = c;
18✔
206
                r = 5;
18✔
207
                break;
18✔
208
        }
209

210
        case 'U': {
10✔
211
                /* C++11 style 32-bit unicode */
212

213
                int a[8];
10✔
214
                size_t i;
10✔
215
                char32_t c;
10✔
216

217
                if (length != SIZE_MAX && length < 9)
10✔
218
                        return -EINVAL;
×
219

220
                for (i = 0; i < 8; i++) {
90✔
221
                        a[i] = unhexchar(p[1 + i]);
80✔
222
                        if (a[i] < 0)
80✔
223
                                return a[i];
224
                }
225

226
                c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
10✔
227
                    ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
10✔
228

229
                /* Don't allow 0 chars */
230
                if (c == 0 && !accept_nul)
10✔
231
                        return -EINVAL;
232

233
                /* Don't allow invalid code points */
234
                if (!unichar_is_valid(c))
10✔
235
                        return -EINVAL;
236

237
                *ret = c;
10✔
238
                r = 9;
10✔
239
                break;
10✔
240
        }
241

242
        case '0':
20✔
243
        case '1':
244
        case '2':
245
        case '3':
246
        case '4':
247
        case '5':
248
        case '6':
249
        case '7': {
250
                /* octal encoding */
251
                int a, b, c;
20✔
252
                char32_t m;
20✔
253

254
                if (length != SIZE_MAX && length < 3)
20✔
255
                        return -EINVAL;
256

257
                a = unoctchar(p[0]);
16✔
258
                if (a < 0)
16✔
259
                        return -EINVAL;
260

261
                b = unoctchar(p[1]);
16✔
262
                if (b < 0)
16✔
263
                        return -EINVAL;
264

265
                c = unoctchar(p[2]);
16✔
266
                if (c < 0)
16✔
267
                        return -EINVAL;
268

269
                /* don't allow NUL bytes */
270
                if (a == 0 && b == 0 && c == 0 && !accept_nul)
16✔
271
                        return -EINVAL;
272

273
                /* Don't allow bytes above 255 */
274
                m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
14✔
275
                if (m > 255)
14✔
276
                        return -EINVAL;
277

278
                *ret = m;
14✔
279
                *eight_bit = true;
14✔
280
                r = 3;
14✔
281
                break;
14✔
282
        }
283

284
        default:
285
                return -EINVAL;
286
        }
287

288
        return r;
289
}
290

291
ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
137,312✔
292
        _cleanup_free_ char *ans = NULL;
137,312✔
293
        char *t;
137,312✔
294
        const char *f;
137,312✔
295
        size_t pl;
137,312✔
296
        int r;
137,312✔
297

298
        assert(s);
137,312✔
299
        assert(ret);
137,312✔
300

301
        /* Undoes C style string escaping, and optionally prefixes it. */
302

303
        if (length == SIZE_MAX)
137,312✔
304
                length = strlen(s);
107,976✔
305

306
        pl = strlen_ptr(prefix);
137,312✔
307

308
        ans = new(char, pl+length+1);
137,312✔
309
        if (!ans)
137,312✔
310
                return -ENOMEM;
311

312
        if (prefix)
137,312✔
313
                memcpy(ans, prefix, pl);
29,295✔
314

315
        for (f = s, t = ans + pl; f < s + length; f++) {
4,794,941✔
316
                size_t remaining;
4,657,638✔
317
                bool eight_bit = false;
4,657,638✔
318
                char32_t u;
4,657,638✔
319

320
                remaining = s + length - f;
4,657,638✔
321
                assert(remaining > 0);
4,657,638✔
322

323
                if (*f != '\\') {
4,657,638✔
324
                        /* A literal, copy verbatim */
325
                        *(t++) = *f;
4,646,955✔
326
                        continue;
4,646,962✔
327
                }
328

329
                if (remaining == 1) {
10,683✔
330
                        if (flags & UNESCAPE_RELAX) {
3✔
331
                                /* A trailing backslash, copy verbatim */
332
                                *(t++) = *f;
1✔
333
                                continue;
1✔
334
                        }
335

336
                        return -EINVAL;
9✔
337
                }
338

339
                r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
10,680✔
340
                if (r < 0) {
10,680✔
341
                        if (flags & UNESCAPE_RELAX) {
13✔
342
                                /* Invalid escape code, let's take it literal then */
343
                                *(t++) = '\\';
6✔
344
                                continue;
6✔
345
                        }
346

347
                        return r;
7✔
348
                }
349

350
                f += r;
10,667✔
351
                if (eight_bit)
10,667✔
352
                        /* One byte? Set directly as specified */
353
                        *(t++) = u;
10,568✔
354
                else
355
                        /* Otherwise encode as multi-byte UTF-8 */
356
                        t += utf8_encode_unichar(t, u);
99✔
357
        }
358

359
        *t = 0;
137,303✔
360

361
        assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */
137,303✔
362
        *ret = TAKE_PTR(ans);
137,303✔
363
        return t - *ret;
137,303✔
364
}
365

366
char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) {
103,488✔
367
        char *ans, *t, *prev, *prev2;
103,488✔
368
        const char *f;
103,488✔
369

370
        assert(s);
103,488✔
371

372
        /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
373
         * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through
374
         * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings.
375
         *
376
         * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is
377
         * appended. */
378

379
        if (console_width == 0)
103,488✔
380
                return strdup("");
4✔
381

382
        size_t body = MIN(strlen(s), console_width) * 4;
103,484✔
383
        ans = new(char, body + STRLEN("...") + 1);
103,484✔
384
        if (!ans)
103,484✔
385
                return NULL;
386

387
        memset(ans, '_', body);
103,484✔
388
        ans[body] = 0;
103,484✔
389

390
        bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS);
103,484✔
391

392
        for (f = s, t = prev = prev2 = ans; ; f++) {
103,484✔
393
                char *tmp_t = t;
6,211,972✔
394

395
                if (!*f) {
6,211,972✔
396
                        if (force_ellipsis)
103,280✔
397
                                break;
398

399
                        *t = 0;
103,266✔
400
                        return ans;
103,266✔
401
                }
402

403
                if ((unsigned char) *f < ' ' ||
6,108,692✔
404
                    (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) ||
6,107,523✔
405
                    *f == '\\' || (bad && strchr(bad, *f))) {
6,106,610✔
406
                        if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width)
10,899✔
407
                                break;
408

409
                        *(t++) = '\\';
10,715✔
410
                        *(t++) = 'x';
10,715✔
411
                        *(t++) = hexchar(*f >> 4);
10,715✔
412
                        *(t++) = hexchar(*f);
10,715✔
413
                } else {
414
                        if ((size_t) (t - ans) + 1 + 3 * force_ellipsis > console_width)
6,097,793✔
415
                                break;
416

417
                        *(t++) = *f;
6,097,773✔
418
                }
419

420
                /* We might need to go back two cycles to fit three dots, so remember two positions */
421
                prev2 = prev;
6,108,488✔
422
                prev = tmp_t;
6,108,488✔
423
        }
424

425
        /* We can just write where we want, since chars are one-byte */
426
        size_t c = MIN(console_width, 3u); /* If the console is too narrow, write fewer dots */
218✔
427
        size_t off;
218✔
428
        if (console_width - c >= (size_t) (t - ans))
218✔
429
                off = (size_t) (t - ans);
430
        else if (console_width - c >= (size_t) (prev - ans))
76✔
431
                off = (size_t) (prev - ans);
432
        else if (console_width - c >= (size_t) (prev2 - ans))
10✔
433
                off = (size_t) (prev2 - ans);
434
        else
435
                off = console_width - c;
1✔
436
        assert(off <= (size_t) (t - ans));
218✔
437

438
        memcpy(ans + off, "...", c);
218✔
439
        ans[off + c] = '\0';
218✔
440
        return ans;
218✔
441
}
442

443
char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) {
340✔
444
        if (FLAGS_SET(flags, XESCAPE_8_BIT))
340✔
445
                return xescape_full(str, /* bad= */ NULL, console_width, flags);
×
446
        else
447
                return utf8_escape_non_printable_full(str,
340✔
448
                                                      console_width,
449
                                                      FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS));
340✔
450
}
451

452
char* octescape_full(const char *s, size_t len, const char *bad) {
80✔
453
        char *buf, *t;
80✔
454

455
        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */
456

457
        assert(s || len == 0);
80✔
458

459
        if (len == SIZE_MAX)
80✔
460
                len = strlen(s);
27✔
461

462
        if (len > (SIZE_MAX - 1) / 4)
80✔
463
                return NULL;
464

465
        t = buf = new(char, len * 4 + 1);
80✔
466
        if (!buf)
80✔
467
                return NULL;
468

469
        for (size_t i = 0; i < len; i++) {
908✔
470
                uint8_t u = (uint8_t) s[i];
828✔
471

472
                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) {
828✔
473
                        *(t++) = '\\';
5✔
474
                        *(t++) = '0' + (u >> 6);
5✔
475
                        *(t++) = '0' + ((u >> 3) & 7);
5✔
476
                        *(t++) = '0' + (u & 7);
5✔
477
                } else
478
                        *(t++) = u;
823✔
479
        }
480

481
        *t = 0;
80✔
482
        return buf;
80✔
483
}
484

485
char* decescape(const char *s, size_t len, const char *bad) {
8✔
486
        char *buf, *t;
8✔
487

488
        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
489

490
        assert(s || len == 0);
8✔
491

492
        if (len == SIZE_MAX)
8✔
493
                len = strlen(s);
6✔
494

495
        if (len > (SIZE_MAX - 1) / 4)
8✔
496
                return NULL;
497

498
        t = buf = new(char, len * 4 + 1);
8✔
499
        if (!buf)
8✔
500
                return NULL;
501

502
        for (size_t i = 0; i < len; i++) {
47✔
503
                uint8_t u = (uint8_t) s[i];
39✔
504

505
                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
39✔
506
                        *(t++) = '\\';
20✔
507
                        *(t++) = '0' + (u / 100);
20✔
508
                        *(t++) = '0' + ((u / 10) % 10);
20✔
509
                        *(t++) = '0' + (u % 10);
20✔
510
                } else
511
                        *(t++) = u;
19✔
512
        }
513

514
        *t = 0;
8✔
515
        return buf;
8✔
516
}
517

518
static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
191,130✔
519
        assert(bad);
191,130✔
520
        assert(t);
191,130✔
521
        assert(s);
191,130✔
522

523
        while (*s) {
3,711,250✔
524
                int l = utf8_encoded_valid_unichar(s, SIZE_MAX);
3,520,120✔
525

526
                if (char_is_cc(*s) || l < 0)
3,520,120✔
527
                        t += cescape_char(*(s++), t);
16,563✔
528
                else if (l == 1) {
3,503,557✔
529
                        if (*s == '\\' || strchr(bad, *s))
3,503,551✔
530
                                *(t++) = '\\';
4,405✔
531
                        *(t++) = *(s++);
3,503,551✔
532
                } else {
533
                        t = mempcpy(t, s, l);
6✔
534
                        s += l;
6✔
535
                }
536
        }
537

538
        return t;
191,130✔
539
}
540

541
char* shell_escape(const char *s, const char *bad) {
186,233✔
542
        char *buf, *t;
186,233✔
543

544
        buf = new(char, strlen(s)*4+1);
186,233✔
545
        if (!buf)
186,233✔
546
                return NULL;
547

548
        t = strcpy_backslash_escaped(buf, s, bad);
186,233✔
549
        *t = 0;
186,233✔
550

551
        return buf;
186,233✔
552
}
553

554
char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) {
92,669✔
555
        const char *p;
92,669✔
556
        char *buf, *t;
92,669✔
557

558
        assert(s);
92,669✔
559

560
        /* Encloses a string in quotes if necessary to make it OK as a shell string. */
561

562
        if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s))
92,669✔
563
                return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
567✔
564

565
        for (p = s; *p; ) {
1,901,221✔
566
                int l = utf8_encoded_valid_unichar(p, SIZE_MAX);
1,814,016✔
567

568
                if (char_is_cc(*p) || l < 0 ||
1,814,016✔
569
                    strchr(WHITESPACE SHELL_NEED_QUOTES, *p))
1,813,980✔
570
                        break;
571

572
                p += l;
1,809,119✔
573
        }
574

575
        if (!*p)
92,102✔
576
                return strdup(s);
87,205✔
577

578
        buf = new(char, FLAGS_SET(flags, SHELL_ESCAPE_POSIX) + 1 + strlen(s)*4 + 1 + 1);
9,722✔
579
        if (!buf)
4,897✔
580
                return NULL;
581

582
        t = buf;
4,897✔
583
        if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) {
4,897✔
584
                *(t++) = '$';
72✔
585
                *(t++) = '\'';
72✔
586
        } else
587
                *(t++) = '"';
4,825✔
588

589
        t = mempcpy(t, s, p - s);
4,897✔
590

591
        t = strcpy_backslash_escaped(t, p,
9,722✔
592
                                     FLAGS_SET(flags, SHELL_ESCAPE_POSIX) ? SHELL_NEED_ESCAPE_POSIX : SHELL_NEED_ESCAPE);
593

594
        if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX))
4,897✔
595
                *(t++) = '\'';
72✔
596
        else
597
                *(t++) = '"';
4,825✔
598
        *t = 0;
4,897✔
599

600
        return str_realloc(buf);
4,897✔
601
}
602

603
char* quote_command_line(char * const *argv, ShellEscapeFlags flags) {
30,549✔
604
        _cleanup_free_ char *result = NULL;
30,549✔
605

606
        assert(argv);
30,549✔
607

608
        STRV_FOREACH(a, argv) {
122,283✔
609
                _cleanup_free_ char *t = NULL;
91,734✔
610

611
                t = shell_maybe_quote(*a, flags);
91,734✔
612
                if (!t)
91,734✔
613
                        return NULL;
614

615
                if (!strextend_with_separator(&result, " ", t))
91,734✔
616
                        return NULL;
617
        }
618

619
        return str_realloc(TAKE_PTR(result));
30,549✔
620
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc