delta.c revision 807f46452202891731b2317ef9bc9a6bc7115f23
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering/***
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering This file is part of systemd.
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering Copyright 2012 Lennart Poettering
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering systemd is free software; you can redistribute it and/or modify it
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering under the terms of the GNU Lesser General Public License as published by
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering (at your option) any later version.
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering systemd is distributed in the hope that it will be useful, but
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
d360705f0f1262d49cccb6507abeafb7cfb5bbe0Lennart Poettering Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include "hashmap.h"
#include "util.h"
#include "path-util.h"
#include "log.h"
#include "pager.h"
#include "build.h"
static bool arg_no_pager = false;
static int equivalent(const char *a, const char *b) {
char *x, *y;
int r;
x = canonicalize_file_name(a);
if (!x)
return -errno;
y = canonicalize_file_name(b);
if (!y) {
free(x);
return -errno;
}
r = path_equal(x, y);
free(x);
free(y);
return r;
}
#define SHOW_MASKED 1 << 0
#define SHOW_EQUIV 1 << 1
#define SHOW_REDIR 1 << 2
#define SHOW_OVERRIDEN 1 << 3
#define SHOW_UNCHANGED 1 << 4
#define SHOW_DIFF 1 << 5
#define SHOW_DEFAULTS \
(SHOW_MASKED | SHOW_EQUIV | SHOW_REDIR | SHOW_OVERRIDEN | SHOW_DIFF)
static int notify_override_masked(int flags, const char *top, const char *bottom) {
if (!(flags & SHOW_MASKED))
return 0;
printf(ANSI_HIGHLIGHT_RED_ON "[MASK]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
return 1;
}
static int notify_override_equiv(int flags, const char *top, const char *bottom) {
if (!(flags & SHOW_EQUIV))
return 0;
printf(ANSI_HIGHLIGHT_GREEN_ON "[EQUIVALENT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
return 1;
}
static int notify_override_redir(int flags, const char *top, const char *bottom) {
if (!(flags & SHOW_REDIR))
return 0;
printf(ANSI_HIGHLIGHT_ON "[REDIRECT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
return 1;
}
static int notify_override_overriden(int flags, const char *top, const char *bottom) {
if (!(flags & SHOW_OVERRIDEN))
return 0;
printf(ANSI_HIGHLIGHT_ON "[OVERRIDE]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
return 1;
}
static int notify_override_unchanged(int flags, const char *top, const char *bottom) {
if (!(flags & SHOW_UNCHANGED))
return 0;
printf(ANSI_HIGHLIGHT_ON "[UNCHANGED]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
return 1;
}
static int found_override(int flags, const char *top, const char *bottom) {
char *dest;
int k;
pid_t pid;
assert(top);
assert(bottom);
if (null_or_empty_path(top) > 0) {
notify_override_masked(flags, top, bottom);
goto finish;
}
k = readlink_malloc(top, &dest);
if (k >= 0) {
if (equivalent(dest, bottom) > 0)
notify_override_equiv(flags, top, bottom);
else
notify_override_redir(flags, top, bottom);
free(dest);
goto finish;
}
notify_override_overriden(flags, top, bottom);
if (!(flags & SHOW_DIFF))
goto finish;
putchar('\n');
fflush(stdout);
pid = fork();
if (pid < 0) {
log_error("Failed to fork off diff: %m");
return -errno;
} else if (pid == 0) {
execlp("diff", "diff", "-us", "--", bottom, top, NULL);
log_error("Failed to execute diff: %m");
_exit(1);
}
wait_for_terminate(pid, NULL);
putchar('\n');
finish:
return 0;
}
static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) {
DIR *d;
int r = 0;
assert(top);
assert(bottom);
assert(path);
d = opendir(path);
if (!d) {
if (errno == ENOENT)
return 0;
log_error("Failed to enumerate %s: %m", path);
return -errno;
}
for (;;) {
struct dirent *de, buf;
int k;
char *p;
k = readdir_r(d, &buf, &de);
if (k != 0) {
r = -k;
goto finish;
}
if (!de)
break;
if (!dirent_is_file(de))
continue;
p = join(path, "/", de->d_name, NULL);
if (!p) {
r = -ENOMEM;
goto finish;
}
path_kill_slashes(p);
k = hashmap_put(top, path_get_file_name(p), p);
if (k >= 0) {
p = strdup(p);
if (!p) {
r = -ENOMEM;
goto finish;
}
} else if (k != -EEXIST) {
free(p);
r = k;
goto finish;
}
free(hashmap_remove(bottom, path_get_file_name(p)));
k = hashmap_put(bottom, path_get_file_name(p), p);
if (k < 0) {
free(p);
r = k;
goto finish;
}
}
finish:
closedir(d);
return r;
}
static int process_suffix(int flags, const char *prefixes, const char *suffix) {
const char *p;
char *f;
Hashmap *top, *bottom;
int r = 0, k;
Iterator i;
int n_found = 0;
assert(prefixes);
assert(suffix);
top = hashmap_new(string_hash_func, string_compare_func);
if (!top) {
r = -ENOMEM;
goto finish;
}
bottom = hashmap_new(string_hash_func, string_compare_func);
if (!bottom) {
r = -ENOMEM;
goto finish;
}
NULSTR_FOREACH(p, prefixes) {
char *t;
t = join(p, "/", suffix, NULL);
if (!t) {
r = -ENOMEM;
goto finish;
}
k = enumerate_dir(top, bottom, t);
if (k < 0)
r = k;
log_debug("Looking at %s", t);
free(t);
}
HASHMAP_FOREACH(f, top, i) {
char *o;
o = hashmap_get(bottom, path_get_file_name(f));
assert(o);
if (path_equal(o, f)) {
notify_override_unchanged(flags, f, o);
continue;
}
k = found_override(flags, f, o);
if (k < 0)
r = k;
n_found ++;
}
finish:
if (top)
hashmap_free_free(top);
if (bottom)
hashmap_free_free(bottom);
return r < 0 ? r : n_found;
}
static int process_suffix_chop(int flags, const char *prefixes, const char *suffix) {
const char *p;
assert(prefixes);
assert(suffix);
if (!path_is_absolute(suffix))
return process_suffix(flags, prefixes, suffix);
/* Strip prefix from the suffix */
NULSTR_FOREACH(p, prefixes) {
if (startswith(suffix, p)) {
suffix += strlen(p);;
suffix += strspn(suffix, "/");
return process_suffix(flags, prefixes, suffix);
}
}
log_error("Invalid suffix specification %s.", suffix);
return -EINVAL;
}
static void help(void) {
printf("%s [OPTIONS...] [SUFFIX...]\n\n"
"Find overridden configuration files.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --diff[=1|0] Show a diff when overriden files differ\n"
" -t --type=LIST... Only display a selected set of override types\n",
program_invocation_short_name);
}
static int parse_flags(int flags, const char *flag_str) {
char *w, *state;
size_t l;
FOREACH_WORD(w, l, flag_str, state) {
if (strncmp("masked", w, l) == 0) {
flags |= SHOW_MASKED;
} else if (strncmp ("equivalent", w, l) == 0) {
flags |= SHOW_EQUIV;
} else if (strncmp("redirected", w, l) == 0) {
flags |= SHOW_REDIR;
} else if (strncmp("override", w, l) == 0) {
flags |= SHOW_OVERRIDEN;
} else if (strncmp("unchanged", w, l) == 0) {
flags |= SHOW_UNCHANGED;
} else if (strncmp("default", w, l) == 0) {
flags |= SHOW_DEFAULTS;
} else {
log_error("Unknown type filter: %s", w);
return -1;
}
}
return flags;
}
static int parse_argv(int argc, char *argv[], int *flags) {
enum {
ARG_NO_PAGER = 0x100,
ARG_DIFF,
ARG_VERSION
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "diff", optional_argument, NULL, ARG_DIFF },
{ "type", required_argument, NULL, 't' },
{ NULL, 0, NULL, 0 }
};
int c;
assert(argc >= 1);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(DISTRIBUTION);
puts(SYSTEMD_FEATURES);
return 0;
case ARG_NO_PAGER:
arg_no_pager = true;
break;
case '?':
return -EINVAL;
case 't':
*flags = parse_flags(*flags, optarg);
if (*flags < 0)
return -EINVAL;
break;
case ARG_DIFF:
if (!optarg) {
*flags |= SHOW_DIFF;
} else {
if (parse_boolean(optarg))
*flags |= SHOW_DIFF;
else
*flags &= ~SHOW_DIFF;
}
break;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
}
return 1;
}
int main(int argc, char *argv[]) {
const char prefixes[] =
"/etc\0"
"/run\0"
"/usr/local/lib\0"
"/usr/local/share\0"
"/usr/lib\0"
"/usr/share\0"
#ifdef HAVE_SPLIT_USR
"/lib\0"
#endif
;
const char suffixes[] =
"sysctl.d\0"
"tmpfiles.d\0"
"modules-load.d\0"
"binfmt.d\0"
"systemd/system\0"
"systemd/user\0"
"systemd/system.preset\0"
"systemd/user.preset\0"
"udev/rules.d\0"
"modprobe.d\0";
int r = 0, k;
int n_found = 0;
int flags = 0;
log_parse_environment();
log_open();
r = parse_argv(argc, argv, &flags);
if (r <= 0)
goto finish;
if (flags == 0)
flags = SHOW_DEFAULTS;
if (flags == SHOW_DIFF)
flags |= SHOW_OVERRIDEN;
if (!arg_no_pager)
pager_open();
if (optind < argc) {
int i;
for (i = optind; i < argc; i++) {
k = process_suffix_chop(flags, prefixes, argv[i]);
if (k < 0)
r = k;
else
n_found += k;
}
} else {
const char *n;
NULSTR_FOREACH(n, suffixes) {
k = process_suffix(flags, prefixes, n);
if (k < 0)
r = k;
else
n_found += k;
}
}
if (r >= 0)
printf("\n%i overriden configuration files found.\n", n_found);
finish:
pager_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}