On Wed, 1 Nov 2023 at 21:57, James Carter <jwcart2@xxxxxxxxx> wrote: > > On Wed, Nov 1, 2023 at 12:47 PM Christian Göttsche > <cgzones@xxxxxxxxxxxxxx> wrote: > > > > On Thu, 5 Oct 2023 at 17:29, James Carter <jwcart2@xxxxxxxxx> wrote: > > > > > > On Mon, Aug 14, 2023 at 9:42 AM Christian Göttsche > > > <cgzones@xxxxxxxxxxxxxx> wrote: > > > > > > > > Add a utility around selabel_cmp(3). > > > > > > > > Can be used by users to compare a pre-compiled fcontext file to an > > > > original text-based file context definition file. > > > > > > > > Can be used for development to verify compilation and parsing of the > > > > pre-compiled fcontext format works correctly. > > > > > > > > Signed-off-by: Christian Göttsche <cgzones@xxxxxxxxxxxxxx> > > > > --- > > > > libselinux/utils/.gitignore | 1 + > > > > libselinux/utils/selabel_compare.c | 119 +++++++++++++++++++++++++++++ > > > > 2 files changed, 120 insertions(+) > > > > create mode 100644 libselinux/utils/selabel_compare.c > > > > > > > > diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore > > > > index a92e1e94..4e2dfba8 100644 > > > > --- a/libselinux/utils/.gitignore > > > > +++ b/libselinux/utils/.gitignore > > > > @@ -16,6 +16,7 @@ getseuser > > > > matchpathcon > > > > policyvers > > > > sefcontext_compile > > > > +selabel_compare > > > > selabel_digest > > > > selabel_get_digests_all_partial_matches > > > > selabel_lookup > > > > diff --git a/libselinux/utils/selabel_compare.c b/libselinux/utils/selabel_compare.c > > > > new file mode 100644 > > > > index 00000000..f4325f7e > > > > --- /dev/null > > > > +++ b/libselinux/utils/selabel_compare.c > > > > @@ -0,0 +1,119 @@ > > > > +#include <getopt.h> > > > > +#include <stdio.h> > > > > +#include <stdlib.h> > > > > +#include <string.h> > > > > + > > > > +#include <selinux/label.h> > > > > + > > > > + > > > > +static void usage(const char *progname) > > > > +{ > > > > + fprintf(stderr, > > > > + "usage: %s [-b backend] [-v] file1 file2\n\n" > > > > + "Where:\n\t" > > > > + "-b The backend - \"file\", \"media\", \"x\", \"db\" or \"prop\" (defaults to \"file\")\n\t" > > > > + "-v Validate entries against loaded policy.\n\t" > > > > + "file1/file2 Files containing the specs.\n", > > > > + progname); > > > > +} > > > > + > > > > +int main(int argc, char *argv[]) > > > > +{ > > > > + unsigned int backend = SELABEL_CTX_FILE; > > > > + int opt; > > > > + const char *validate = NULL, *file1 = NULL, *file2 = NULL; > > > > + > > > > + if (argc < 3) { > > > > + usage(argv[0]); > > > > + return EXIT_FAILURE; > > > > + } > > > > + > > > > + while ((opt = getopt(argc, argv, "b:v")) > 0) { > > > > + switch (opt) { > > > > + case 'b': > > > > + if (!strcasecmp(optarg, "file")) { > > > > + backend = SELABEL_CTX_FILE; > > > > + } else if (!strcmp(optarg, "media")) { > > > > + backend = SELABEL_CTX_MEDIA; > > > > + } else if (!strcmp(optarg, "x")) { > > > > + backend = SELABEL_CTX_X; > > > > + } else if (!strcmp(optarg, "db")) { > > > > + backend = SELABEL_CTX_DB; > > > > + } else if (!strcmp(optarg, "prop")) { > > > > + backend = SELABEL_CTX_ANDROID_PROP; > > > > + } else if (!strcmp(optarg, "service")) { > > > > + backend = SELABEL_CTX_ANDROID_SERVICE; > > > > + } else { > > > > + fprintf(stderr, "Unknown backend: %s\n", optarg); > > > > + usage(argv[0]); > > > > + return EXIT_FAILURE; > > > > + } > > > > + break; > > > > + case 'v': > > > > + validate = (char *)1; > > > > + break; > > > > + default: > > > > + usage(argv[0]); > > > > + return EXIT_FAILURE; > > > > + } > > > > + } > > > > + > > > > + if (argc != optind + 2) { > > > > + usage(argv[0]); > > > > + return EXIT_FAILURE; > > > > + } > > > > + > > > > + file1 = argv[optind++]; > > > > + file2 = argv[optind]; > > > > + > > > > + { > > > > > > To me, having a block like this means that the stuff below should be > > > in a helper function. > > > > > > Everything else looks good. > > > Jim > > > > > > > I didn't want to split the function to complicate the reasoning by > > splitting the control flow, introducing 4 function parameters, and > > adding return value handling. > > Do you still prefer to split? > > (I'll send a v2 regardless for a typo below) > > > > Yes, I would. Fair enough. I am not going to send a standalone version of this patch though, since the current compare function can not handle compiled regular expressions, e.g.: $ DESTDIR=/tmp/destdir/ ../selinux/scripts/env_use_destdir /tmp/destdir/usr/sbin/selabel_compare file_contexts.bin file_contexts.bin selabel_cmp: mismatched regex on entry 0: (/.*, 0, system_u:object_r:default_t) vs entry 0: (/.*, 0, system_u:object_r:default_t) spec file_contexts.bin is uncompareable to spec file_contexts.bin > Something like this: > > static enum selabel_cmp_result compare(unsigned int backend, const > char *file1, const char *file2, const char *validate) > { > struct selabel_handle *hnd1, *hnd2; > const struct selinux_opt selabel_option1[] = { > { SELABEL_OPT_PATH, file1 }, > { SELABEL_OPT_VALIDATE, validate } > }; > const struct selinux_opt selabel_option2[] = { > { SELABEL_OPT_PATH, file2 }, > { SELABEL_OPT_VALIDATE, validate } > }; > enum selabel_cmp_result result; > > hnd1 = selabel_open(backend, selabel_option1, 2); > if (!hnd1) { > fprintf(stderr, "ERROR: selabel_open - Could not obtain handle > for %s: %m\n", file1); > return EXIT_FAILURE; > } > > hnd2 = selabel_open(backend, selabel_option2, 2); > if (!hnd2) { > fprintf(stderr, "ERROR: selabel_open - Could not obtain handle > for %s: %m\n", file2); > selabel_close(hnd1); > return EXIT_FAILURE; > } > > result = selabel_cmp(hnd1, hnd2); > > selabel_close(hnd2); > selabel_close(hnd1); > return result; > } > > ... > > int main(int argc, char *argv[]) > { > ... > result = compare(backend, file1, file2, validate); > switch (result) { > case SELABEL_SUBSET: > printf("spec %s is a subset of spec %s\n", file1, file2); > break; > case SELABEL_EQUAL: > printf("spec %s is equal to spec %s\n", file1, file2); > break; > case SELABEL_SUPERSET: > printf("spec %s is a superset of spec %s\n", file1, file2); > break; > case SELABEL_INCOMPARABLE: > printf("spec %s is uncompareable to spec %s\n", file1, file2); > break; > default: > fprintf(stderr, "ERROR: selabel_cmp - Unexpected result %d\n", result); > return EXIT_FAILURE; > } > > return EXIT_SUCCESS; > } > > You can probably think of a better name for the function. > > Thanks, > Jim > > > > > > > > > + struct selabel_handle *hnd1, *hnd2; > > > > + const struct selinux_opt selabel_option1[] = { > > > > + { SELABEL_OPT_PATH, file1 }, > > > > + { SELABEL_OPT_VALIDATE, validate } > > > > + }; > > > > + const struct selinux_opt selabel_option2[] = { > > > > + { SELABEL_OPT_PATH, file2 }, > > > > + { SELABEL_OPT_VALIDATE, validate } > > > > + }; > > > > + enum selabel_cmp_result result; > > > > + > > > > + hnd1 = selabel_open(backend, selabel_option1, 2); > > > > + if (!hnd1) { > > > > + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file1); > > > > + return EXIT_FAILURE; > > > > + } > > > > + > > > > + hnd2 = selabel_open(backend, selabel_option2, 2); > > > > + if (!hnd2) { > > > > + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file2); > > > > + selabel_close(hnd1); > > > > + return EXIT_FAILURE; > > > > + } > > > > + > > > > + result = selabel_cmp(hnd1, hnd2); > > > > + > > > > + selabel_close(hnd2); > > > > + selabel_close(hnd1); > > > > + > > > > + switch (result) { > > > > + case SELABEL_SUBSET: > > > > + printf("spec %s is a subset of spec %s\n", file1, file2); > > > > + break; > > > > + case SELABEL_EQUAL: > > > > + printf("spec %s is equal to spec %s\n", file1, file2); > > > > + break; > > > > + case SELABEL_SUPERSET: > > > > + printf("spec %s is a superset of spec %s\n", file1, file2); > > > > + break; > > > > + case SELABEL_INCOMPARABLE: > > > > + printf("spec %s is uncompareable to spec %s\n", file1, file2); > > > > + break; > > > > + default: > > > > + fprintf(stderr, "ERROR: selabel_cmp - Unexpected result %d\n", result); > > > > + return EXIT_FAILURE; > > > > + } > > > > + > > > > + return EXIT_SUCCESS; > > > > + } > > > > +} > > > > -- > > > > 2.40.1 > > > >