]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/delta/delta.c
util: move string_is_safe() to string-util.[ch]
[thirdparty/systemd.git] / src / delta / delta.c
CommitLineData
7e8d5761
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
6096dfd6 7 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7e8d5761
LP
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
23#include <errno.h>
7e8d5761 24#include <getopt.h>
3f6fd1ba 25#include <string.h>
ce30c8dc 26#include <sys/prctl.h>
3f6fd1ba 27#include <unistd.h>
7e8d5761 28
a0956174 29#include "dirent-util.h"
3ffd4af2 30#include "fd-util.h"
f4f15635 31#include "fs-util.h"
7e8d5761 32#include "hashmap.h"
7e8d5761
LP
33#include "log.h"
34#include "pager.h"
6bedfcbb 35#include "parse-util.h"
3f6fd1ba 36#include "path-util.h"
0b452006 37#include "process-util.h"
ce30c8dc 38#include "signal-util.h"
07630cea 39#include "string-util.h"
3f6fd1ba
LP
40#include "strv.h"
41#include "terminal-util.h"
42#include "util.h"
7e8d5761 43
f939e9a4
ZJS
44static const char prefixes[] =
45 "/etc\0"
46 "/run\0"
47 "/usr/local/lib\0"
48 "/usr/local/share\0"
49 "/usr/lib\0"
50 "/usr/share\0"
51#ifdef HAVE_SPLIT_USR
52 "/lib\0"
53#endif
54 ;
55
56static const char suffixes[] =
57 "sysctl.d\0"
58 "tmpfiles.d\0"
59 "modules-load.d\0"
60 "binfmt.d\0"
61 "systemd/system\0"
62 "systemd/user\0"
63 "systemd/system-preset\0"
64 "systemd/user-preset\0"
65 "udev/rules.d\0"
66 "modprobe.d\0";
67
68static const char have_dropins[] =
69 "systemd/system\0"
70 "systemd/user\0";
71
7e8d5761 72static bool arg_no_pager = false;
866062b1 73static int arg_diff = -1;
7e8d5761 74
866062b1 75static enum {
4c4e6431 76 SHOW_MASKED = 1 << 0,
c8021373
LP
77 SHOW_EQUIVALENT = 1 << 1,
78 SHOW_REDIRECTED = 1 << 2,
386da858 79 SHOW_OVERRIDDEN = 1 << 3,
4c4e6431 80 SHOW_UNCHANGED = 1 << 4,
0000ce05 81 SHOW_EXTENDED = 1 << 5,
4c4e6431
LP
82
83 SHOW_DEFAULTS =
0000ce05 84 (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
866062b1 85} arg_flags = 0;
4c4e6431 86
f939e9a4
ZJS
87static void pager_open_if_enabled(void) {
88
89 if (arg_no_pager)
90 return;
91
92 pager_open(false);
93}
94
7e8d5761 95static int equivalent(const char *a, const char *b) {
e26970a8 96 _cleanup_free_ char *x = NULL, *y = NULL;
7e8d5761
LP
97
98 x = canonicalize_file_name(a);
99 if (!x)
100 return -errno;
101
102 y = canonicalize_file_name(b);
e26970a8 103 if (!y)
7e8d5761 104 return -errno;
7e8d5761 105
e26970a8 106 return path_equal(x, y);
7e8d5761
LP
107}
108
866062b1
LP
109static int notify_override_masked(const char *top, const char *bottom) {
110 if (!(arg_flags & SHOW_MASKED))
807f4645
GN
111 return 0;
112
00a5cc3a 113 printf("%s%s%s %s %s %s\n",
1fc464f6 114 ansi_highlight_red(), "[MASKED]", ansi_normal(),
6b01f1d3 115 top, draw_special_char(DRAW_ARROW), bottom);
807f4645
GN
116 return 1;
117}
118
866062b1
LP
119static int notify_override_equivalent(const char *top, const char *bottom) {
120 if (!(arg_flags & SHOW_EQUIVALENT))
807f4645
GN
121 return 0;
122
00a5cc3a 123 printf("%s%s%s %s %s %s\n",
1fc464f6 124 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
6b01f1d3 125 top, draw_special_char(DRAW_ARROW), bottom);
807f4645
GN
126 return 1;
127}
128
866062b1
LP
129static int notify_override_redirected(const char *top, const char *bottom) {
130 if (!(arg_flags & SHOW_REDIRECTED))
807f4645
GN
131 return 0;
132
9b6e0ce5 133 printf("%s%s%s %s %s %s\n",
1fc464f6 134 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
6b01f1d3 135 top, draw_special_char(DRAW_ARROW), bottom);
807f4645
GN
136 return 1;
137}
138
386da858
NM
139static int notify_override_overridden(const char *top, const char *bottom) {
140 if (!(arg_flags & SHOW_OVERRIDDEN))
807f4645
GN
141 return 0;
142
00a5cc3a 143 printf("%s%s%s %s %s %s\n",
1fc464f6 144 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
6b01f1d3 145 top, draw_special_char(DRAW_ARROW), bottom);
807f4645
GN
146 return 1;
147}
148
0000ce05
LN
149static int notify_override_extended(const char *top, const char *bottom) {
150 if (!(arg_flags & SHOW_EXTENDED))
151 return 0;
152
00a5cc3a 153 printf("%s%s%s %s %s %s\n",
1fc464f6 154 ansi_highlight(), "[EXTENDED]", ansi_normal(),
6b01f1d3 155 top, draw_special_char(DRAW_ARROW), bottom);
0000ce05
LN
156 return 1;
157}
158
866062b1
LP
159static int notify_override_unchanged(const char *f) {
160 if (!(arg_flags & SHOW_UNCHANGED))
807f4645
GN
161 return 0;
162
8e812a23 163 printf("[UNCHANGED] %s\n", f);
807f4645
GN
164 return 1;
165}
166
866062b1 167static int found_override(const char *top, const char *bottom) {
e26970a8 168 _cleanup_free_ char *dest = NULL;
a572b2e0 169 int k;
7e8d5761
LP
170 pid_t pid;
171
172 assert(top);
173 assert(bottom);
174
8fd57568
ZJS
175 if (null_or_empty_path(top) > 0)
176 return notify_override_masked(top, bottom);
7e8d5761
LP
177
178 k = readlink_malloc(top, &dest);
179 if (k >= 0) {
180 if (equivalent(dest, bottom) > 0)
8fd57568 181 return notify_override_equivalent(top, bottom);
7e8d5761 182 else
8fd57568 183 return notify_override_redirected(top, bottom);
7e8d5761
LP
184 }
185
8fd57568 186 k = notify_override_overridden(top, bottom);
866062b1 187 if (!arg_diff)
8fd57568 188 return k;
7e8d5761
LP
189
190 putchar('\n');
191
192 fflush(stdout);
193
194 pid = fork();
4a62c710
MS
195 if (pid < 0)
196 return log_error_errno(errno, "Failed to fork off diff: %m");
197 else if (pid == 0) {
ce30c8dc
LP
198
199 (void) reset_all_signal_handlers();
200 (void) reset_signal_mask();
201 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
202
7e8d5761 203 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
56f64d95 204 log_error_errno(errno, "Failed to execute diff: %m");
ce30c8dc 205 _exit(EXIT_FAILURE);
7e8d5761
LP
206 }
207
820d3acf 208 wait_for_terminate_and_warn("diff", pid, false);
7e8d5761
LP
209 putchar('\n');
210
8fd57568 211 return k;
7e8d5761
LP
212}
213
0000ce05 214static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
f939e9a4 215 _cleanup_free_ char *unit = NULL;
0000ce05
LN
216 _cleanup_free_ char *path = NULL;
217 _cleanup_strv_free_ char **list = NULL;
218 char **file;
219 char *c;
220 int r;
221
f939e9a4
ZJS
222 assert(!endswith(drop, "/"));
223
0000ce05
LN
224 path = strjoin(toppath, "/", drop, NULL);
225 if (!path)
226 return -ENOMEM;
227
f939e9a4 228 log_debug("Looking at %s", path);
0000ce05 229
f939e9a4
ZJS
230 unit = strdup(drop);
231 if (!unit)
0000ce05
LN
232 return -ENOMEM;
233
f939e9a4 234 c = strrchr(unit, '.');
0000ce05
LN
235 if (!c)
236 return -EINVAL;
237 *c = 0;
238
239 r = get_files_in_directory(path, &list);
23bbb0de
MS
240 if (r < 0)
241 return log_error_errno(r, "Failed to enumerate %s: %m", path);
0000ce05
LN
242
243 STRV_FOREACH(file, list) {
244 Hashmap *h;
245 int k;
246 char *p;
247 char *d;
248
249 if (!endswith(*file, ".conf"))
250 continue;
251
252 p = strjoin(path, "/", *file, NULL);
253 if (!p)
254 return -ENOMEM;
f939e9a4 255 d = p + strlen(toppath) + 1;
0000ce05 256
6b01f1d3 257 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
0000ce05
LN
258 k = hashmap_put(top, d, p);
259 if (k >= 0) {
260 p = strdup(p);
261 if (!p)
262 return -ENOMEM;
f939e9a4 263 d = p + strlen(toppath) + 1;
0000ce05
LN
264 } else if (k != -EEXIST) {
265 free(p);
266 return k;
267 }
268
6b01f1d3 269 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
0000ce05
LN
270 free(hashmap_remove(bottom, d));
271 k = hashmap_put(bottom, d, p);
272 if (k < 0) {
273 free(p);
274 return k;
275 }
276
f939e9a4 277 h = hashmap_get(drops, unit);
0000ce05 278 if (!h) {
d5099efc 279 h = hashmap_new(&string_hash_ops);
0000ce05
LN
280 if (!h)
281 return -ENOMEM;
f939e9a4
ZJS
282 hashmap_put(drops, unit, h);
283 unit = strdup(unit);
284 if (!unit)
0000ce05
LN
285 return -ENOMEM;
286 }
287
288 p = strdup(p);
289 if (!p)
290 return -ENOMEM;
291
00a5cc3a 292 log_debug("Adding to drops: %s %s %s %s %s",
6b01f1d3 293 unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
2b6bf07d 294 k = hashmap_put(h, basename(p), p);
0000ce05
LN
295 if (k < 0) {
296 free(p);
297 if (k != -EEXIST)
298 return k;
299 }
300 }
301 return 0;
302}
303
304static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
e26970a8 305 _cleanup_closedir_ DIR *d;
7e8d5761
LP
306
307 assert(top);
308 assert(bottom);
0000ce05 309 assert(drops);
7e8d5761
LP
310 assert(path);
311
f939e9a4
ZJS
312 log_debug("Looking at %s", path);
313
7e8d5761
LP
314 d = opendir(path);
315 if (!d) {
316 if (errno == ENOENT)
317 return 0;
318
56f64d95 319 log_error_errno(errno, "Failed to open %s: %m", path);
7e8d5761
LP
320 return -errno;
321 }
322
323 for (;;) {
7d5e9c0f 324 struct dirent *de;
7e8d5761
LP
325 int k;
326 char *p;
327
6887ec4f
FW
328 errno = 0;
329 de = readdir(d);
7e8d5761 330 if (!de)
f939e9a4 331 return -errno;
7e8d5761 332
277f2f75
LN
333 dirent_ensure_type(d, de);
334
0000ce05
LN
335 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
336 enumerate_dir_d(top, bottom, drops, path, de->d_name);
337
7e8d5761
LP
338 if (!dirent_is_file(de))
339 continue;
340
b7def684 341 p = strjoin(path, "/", de->d_name, NULL);
e26970a8
TA
342 if (!p)
343 return -ENOMEM;
7e8d5761 344
6b01f1d3 345 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
2b6bf07d 346 k = hashmap_put(top, basename(p), p);
7e8d5761
LP
347 if (k >= 0) {
348 p = strdup(p);
e26970a8
TA
349 if (!p)
350 return -ENOMEM;
7e8d5761
LP
351 } else if (k != -EEXIST) {
352 free(p);
e26970a8 353 return k;
7e8d5761
LP
354 }
355
6b01f1d3 356 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
2b6bf07d
ZJS
357 free(hashmap_remove(bottom, basename(p)));
358 k = hashmap_put(bottom, basename(p), p);
7e8d5761
LP
359 if (k < 0) {
360 free(p);
e26970a8 361 return k;
7e8d5761
LP
362 }
363 }
7e8d5761
LP
364}
365
6096dfd6 366static int process_suffix(const char *suffix, const char *onlyprefix) {
7e8d5761
LP
367 const char *p;
368 char *f;
f939e9a4 369 Hashmap *top, *bottom, *drops;
0000ce05
LN
370 Hashmap *h;
371 char *key;
7e8d5761 372 int r = 0, k;
0000ce05 373 Iterator i, j;
7e8d5761 374 int n_found = 0;
f939e9a4 375 bool dropins;
7e8d5761 376
7e8d5761 377 assert(suffix);
f939e9a4
ZJS
378 assert(!startswith(suffix, "/"));
379 assert(!strstr(suffix, "//"));
7e8d5761 380
f939e9a4 381 dropins = nulstr_contains(have_dropins, suffix);
7e8d5761 382
d5099efc
MS
383 top = hashmap_new(&string_hash_ops);
384 bottom = hashmap_new(&string_hash_ops);
385 drops = hashmap_new(&string_hash_ops);
f939e9a4 386 if (!top || !bottom || !drops) {
0000ce05
LN
387 r = -ENOMEM;
388 goto finish;
389 }
390
7e8d5761 391 NULSTR_FOREACH(p, prefixes) {
e26970a8 392 _cleanup_free_ char *t = NULL;
7e8d5761 393
b7def684 394 t = strjoin(p, "/", suffix, NULL);
7e8d5761
LP
395 if (!t) {
396 r = -ENOMEM;
397 goto finish;
398 }
399
0000ce05 400 k = enumerate_dir(top, bottom, drops, t, dropins);
f939e9a4 401 if (r == 0)
7e8d5761 402 r = k;
7e8d5761
LP
403 }
404
0000ce05 405 HASHMAP_FOREACH_KEY(f, key, top, i) {
7e8d5761
LP
406 char *o;
407
0000ce05 408 o = hashmap_get(bottom, key);
7e8d5761
LP
409 assert(o);
410
6096dfd6
ZJS
411 if (!onlyprefix || startswith(o, onlyprefix)) {
412 if (path_equal(o, f)) {
413 notify_override_unchanged(f);
414 } else {
415 k = found_override(f, o);
416 if (k < 0)
417 r = k;
418 else
419 n_found += k;
420 }
807f4645 421 }
7e8d5761 422
0000ce05
LN
423 h = hashmap_get(drops, key);
424 if (h)
8fd57568 425 HASHMAP_FOREACH(o, h, j)
6096dfd6
ZJS
426 if (!onlyprefix || startswith(o, onlyprefix))
427 n_found += notify_override_extended(f, o);
7e8d5761
LP
428 }
429
430finish:
431 if (top)
432 hashmap_free_free(top);
433 if (bottom)
434 hashmap_free_free(bottom);
0000ce05
LN
435 if (drops) {
436 HASHMAP_FOREACH_KEY(h, key, drops, i){
437 hashmap_free_free(hashmap_remove(drops, key));
438 hashmap_remove(drops, key);
439 free(key);
440 }
441 hashmap_free(drops);
442 }
7e8d5761
LP
443 return r < 0 ? r : n_found;
444}
445
6096dfd6
ZJS
446static int process_suffixes(const char *onlyprefix) {
447 const char *n;
448 int n_found = 0, r;
449
450 NULSTR_FOREACH(n, suffixes) {
451 r = process_suffix(n, onlyprefix);
452 if (r < 0)
453 return r;
454 else
455 n_found += r;
456 }
457 return n_found;
458}
459
460static int process_suffix_chop(const char *arg) {
7e8d5761
LP
461 const char *p;
462
6096dfd6 463 assert(arg);
7e8d5761 464
6096dfd6
ZJS
465 if (!path_is_absolute(arg))
466 return process_suffix(arg, NULL);
7e8d5761
LP
467
468 /* Strip prefix from the suffix */
469 NULSTR_FOREACH(p, prefixes) {
6096dfd6
ZJS
470 const char *suffix = startswith(arg, p);
471 if (suffix) {
7e8d5761 472 suffix += strspn(suffix, "/");
6096dfd6
ZJS
473 if (*suffix)
474 return process_suffix(suffix, NULL);
475 else
476 return process_suffixes(arg);
7e8d5761
LP
477 }
478 }
479
6096dfd6 480 log_error("Invalid suffix specification %s.", arg);
7e8d5761
LP
481 return -EINVAL;
482}
483
601185b4 484static void help(void) {
7e8d5761
LP
485 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
486 "Find overridden configuration files.\n\n"
487 " -h --help Show this help\n"
488 " --version Show package version\n"
807f4645 489 " --no-pager Do not pipe output into a pager\n"
386da858 490 " --diff[=1|0] Show a diff when overridden files differ\n"
601185b4
ZJS
491 " -t --type=LIST... Only display a selected set of override types\n"
492 , program_invocation_short_name);
7e8d5761
LP
493}
494
866062b1 495static int parse_flags(const char *flag_str, int flags) {
a2a5291b 496 const char *word, *state;
807f4645
GN
497 size_t l;
498
d0a2e1c3 499 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
a2a5291b 500 if (strneq("masked", word, l))
807f4645 501 flags |= SHOW_MASKED;
a2a5291b 502 else if (strneq ("equivalent", word, l))
c8021373 503 flags |= SHOW_EQUIVALENT;
a2a5291b 504 else if (strneq("redirected", word, l))
c8021373 505 flags |= SHOW_REDIRECTED;
a2a5291b 506 else if (strneq("overridden", word, l))
386da858 507 flags |= SHOW_OVERRIDDEN;
a2a5291b 508 else if (strneq("unchanged", word, l))
807f4645 509 flags |= SHOW_UNCHANGED;
a2a5291b 510 else if (strneq("extended", word, l))
0000ce05 511 flags |= SHOW_EXTENDED;
a2a5291b 512 else if (strneq("default", word, l))
807f4645 513 flags |= SHOW_DEFAULTS;
866062b1
LP
514 else
515 return -EINVAL;
807f4645
GN
516 }
517 return flags;
518}
519
866062b1 520static int parse_argv(int argc, char *argv[]) {
7e8d5761
LP
521
522 enum {
523 ARG_NO_PAGER = 0x100,
807f4645 524 ARG_DIFF,
7e8d5761
LP
525 ARG_VERSION
526 };
527
528 static const struct option options[] = {
529 { "help", no_argument, NULL, 'h' },
530 { "version", no_argument, NULL, ARG_VERSION },
531 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
807f4645
GN
532 { "diff", optional_argument, NULL, ARG_DIFF },
533 { "type", required_argument, NULL, 't' },
eb9da376 534 {}
7e8d5761
LP
535 };
536
537 int c;
538
539 assert(argc >= 1);
540 assert(argv);
541
601185b4 542 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
7e8d5761
LP
543
544 switch (c) {
545
546 case 'h':
547 help();
548 return 0;
549
550 case ARG_VERSION:
3f6fd1ba 551 return version();
7e8d5761
LP
552
553 case ARG_NO_PAGER:
554 arg_no_pager = true;
555 break;
556
866062b1
LP
557 case 't': {
558 int f;
559 f = parse_flags(optarg, arg_flags);
560 if (f < 0) {
561 log_error("Failed to parse flags field.");
807f4645 562 return -EINVAL;
866062b1
LP
563 }
564 arg_flags = f;
807f4645 565 break;
866062b1 566 }
807f4645
GN
567
568 case ARG_DIFF:
866062b1
LP
569 if (!optarg)
570 arg_diff = 1;
571 else {
572 int b;
573
574 b = parse_boolean(optarg);
575 if (b < 0) {
576 log_error("Failed to parse diff boolean.");
577 return -EINVAL;
578 } else if (b)
579 arg_diff = 1;
807f4645 580 else
866062b1 581 arg_diff = 0;
807f4645
GN
582 }
583 break;
584
eb9da376 585 case '?':
7e8d5761 586 return -EINVAL;
eb9da376
LP
587
588 default:
589 assert_not_reached("Unhandled option");
7e8d5761 590 }
7e8d5761
LP
591
592 return 1;
593}
594
595int main(int argc, char *argv[]) {
7e8d5761
LP
596 int r = 0, k;
597 int n_found = 0;
598
599 log_parse_environment();
600 log_open();
601
866062b1 602 r = parse_argv(argc, argv);
7e8d5761
LP
603 if (r <= 0)
604 goto finish;
605
866062b1
LP
606 if (arg_flags == 0)
607 arg_flags = SHOW_DEFAULTS;
608
609 if (arg_diff < 0)
386da858 610 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
866062b1 611 else if (arg_diff)
386da858 612 arg_flags |= SHOW_OVERRIDDEN;
807f4645 613
f939e9a4 614 pager_open_if_enabled();
7e8d5761
LP
615
616 if (optind < argc) {
617 int i;
618
619 for (i = optind; i < argc; i++) {
f939e9a4
ZJS
620 path_kill_slashes(argv[i]);
621 k = process_suffix_chop(argv[i]);
7e8d5761
LP
622 if (k < 0)
623 r = k;
624 else
625 n_found += k;
626 }
627
628 } else {
6096dfd6
ZJS
629 k = process_suffixes(NULL);
630 if (k < 0)
631 r = k;
632 else
633 n_found += k;
7e8d5761
LP
634 }
635
636 if (r >= 0)
8fd57568
ZJS
637 printf("%s%i overridden configuration files found.\n",
638 n_found ? "\n" : "", n_found);
7e8d5761
LP
639
640finish:
641 pager_close();
642
643 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
644}