All of lore.kernel.org
 help / color / mirror / Atom feed
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


  parent reply	other threads:[~2025-11-18  8:59 UTC|newest]

Thread overview: 16+ 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
2026-01-23 15:13                 ` James Carter
2026-01-28 20:28                   ` James Carter
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.