From: Petr Lautrbach <lautrbach@redhat.com>
To: Vit Mojzis <vmojzis@redhat.com>, selinux@vger.kernel.org
Subject: Re: [PATCH v2] restorecon: Add option to count relabeled files
Date: Tue, 18 Nov 2025 09:59:25 +0100 [thread overview]
Message-ID: <87wm3nemxe.fsf@redhat.com> (raw)
In-Reply-To: <20251113171151.719458-1-vmojzis@redhat.com>
Vit Mojzis <vmojzis@redhat.com> writes:
> This is useful in case we want to check that a remediation using
> restorecon was successful (otherwise 0 is always returned, even if no
> files were relabeled).
>
> Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
> ---
> libselinux/include/selinux/restorecon.h | 15 +++++++++++
> libselinux/src/libselinux.map | 1 +
> libselinux/src/selinux_restorecon.c | 34 ++++++++++++++++++++++---
> policycoreutils/setfiles/restore.c | 12 ++++++---
> policycoreutils/setfiles/restore.h | 3 ++-
> policycoreutils/setfiles/restorecon.8 | 3 +++
> policycoreutils/setfiles/setfiles.c | 26 ++++++++++++++-----
> 7 files changed, 79 insertions(+), 15 deletions(-)
>
> diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
> index 0ccf73a6..65aaef23 100644
> --- a/libselinux/include/selinux/restorecon.h
> +++ b/libselinux/include/selinux/restorecon.h
> @@ -134,6 +134,11 @@ extern int selinux_restorecon_parallel(const char *pathname,
> */
> #define SELINUX_RESTORECON_SET_USER_ROLE 0x40000
>
> +/*
> + * Count the number of relabeled files (or would be relabeled if "nochange" was not set).
> + */
> + #define SELINUX_RESTORECON_COUNT_RELABELED 0x80000
> +
> /**
> * selinux_restorecon_set_sehandle - Set the global fc handle.
> * @hndl: specifies handle to set as the global fc handle.
> @@ -228,6 +233,16 @@ extern int selinux_restorecon_xattr(const char *pathname,
> */
> extern long unsigned selinux_restorecon_get_skipped_errors(void);
>
> +/* selinux_restorecon_get_relabeled_files - Get the number of relabeled files
> + *
> + * If SELINUX_RESTORECON_COUNT_RELABELED was passed to selinux_restorecon(3) or
> + * selinux_restorecon_parallel(3), this function returns the number of files
> + * that were successfully relabeled.
> + * If the SELINUX_RESTORECON_NOCHANGE flag was set, this function returns
> + * the number of files that would be relabeled.
> + */
> +extern long unsigned selinux_restorecon_get_relabeled_files(void);
> +
> #ifdef __cplusplus
> }
> #endif
> diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map
> index ab002f01..f21e089e 100644
> --- a/libselinux/src/libselinux.map
> +++ b/libselinux/src/libselinux.map
> @@ -244,6 +244,7 @@ LIBSELINUX_1.0 {
> LIBSELINUX_3.4 {
> global:
> selinux_restorecon_get_skipped_errors;
> + selinux_restorecon_get_relabeled_files;
> selinux_restorecon_parallel;
> } LIBSELINUX_1.0;
This will be a new symbol libselinux-3.10 so it needs to be in new section:
LIBSELINUX_3.10 {
global:
selinux_restorecon_get_relabeled_files;
} LIBSELINUX_3.9;
>
> diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
> index 681c69db..8fadf4d2 100644
> --- a/libselinux/src/selinux_restorecon.c
> +++ b/libselinux/src/selinux_restorecon.c
> @@ -69,6 +69,9 @@ static struct dir_xattr *dir_xattr_last;
> /* Number of errors ignored during the file tree walk. */
> static long unsigned skipped_errors;
>
> +/* Number of successfully relabeled files or files that would be relabeled */
> +static long unsigned relabeled_files;
> +
> /* restorecon_flags for passing to restorecon_sb() */
> struct rest_flags {
> bool nochange;
> @@ -88,6 +91,7 @@ struct rest_flags {
> bool warnonnomatch;
> bool conflicterror;
> bool count_errors;
> + bool count_relabeled;
> };
>
> static void restorecon_init(void)
> @@ -650,11 +654,12 @@ out:
> }
>
> static int restorecon_sb(const char *pathname, const struct stat *sb,
> - const struct rest_flags *flags, bool first)
> + const struct rest_flags *flags, bool first, bool *updated_out)
> {
> char *newcon = NULL;
> char *curcon = NULL;
> int rc;
> + bool updated = false;
> const char *lookup_path = pathname;
>
> if (rootpath) {
> @@ -736,7 +741,6 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
> }
>
> if (curcon == NULL || strcmp(curcon, newcon) != 0) {
> - bool updated = false;
>
> if (!flags->set_specctx && curcon &&
> (is_context_customizable(curcon) > 0)) {
> @@ -796,9 +800,14 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
> syslog(LOG_INFO, "labeling %s to %s\n",
> pathname, newcon);
> }
> +
> + /* Note: relabel counting handled by caller */
> +
> }
>
> out:
> + if (updated_out)
> + *updated_out = updated;
> rc = 0;
> out1:
> freecon(curcon);
> @@ -887,6 +896,7 @@ struct rest_state {
> bool abort;
> int error;
> long unsigned skipped_errors;
> + long unsigned relabeled_files;
> int saved_errno;
> pthread_mutex_t mutex;
> };
> @@ -1010,8 +1020,9 @@ loop_body:
> if (state->parallel)
> pthread_mutex_unlock(&state->mutex);
>
> + bool updated = false;
> error = restorecon_sb(ent_path, &ent_st, &state->flags,
> - first);
> + first, &updated);
>
> if (state->parallel) {
> pthread_mutex_lock(&state->mutex);
> @@ -1030,6 +1041,8 @@ loop_body:
> state->skipped_errors++;
> else
> state->error = error;
> + } else if (updated && state->flags.count_relabeled) {
> + state->relabeled_files++;
> }
> break;
> }
> @@ -1087,6 +1100,8 @@ static int selinux_restorecon_common(const char *pathname_orig,
> SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
> state.flags.count_errors = (restorecon_flags &
> SELINUX_RESTORECON_COUNT_ERRORS) ? true : false;
> + state.flags.count_relabeled = (restorecon_flags &
> + SELINUX_RESTORECON_COUNT_RELABELED) ? true : false;
> state.setrestorecondigest = true;
>
> state.head = NULL;
> @@ -1094,6 +1109,7 @@ static int selinux_restorecon_common(const char *pathname_orig,
> state.abort = false;
> state.error = 0;
> state.skipped_errors = 0;
> + state.relabeled_files = 0;
> state.saved_errno = 0;
>
> struct stat sb;
> @@ -1215,7 +1231,11 @@ static int selinux_restorecon_common(const char *pathname_orig,
> goto cleanup;
> }
>
> - error = restorecon_sb(pathname, &sb, &state.flags, true);
> + bool updated = false;
> + error = restorecon_sb(pathname, &sb, &state.flags, true, &updated);
> + if (updated && state.flags.count_relabeled) {
> + state.relabeled_files++;
> + }
> goto cleanup;
> }
>
> @@ -1341,6 +1361,7 @@ out:
> (void) fts_close(state.fts);
> errno = state.saved_errno;
> cleanup:
> + relabeled_files = state.relabeled_files;
> if (state.flags.add_assoc) {
> if (state.flags.verbose)
> filespec_eval();
> @@ -1618,3 +1639,8 @@ long unsigned selinux_restorecon_get_skipped_errors(void)
> {
> return skipped_errors;
> }
> +
> +long unsigned selinux_restorecon_get_relabeled_files(void)
> +{
> + return relabeled_files;
> +}
> diff --git a/policycoreutils/setfiles/restore.c b/policycoreutils/setfiles/restore.c
> index 2c031ccc..07582e7c 100644
> --- a/policycoreutils/setfiles/restore.c
> +++ b/policycoreutils/setfiles/restore.c
> @@ -43,7 +43,7 @@ void restore_init(struct restore_opts *opts)
> opts->syslog_changes | opts->log_matches |
> opts->ignore_noent | opts->ignore_mounts |
> opts->mass_relabel | opts->conflict_error |
> - opts->count_errors;
> + opts->count_errors | opts->count_relabeled;
>
> /* Use setfiles, restorecon and restorecond own handles */
> selinux_restorecon_set_sehandle(opts->hnd);
> @@ -75,7 +75,7 @@ void restore_finish(void)
> }
>
> int process_glob(char *name, struct restore_opts *opts, size_t nthreads,
> - long unsigned *skipped_errors)
> + long unsigned *skipped_errors, long unsigned *relabeled_files)
> {
> glob_t globbuf;
> size_t i, len;
> @@ -99,8 +99,12 @@ int process_glob(char *name, struct restore_opts *opts, size_t nthreads,
> nthreads);
> if (rc < 0)
> errors = rc;
> - else if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_ERRORS)
> - *skipped_errors += selinux_restorecon_get_skipped_errors();
> + else {
> + if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_ERRORS)
> + *skipped_errors += selinux_restorecon_get_skipped_errors();
> + if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_RELABELED)
> + *relabeled_files += selinux_restorecon_get_relabeled_files();
> + }
> }
>
> globfree(&globbuf);
> diff --git a/policycoreutils/setfiles/restore.h b/policycoreutils/setfiles/restore.h
> index 95afb960..36f73059 100644
> --- a/policycoreutils/setfiles/restore.h
> +++ b/policycoreutils/setfiles/restore.h
> @@ -37,6 +37,7 @@ struct restore_opts {
> unsigned int ignore_mounts;
> unsigned int conflict_error;
> unsigned int count_errors;
> + unsigned int count_relabeled;
> /* restorecon_flags holds | of above for restore_init() */
> unsigned int restorecon_flags;
> char *rootpath;
> @@ -52,7 +53,7 @@ void restore_init(struct restore_opts *opts);
> void restore_finish(void);
> void add_exclude(const char *directory);
> int process_glob(char *name, struct restore_opts *opts, size_t nthreads,
> - long unsigned *skipped_errors);
> + long unsigned *skipped_errors, long unsigned *relabeled_files);
> extern char **exclude_list;
>
> #endif
> diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8
> index 1134420e..b7ff9715 100644
> --- a/policycoreutils/setfiles/restorecon.8
> +++ b/policycoreutils/setfiles/restorecon.8
> @@ -153,6 +153,9 @@ display warnings about entries that had no matching files by outputting the
> .BR selabel_stats (3)
> results.
> .TP
> +.B \-c
> +count and display the number of (would be) relabeled files. The exit code will be set to 0 only if at least one file is relabeled.
> +.TP
> .B \-0
> the separator for the input items is assumed to be the null character
> (instead of the white space). The quotes and the backslash characters are
> diff --git a/policycoreutils/setfiles/setfiles.c b/policycoreutils/setfiles/setfiles.c
> index 31034316..351940f3 100644
> --- a/policycoreutils/setfiles/setfiles.c
> +++ b/policycoreutils/setfiles/setfiles.c
> @@ -35,8 +35,8 @@ static __attribute__((__noreturn__)) void usage(const char *const name)
> {
> if (iamrestorecon) {
> fprintf(stderr,
> - "usage: %s [-iIDFUmnprRv0xT] [-e excludedir] pathname...\n"
> - "usage: %s [-iIDFUmnprRv0xT] [-e excludedir] -f filename\n",
> + "usage: %s [-ciIDFUmnprRv0xT] [-e excludedir] pathname...\n"
> + "usage: %s [-ciIDFUmnprRv0xT] [-e excludedir] -f filename\n",
> name, name);
> } else {
> fprintf(stderr,
> @@ -146,11 +146,12 @@ int main(int argc, char **argv)
> size_t buf_len, nthreads = 1;
> const char *base;
> int errors = 0;
> - const char *ropts = "e:f:hiIDlmno:pqrsvFURW0xT:";
> + const char *ropts = "ce:f:hiIDlmno:pqrsvFURW0xT:";
> const char *sopts = "c:de:f:hiIDlmno:pqr:svACEFUR:W0T:";
> const char *opts;
> union selinux_callback cb;
> long unsigned skipped_errors;
> + long unsigned relabeled_files;
>
> /* Initialize variables */
> memset(&r_opts, 0, sizeof(r_opts));
> @@ -160,6 +161,7 @@ int main(int argc, char **argv)
> request_digest = 0;
> policyfile = NULL;
> skipped_errors = 0;
> + relabeled_files = 0;
>
> if (!argv[0]) {
> fprintf(stderr, "Called without required program name!\n");
> @@ -223,7 +225,10 @@ int main(int argc, char **argv)
> while ((opt = getopt(argc, argv, opts)) > 0) {
> switch (opt) {
> case 'c':
> - {
> + if (iamrestorecon) {
> + r_opts.count_relabeled = SELINUX_RESTORECON_COUNT_RELABELED;
> + break;
> + } else {
> FILE *policystream;
>
> policyfile = optarg;
> @@ -457,14 +462,14 @@ int main(int argc, char **argv)
> if (!strcmp(buf, "/"))
> r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
> errors |= process_glob(buf, &r_opts, nthreads,
> - &skipped_errors) < 0;
> + &skipped_errors, &relabeled_files) < 0;
> }
> if (strcmp(input_filename, "-") != 0)
> fclose(f);
> } else {
> for (i = optind; i < argc; i++)
> errors |= process_glob(argv[i], &r_opts, nthreads,
> - &skipped_errors) < 0;
> + &skipped_errors, &relabeled_files) < 0;
> }
>
> if (r_opts.mass_relabel && !r_opts.nochange)
> @@ -479,5 +484,14 @@ int main(int argc, char **argv)
> if (r_opts.progress)
> fprintf(stdout, "\n");
>
> + /* Output relabeled file count if requested */
> + if (r_opts.count_relabeled) {
> + long unsigned relabeled_count = selinux_restorecon_get_relabeled_files();
> + printf("Relabeled %lu files\n", relabeled_count);
> +
> + /* Set exit code to 0 if at least one file was relabeled */
> + exit(errors ? -1 : relabeled_count ? 0 : 1);
> + }
> +
> exit(errors ? -1 : skipped_errors ? 1 : 0);
> }
> --
> 2.51.1
next prev parent reply other threads:[~2025-11-18 8:59 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-10 18:05 [PATCH] restorecon: Add option to count number of relabeled files Vit Mojzis
2025-11-10 20:21 ` Stephen Smalley
2025-11-10 20:23 ` William Roberts
2025-11-10 20:37 ` William Roberts
2025-11-11 14:44 ` Daniel Burgener
[not found] ` <CAFftDdoTR5ae1qORSjPuOj5ea1O15qtgrRiadhTp2HMh926swg@mail.gmail.com>
2025-11-13 0:43 ` William Roberts
2025-11-13 17:11 ` [PATCH v2] restorecon: Add option to count " Vit Mojzis
2025-11-13 20:17 ` William Roberts
2025-11-18 8:59 ` Petr Lautrbach [this message]
2025-11-18 20:11 ` [PATCH v3] " Vit Mojzis
2025-11-13 17:35 ` [PATCH] restorecon: Add option to count number of " Daniel Burgener
2025-11-13 19:29 ` William Roberts
2025-11-13 19:32 ` Daniel Burgener
2025-11-13 19:39 ` William Roberts
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wm3nemxe.fsf@redhat.com \
--to=lautrbach@redhat.com \
--cc=selinux@vger.kernel.org \
--cc=vmojzis@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).