selinux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] restorecon: Add option to count number of relabeled files
@ 2025-11-10 18:05 Vit Mojzis
  2025-11-10 20:21 ` Stephen Smalley
  2025-11-10 20:23 ` William Roberts
  0 siblings, 2 replies; 14+ messages in thread
From: Vit Mojzis @ 2025-11-10 18:05 UTC (permalink / raw)
  To: selinux

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 where relabeled).

Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
---
 libselinux/include/selinux/restorecon.h |  9 +++++++++
 libselinux/src/libselinux.map           |  1 +
 libselinux/src/selinux_restorecon.c     | 14 ++++++++++++++
 policycoreutils/setfiles/restore.h      |  1 +
 policycoreutils/setfiles/restorecon.8   |  3 +++
 policycoreutils/setfiles/setfiles.c     | 24 ++++++++++++++++++++----
 6 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
index 0ccf73a6..736481bb 100644
--- a/libselinux/include/selinux/restorecon.h
+++ b/libselinux/include/selinux/restorecon.h
@@ -228,6 +228,15 @@ 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(3) or selinux_restorecon_parallel(3) was called,
+ * 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;
 
diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
index 681c69db..6e9a159e 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;
@@ -796,6 +799,10 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
 				syslog(LOG_INFO, "labeling %s to %s\n",
 					    pathname, newcon);
 		}
+
+		/* Count relabeled files (or would be relabeled if "nochange" was not set) */
+		relabeled_files++;
+
 	}
 
 out:
@@ -1096,6 +1103,8 @@ static int selinux_restorecon_common(const char *pathname_orig,
 	state.skipped_errors = 0;
 	state.saved_errno = 0;
 
+	relabeled_files = 0;
+
 	struct stat sb;
 	char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
 	char *paths[2] = { NULL, NULL };
@@ -1618,3 +1627,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.h b/policycoreutils/setfiles/restore.h
index 95afb960..7c949c1c 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;
diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8
index 1134420e..e9bd16b6 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 the number of relabeled files (capped at 254).
+.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..6323f56c 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,7 +146,7 @@ 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;
@@ -223,7 +223,10 @@ int main(int argc, char **argv)
 	while ((opt = getopt(argc, argv, opts)) > 0) {
 		switch (opt) {
 		case 'c':
-			{
+			if (iamrestorecon) {
+				r_opts.count_relabeled = 1;
+				break;
+			} else {
 				FILE *policystream;
 
 				policyfile = optarg;
@@ -479,5 +482,18 @@ 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 number of relabeled files (capped at 254) */
+		if (errors) {
+			exit(-1);
+		} else {
+			exit(relabeled_count > 254 ? 254 : (int)relabeled_count);
+		}
+	}
+
 	exit(errors ? -1 : skipped_errors ? 1 : 0);
 }
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  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
  1 sibling, 0 replies; 14+ messages in thread
From: Stephen Smalley @ 2025-11-10 20:21 UTC (permalink / raw)
  To: Vit Mojzis; +Cc: selinux

On Mon, Nov 10, 2025 at 1:11 PM Vit Mojzis <vmojzis@redhat.com> wrote:
>
> 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 where relabeled).
>
> Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
> ---
>  libselinux/include/selinux/restorecon.h |  9 +++++++++
>  libselinux/src/libselinux.map           |  1 +
>  libselinux/src/selinux_restorecon.c     | 14 ++++++++++++++
>  policycoreutils/setfiles/restore.h      |  1 +
>  policycoreutils/setfiles/restorecon.8   |  3 +++
>  policycoreutils/setfiles/setfiles.c     | 24 ++++++++++++++++++++----
>  6 files changed, 48 insertions(+), 4 deletions(-)
>

> diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
> index 681c69db..6e9a159e 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;

Looking at skipped_errors handling, there is a skipped_errors field in
struct rest_state, which is initialized to 0 in
selinux_restorecon_common(), incremented on errors only if
state->flags.count_errors is set, and then copied to the static
skipped_errors before returning. Any particular reason not to follow
that model?  Otherwise, you seemingly don't handle the multi-threaded
situation correctly AFAICT.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  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
  1 sibling, 1 reply; 14+ messages in thread
From: William Roberts @ 2025-11-10 20:23 UTC (permalink / raw)
  To: Vit Mojzis; +Cc: selinux

On Mon, Nov 10, 2025 at 12:11 PM Vit Mojzis <vmojzis@redhat.com> wrote:
>
> 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 where relabeled).
>
> Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
> ---
>  libselinux/include/selinux/restorecon.h |  9 +++++++++
>  libselinux/src/libselinux.map           |  1 +
>  libselinux/src/selinux_restorecon.c     | 14 ++++++++++++++
>  policycoreutils/setfiles/restore.h      |  1 +
>  policycoreutils/setfiles/restorecon.8   |  3 +++
>  policycoreutils/setfiles/setfiles.c     | 24 ++++++++++++++++++++----
>  6 files changed, 48 insertions(+), 4 deletions(-)
>
> diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
> index 0ccf73a6..736481bb 100644
> --- a/libselinux/include/selinux/restorecon.h
> +++ b/libselinux/include/selinux/restorecon.h
> @@ -228,6 +228,15 @@ 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(3) or selinux_restorecon_parallel(3) was called,
> + * 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;
>
> diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
> index 681c69db..6e9a159e 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;
> @@ -796,6 +799,10 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
>                                 syslog(LOG_INFO, "labeling %s to %s\n",
>                                             pathname, newcon);
>                 }
> +
> +               /* Count relabeled files (or would be relabeled if "nochange" was not set) */
> +               relabeled_files++;
> +
>         }
>
>  out:
> @@ -1096,6 +1103,8 @@ static int selinux_restorecon_common(const char *pathname_orig,
>         state.skipped_errors = 0;
>         state.saved_errno = 0;
>
> +       relabeled_files = 0;
> +
>         struct stat sb;
>         char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
>         char *paths[2] = { NULL, NULL };
> @@ -1618,3 +1627,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.h b/policycoreutils/setfiles/restore.h
> index 95afb960..7c949c1c 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;

It's using unsigned int, but other places use unsigned long. Wouldn't we want to
keep this consistent to prevent truncating long to int? Do we care
about rollovers?
It's unlikely to happen, but some file systems don't have bounds on
the number of files,
also can't restorecon go across fs boundaries, so the count could be
high, albeit unlikely.

>         /* restorecon_flags holds | of above for restore_init() */
>         unsigned int restorecon_flags;
>         char *rootpath;
> diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8
> index 1134420e..e9bd16b6 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 the number of relabeled files (capped at 254).
> +.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..6323f56c 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,7 +146,7 @@ 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;
> @@ -223,7 +223,10 @@ int main(int argc, char **argv)
>         while ((opt = getopt(argc, argv, opts)) > 0) {
>                 switch (opt) {
>                 case 'c':
> -                       {
> +                       if (iamrestorecon) {
> +                               r_opts.count_relabeled = 1;
> +                               break;
> +                       } else {
>                                 FILE *policystream;
>
>                                 policyfile = optarg;
> @@ -479,5 +482,18 @@ 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 number of relabeled files (capped at 254) */
> +               if (errors) {
> +                       exit(-1);
> +               } else {
> +                       exit(relabeled_count > 254 ? 254 : (int)relabeled_count);
> +               }

This I don't like. By convention, certain exit codes mean things. If
someone is debugging a restorecon with this option set,
and sees exit code 127, they're going to think, "command not found".
There are other codes to, like 126 not executable,
128 + signum, ie 137 is I was killed by SIGKILL Additionally, the
truncation makes it less useful.

I'd rather see this just exit 0 or 1, if folks need the count they can
parse it from stdout.

I'm no longer an SELinux maintainer, so don't let my nack stop anyone.

> +       }
> +
>         exit(errors ? -1 : skipped_errors ? 1 : 0);
>  }
> --
> 2.51.0
>
>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  2025-11-10 20:23 ` William Roberts
@ 2025-11-10 20:37   ` William Roberts
  2025-11-11 14:44     ` Daniel Burgener
  0 siblings, 1 reply; 14+ messages in thread
From: William Roberts @ 2025-11-10 20:37 UTC (permalink / raw)
  To: Vit Mojzis; +Cc: selinux

On Mon, Nov 10, 2025 at 2:23 PM William Roberts
<bill.c.roberts@gmail.com> wrote:
>
> On Mon, Nov 10, 2025 at 12:11 PM Vit Mojzis <vmojzis@redhat.com> wrote:
> >
> > 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 where relabeled).
> >
> > Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
> > ---
> >  libselinux/include/selinux/restorecon.h |  9 +++++++++
> >  libselinux/src/libselinux.map           |  1 +
> >  libselinux/src/selinux_restorecon.c     | 14 ++++++++++++++
> >  policycoreutils/setfiles/restore.h      |  1 +
> >  policycoreutils/setfiles/restorecon.8   |  3 +++
> >  policycoreutils/setfiles/setfiles.c     | 24 ++++++++++++++++++++----
> >  6 files changed, 48 insertions(+), 4 deletions(-)
> >
> > diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
> > index 0ccf73a6..736481bb 100644
> > --- a/libselinux/include/selinux/restorecon.h
> > +++ b/libselinux/include/selinux/restorecon.h
> > @@ -228,6 +228,15 @@ 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(3) or selinux_restorecon_parallel(3) was called,
> > + * 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;
> >
> > diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
> > index 681c69db..6e9a159e 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;
> > @@ -796,6 +799,10 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
> >                                 syslog(LOG_INFO, "labeling %s to %s\n",
> >                                             pathname, newcon);
> >                 }
> > +
> > +               /* Count relabeled files (or would be relabeled if "nochange" was not set) */
> > +               relabeled_files++;
> > +
> >         }
> >
> >  out:
> > @@ -1096,6 +1103,8 @@ static int selinux_restorecon_common(const char *pathname_orig,
> >         state.skipped_errors = 0;
> >         state.saved_errno = 0;
> >
> > +       relabeled_files = 0;
> > +
> >         struct stat sb;
> >         char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
> >         char *paths[2] = { NULL, NULL };
> > @@ -1618,3 +1627,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.h b/policycoreutils/setfiles/restore.h
> > index 95afb960..7c949c1c 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;
>
> It's using unsigned int, but other places use unsigned long. Wouldn't we want to
> keep this consistent to prevent truncating long to int? Do we care
> about rollovers?
> It's unlikely to happen, but some file systems don't have bounds on
> the number of files,
> also can't restorecon go across fs boundaries, so the count could be
> high, albeit unlikely.
>
> >         /* restorecon_flags holds | of above for restore_init() */
> >         unsigned int restorecon_flags;
> >         char *rootpath;
> > diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8
> > index 1134420e..e9bd16b6 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 the number of relabeled files (capped at 254).
> > +.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..6323f56c 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,7 +146,7 @@ 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;
> > @@ -223,7 +223,10 @@ int main(int argc, char **argv)
> >         while ((opt = getopt(argc, argv, opts)) > 0) {
> >                 switch (opt) {
> >                 case 'c':
> > -                       {
> > +                       if (iamrestorecon) {
> > +                               r_opts.count_relabeled = 1;
> > +                               break;
> > +                       } else {
> >                                 FILE *policystream;
> >
> >                                 policyfile = optarg;
> > @@ -479,5 +482,18 @@ 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 number of relabeled files (capped at 254) */
> > +               if (errors) {
> > +                       exit(-1);
> > +               } else {
> > +                       exit(relabeled_count > 254 ? 254 : (int)relabeled_count);
> > +               }
>
> This I don't like. By convention, certain exit codes mean things. If
> someone is debugging a restorecon with this option set,
> and sees exit code 127, they're going to think, "command not found".
> There are other codes to, like 126 not executable,
> 128 + signum, ie 137 is I was killed by SIGKILL Additionally, the
> truncation makes it less useful.

I forgot to mention as well, If a user enables this option in a script
set to exit
on error, they have to code around this command under specific options.

>
> I'd rather see this just exit 0 or 1, if folks need the count they can
> parse it from stdout.
>
> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.
>
> > +       }
> > +
> >         exit(errors ? -1 : skipped_errors ? 1 : 0);
> >  }
> > --
> > 2.51.0
> >
> >

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  2025-11-10 20:37   ` William Roberts
@ 2025-11-11 14:44     ` Daniel Burgener
       [not found]       ` <CAFftDdoTR5ae1qORSjPuOj5ea1O15qtgrRiadhTp2HMh926swg@mail.gmail.com>
  0 siblings, 1 reply; 14+ messages in thread
From: Daniel Burgener @ 2025-11-11 14:44 UTC (permalink / raw)
  To: William Roberts, Vit Mojzis; +Cc: selinux

On 11/10/2025 3:37 PM, William Roberts wrote:
> On Mon, Nov 10, 2025 at 2:23 PM William Roberts
> <bill.c.roberts@gmail.com> wrote:
>>
>> On Mon, Nov 10, 2025 at 12:11 PM Vit Mojzis <vmojzis@redhat.com> wrote:
>>>
>>> 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 where relabeled).
>>>
>>> Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
>>> ---
>>>   libselinux/include/selinux/restorecon.h |  9 +++++++++
>>>   libselinux/src/libselinux.map           |  1 +
>>>   libselinux/src/selinux_restorecon.c     | 14 ++++++++++++++
>>>   policycoreutils/setfiles/restore.h      |  1 +
>>>   policycoreutils/setfiles/restorecon.8   |  3 +++
>>>   policycoreutils/setfiles/setfiles.c     | 24 ++++++++++++++++++++----
>>>   6 files changed, 48 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
>>> index 0ccf73a6..736481bb 100644
>>> --- a/libselinux/include/selinux/restorecon.h
>>> +++ b/libselinux/include/selinux/restorecon.h
>>> @@ -228,6 +228,15 @@ 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(3) or selinux_restorecon_parallel(3) was called,
>>> + * 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;
>>>
>>> diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
>>> index 681c69db..6e9a159e 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;
>>> @@ -796,6 +799,10 @@ static int restorecon_sb(const char *pathname, const struct stat *sb,
>>>                                  syslog(LOG_INFO, "labeling %s to %s\n",
>>>                                              pathname, newcon);
>>>                  }
>>> +
>>> +               /* Count relabeled files (or would be relabeled if "nochange" was not set) */
>>> +               relabeled_files++;
>>> +
>>>          }
>>>
>>>   out:
>>> @@ -1096,6 +1103,8 @@ static int selinux_restorecon_common(const char *pathname_orig,
>>>          state.skipped_errors = 0;
>>>          state.saved_errno = 0;
>>>
>>> +       relabeled_files = 0;
>>> +
>>>          struct stat sb;
>>>          char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
>>>          char *paths[2] = { NULL, NULL };
>>> @@ -1618,3 +1627,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.h b/policycoreutils/setfiles/restore.h
>>> index 95afb960..7c949c1c 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;
>>
>> It's using unsigned int, but other places use unsigned long. Wouldn't we want to
>> keep this consistent to prevent truncating long to int? Do we care
>> about rollovers?
>> It's unlikely to happen, but some file systems don't have bounds on
>> the number of files,
>> also can't restorecon go across fs boundaries, so the count could be
>> high, albeit unlikely.
>>
>>>          /* restorecon_flags holds | of above for restore_init() */
>>>          unsigned int restorecon_flags;
>>>          char *rootpath;
>>> diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8
>>> index 1134420e..e9bd16b6 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 the number of relabeled files (capped at 254).
>>> +.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..6323f56c 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,7 +146,7 @@ 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;
>>> @@ -223,7 +223,10 @@ int main(int argc, char **argv)
>>>          while ((opt = getopt(argc, argv, opts)) > 0) {
>>>                  switch (opt) {
>>>                  case 'c':
>>> -                       {
>>> +                       if (iamrestorecon) {
>>> +                               r_opts.count_relabeled = 1;
>>> +                               break;
>>> +                       } else {
>>>                                  FILE *policystream;
>>>
>>>                                  policyfile = optarg;
>>> @@ -479,5 +482,18 @@ 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 number of relabeled files (capped at 254) */
>>> +               if (errors) {
>>> +                       exit(-1);
>>> +               } else {
>>> +                       exit(relabeled_count > 254 ? 254 : (int)relabeled_count);
>>> +               }
>>
>> This I don't like. By convention, certain exit codes mean things. If
>> someone is debugging a restorecon with this option set,
>> and sees exit code 127, they're going to think, "command not found".
>> There are other codes to, like 126 not executable,
>> 128 + signum, ie 137 is I was killed by SIGKILL Additionally, the
>> truncation makes it less useful.
> 
> I forgot to mention as well, If a user enables this option in a script
> set to exit
> on error, they have to code around this command under specific options.
> 
>>
>> I'd rather see this just exit 0 or 1, if folks need the count they can
>> parse it from stdout.
>>
>> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.

We have a need for a similar use case in terms of ensuring that 
restorecon actually performed relabeling, but I agree that I don't think 
this patch as is would meet our needs.

One thing that might make the patch more usable and address these 
comments would be to instead pass the expected number of relabels as an 
argument to restorecon and then return success if the relabel count == 
the expected count.  That avoids all the problems around exit code 
handling while still verifying the count.

The other problem though is that in the presence of globbing it's not 
clear that getting the expected number of files relabeled means that you 
actually relabeled the files you expected to.  But I guess the answer to 
that is just "don't use the count feature with globbing".  Even without 
globbing though, if you don't relabel all the files, you don't know 
which one you skipped without extra handling, which seems like you 
really don't need to know the number relabeled as much as whether it was 
the number you expected, which seems like a point in favor of "pass the 
expected count".

-Daniel

>>
>>> +       }
>>> +
>>>          exit(errors ? -1 : skipped_errors ? 1 : 0);
>>>   }
>>> --
>>> 2.51.0
>>>
>>>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
       [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 17:35           ` [PATCH] restorecon: Add option to count number of " Daniel Burgener
  0 siblings, 2 replies; 14+ messages in thread
From: William Roberts @ 2025-11-13  0:43 UTC (permalink / raw)
  To: Daniel Burgener, SElinux list

On Tue, Nov 11, 2025 at 10:34 AM William Roberts
<bill.c.roberts@gmail.com> wrote:
>
> <snip>
> > >> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.
> >
> > We have a need for a similar use case in terms of ensuring that
> > restorecon actually performed relabeling, but I agree that I don't think
> > this patch as is would meet our needs.
> >
> > One thing that might make the patch more usable and address these
> > comments would be to instead pass the expected number of relabels as an
> > argument to restorecon and then return success if the relabel count ==
> > the expected count.  That avoids all the problems around exit code
> > handling while still verifying the count.
> >
> > The other problem though is that in the presence of globbing it's not
> > clear that getting the expected number of files relabeled means that you
> > actually relabeled the files you expected to.  But I guess the answer to
> > that is just "don't use the count feature with globbing".  Even without
> > globbing though, if you don't relabel all the files, you don't know
> > which one you skipped without extra handling, which seems like you
> > really don't need to know the number relabeled as much as whether it was
> > the number you expected, which seems like a point in favor of "pass the
> > expected count".
> >
>

Sorry I accidentally sent this only to Daniel, adding back the list.

With -v doesn't restorecon show what would be changed? Perhaps it
would be better
to add an option that produces some standard formatting for an audit
trail and that output
could include various statistics. Then folks could parse those
records. I see -p does some form
of progress/status meter as well, for whatever that is worth.

<snip>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2] restorecon: Add option to count relabeled files
  2025-11-13  0:43         ` William Roberts
@ 2025-11-13 17:11           ` Vit Mojzis
  2025-11-13 20:17             ` William Roberts
  2025-11-18  8:59             ` Petr Lautrbach
  2025-11-13 17:35           ` [PATCH] restorecon: Add option to count number of " Daniel Burgener
  1 sibling, 2 replies; 14+ messages in thread
From: Vit Mojzis @ 2025-11-13 17:11 UTC (permalink / raw)
  To: selinux

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;
 
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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  2025-11-13  0:43         ` William Roberts
  2025-11-13 17:11           ` [PATCH v2] restorecon: Add option to count " Vit Mojzis
@ 2025-11-13 17:35           ` Daniel Burgener
  2025-11-13 19:29             ` William Roberts
  1 sibling, 1 reply; 14+ messages in thread
From: Daniel Burgener @ 2025-11-13 17:35 UTC (permalink / raw)
  To: William Roberts, SElinux list

On 11/12/2025 7:43 PM, William Roberts wrote:
> On Tue, Nov 11, 2025 at 10:34 AM William Roberts
> <bill.c.roberts@gmail.com> wrote:
>>
>> <snip>
>>>>> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.
>>>
>>> We have a need for a similar use case in terms of ensuring that
>>> restorecon actually performed relabeling, but I agree that I don't think
>>> this patch as is would meet our needs.
>>>
>>> One thing that might make the patch more usable and address these
>>> comments would be to instead pass the expected number of relabels as an
>>> argument to restorecon and then return success if the relabel count ==
>>> the expected count.  That avoids all the problems around exit code
>>> handling while still verifying the count.
>>>
>>> The other problem though is that in the presence of globbing it's not
>>> clear that getting the expected number of files relabeled means that you
>>> actually relabeled the files you expected to.  But I guess the answer to
>>> that is just "don't use the count feature with globbing".  Even without
>>> globbing though, if you don't relabel all the files, you don't know
>>> which one you skipped without extra handling, which seems like you
>>> really don't need to know the number relabeled as much as whether it was
>>> the number you expected, which seems like a point in favor of "pass the
>>> expected count".
>>>
>>
> 
> Sorry I accidentally sent this only to Daniel, adding back the list.
> 
> With -v doesn't restorecon show what would be changed? Perhaps it
> would be better
> to add an option that produces some standard formatting for an audit
> trail and that output
> could include various statistics. Then folks could parse those
> records. I see -p does some form
> of progress/status meter as well, for whatever that is worth.
> 
> <snip>

My two cents FWIW is that being able to see whether you actually 
relabeled via exit status is way more useful than having to parse output 
to get at that info.  There's no need for the complexity of the wrapper, 
no opportunities for parser bugs, and you can just directly succeed/fail 
a systemd unit or bash script based on the return code.

-Daniel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  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
  0 siblings, 1 reply; 14+ messages in thread
From: William Roberts @ 2025-11-13 19:29 UTC (permalink / raw)
  To: Daniel Burgener; +Cc: SElinux list

On Thu, Nov 13, 2025 at 11:36 AM Daniel Burgener
<dburgener@linux.microsoft.com> wrote:
>
> On 11/12/2025 7:43 PM, William Roberts wrote:
> > On Tue, Nov 11, 2025 at 10:34 AM William Roberts
> > <bill.c.roberts@gmail.com> wrote:
> >>
> >> <snip>
> >>>>> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.
> >>>
> >>> We have a need for a similar use case in terms of ensuring that
> >>> restorecon actually performed relabeling, but I agree that I don't think
> >>> this patch as is would meet our needs.
> >>>
> >>> One thing that might make the patch more usable and address these
> >>> comments would be to instead pass the expected number of relabels as an
> >>> argument to restorecon and then return success if the relabel count ==
> >>> the expected count.  That avoids all the problems around exit code
> >>> handling while still verifying the count.
> >>>
> >>> The other problem though is that in the presence of globbing it's not
> >>> clear that getting the expected number of files relabeled means that you
> >>> actually relabeled the files you expected to.  But I guess the answer to
> >>> that is just "don't use the count feature with globbing".  Even without
> >>> globbing though, if you don't relabel all the files, you don't know
> >>> which one you skipped without extra handling, which seems like you
> >>> really don't need to know the number relabeled as much as whether it was
> >>> the number you expected, which seems like a point in favor of "pass the
> >>> expected count".
> >>>
> >>
> >
> > Sorry I accidentally sent this only to Daniel, adding back the list.
> >
> > With -v doesn't restorecon show what would be changed? Perhaps it
> > would be better
> > to add an option that produces some standard formatting for an audit
> > trail and that output
> > could include various statistics. Then folks could parse those
> > records. I see -p does some form
> > of progress/status meter as well, for whatever that is worth.
> >
> > <snip>
>
> My two cents FWIW is that being able to see whether you actually
> relabeled via exit status is way more useful than having to parse output
> to get at that info.  There's no need for the complexity of the wrapper,
> no opportunities for parser bugs, and you can just directly succeed/fail
> a systemd unit or bash script based on the return code.

How would someone distinguish between error and one file labeled? It's
also clipped to a very small
number, so will it really be useful on larger file systems?

We can simplify the output to stdout is just the number then no
parsing needed, albeit
we may want to look at the verbose option and define a format for that
as well (not now, future work).
So folks could do -vc or -c and have a way to get an audit trail of
files and a count independently.
The last line will always be the number in base 10 with a newline.
POSIX shells will strip that
in assignments from command substitution, so you can still just use
the number directly.

For -c:
set -e
x="$(restorecon -c)"
if x == 400; then
  whatever
fi

For -cv:
x="$(restorecon -cv | tail -n1)"
if x == 400; then
  whatever
fi

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  2025-11-13 19:29             ` William Roberts
@ 2025-11-13 19:32               ` Daniel Burgener
  2025-11-13 19:39                 ` William Roberts
  0 siblings, 1 reply; 14+ messages in thread
From: Daniel Burgener @ 2025-11-13 19:32 UTC (permalink / raw)
  To: William Roberts; +Cc: SElinux list

On 11/13/2025 2:29 PM, William Roberts wrote:
> On Thu, Nov 13, 2025 at 11:36 AM Daniel Burgener
> <dburgener@linux.microsoft.com> wrote:
>>
>> On 11/12/2025 7:43 PM, William Roberts wrote:
>>> On Tue, Nov 11, 2025 at 10:34 AM William Roberts
>>> <bill.c.roberts@gmail.com> wrote:
>>>>
>>>> <snip>
>>>>>>> I'm no longer an SELinux maintainer, so don't let my nack stop anyone.
>>>>>
>>>>> We have a need for a similar use case in terms of ensuring that
>>>>> restorecon actually performed relabeling, but I agree that I don't think
>>>>> this patch as is would meet our needs.
>>>>>
>>>>> One thing that might make the patch more usable and address these
>>>>> comments would be to instead pass the expected number of relabels as an
>>>>> argument to restorecon and then return success if the relabel count ==
>>>>> the expected count.  That avoids all the problems around exit code
>>>>> handling while still verifying the count.
>>>>>
>>>>> The other problem though is that in the presence of globbing it's not
>>>>> clear that getting the expected number of files relabeled means that you
>>>>> actually relabeled the files you expected to.  But I guess the answer to
>>>>> that is just "don't use the count feature with globbing".  Even without
>>>>> globbing though, if you don't relabel all the files, you don't know
>>>>> which one you skipped without extra handling, which seems like you
>>>>> really don't need to know the number relabeled as much as whether it was
>>>>> the number you expected, which seems like a point in favor of "pass the
>>>>> expected count".
>>>>>
>>>>
>>>
>>> Sorry I accidentally sent this only to Daniel, adding back the list.
>>>
>>> With -v doesn't restorecon show what would be changed? Perhaps it
>>> would be better
>>> to add an option that produces some standard formatting for an audit
>>> trail and that output
>>> could include various statistics. Then folks could parse those
>>> records. I see -p does some form
>>> of progress/status meter as well, for whatever that is worth.
>>>
>>> <snip>
>>
>> My two cents FWIW is that being able to see whether you actually
>> relabeled via exit status is way more useful than having to parse output
>> to get at that info.  There's no need for the complexity of the wrapper,
>> no opportunities for parser bugs, and you can just directly succeed/fail
>> a systemd unit or bash script based on the return code.
> 
> How would someone distinguish between error and one file labeled? It's
> also clipped to a very small
> number, so will it really be useful on larger file systems?

No, I agree with your concern about returning the number of files 
relabeled.  My suggestion was:

 > One thing that might make the patch more usable and address these
 > comments would be to instead pass the expected number of relabels as an
 > argument to restorecon and then return success if the relabel count ==
 > the expected count.  That avoids all the problems around exit code
 > handling while still verifying the count.

> 
> We can simplify the output to stdout is just the number then no
> parsing needed, albeit
> we may want to look at the verbose option and define a format for that
> as well (not now, future work).
> So folks could do -vc or -c and have a way to get an audit trail of
> files and a count independently.
> The last line will always be the number in base 10 with a newline.
> POSIX shells will strip that
> in assignments from command substitution, so you can still just use
> the number directly.
> 
> For -c:
> set -e
> x="$(restorecon -c)"
> if x == 400; then
>    whatever
> fi
> 
> For -cv:
> x="$(restorecon -cv | tail -n1)"
> if x == 400; then
>    whatever
> fi

That may not strictly be "parsing", but it's still added complexity vs 
checking a return code.

-Daniel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] restorecon: Add option to count number of relabeled files
  2025-11-13 19:32               ` Daniel Burgener
@ 2025-11-13 19:39                 ` William Roberts
  0 siblings, 0 replies; 14+ messages in thread
From: William Roberts @ 2025-11-13 19:39 UTC (permalink / raw)
  To: Daniel Burgener; +Cc: SElinux list

<snip>

> >>
> >> My two cents FWIW is that being able to see whether you actually
> >> relabeled via exit status is way more useful than having to parse output
> >> to get at that info.  There's no need for the complexity of the wrapper,
> >> no opportunities for parser bugs, and you can just directly succeed/fail
> >> a systemd unit or bash script based on the return code.
> >
> > How would someone distinguish between error and one file labeled? It's
> > also clipped to a very small
> > number, so will it really be useful on larger file systems?
>
> No, I agree with your concern about returning the number of files
> relabeled.  My suggestion was:
>
>  > One thing that might make the patch more usable and address these
>  > comments would be to instead pass the expected number of relabels as an
>  > argument to restorecon and then return success if the relabel count ==
>  > the expected count.  That avoids all the problems around exit code
>  > handling while still verifying the count.
>

Oh I read right over that, sorry. I like that more, but I could see this
feature creeping to users wanting conditional expressions other than equals,
i.e. count < N, etc.

Whereas, if we just dump the number at the tail on stdout, folks can code their
own logic without embedding it into the tool and adding one more thing to
maintain.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2] restorecon: Add option to count relabeled files
  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
  1 sibling, 0 replies; 14+ messages in thread
From: William Roberts @ 2025-11-13 20:17 UTC (permalink / raw)
  To: Vit Mojzis; +Cc: selinux

<snip>

Just reviewing for the output formatting.

>         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);

I like this better, I guess the question is now, do we just make it
printf("%lu\n", relabeled_count) for
ease of parsing? I can see Dan's point there. I am fine either way, as
adding cut -d' ' -f1-1 is fine by me.
However, whatever we choose, the output **has to be stable**.

Hard lessons from tpm2-tools still haunt me, sorry Stephen :-p


> +
> +               /* 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
>
>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2] restorecon: Add option to count relabeled files
  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
  2025-11-18 20:11               ` [PATCH v3] " Vit Mojzis
  1 sibling, 1 reply; 14+ messages in thread
From: Petr Lautrbach @ 2025-11-18  8:59 UTC (permalink / raw)
  To: Vit Mojzis, selinux

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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3] restorecon: Add option to count relabeled files
  2025-11-18  8:59             ` Petr Lautrbach
@ 2025-11-18 20:11               ` Vit Mojzis
  0 siblings, 0 replies; 14+ messages in thread
From: Vit Mojzis @ 2025-11-18 20:11 UTC (permalink / raw)
  To: selinux

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           |  5 ++++
 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, 83 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..95cd53b0 100644
--- a/libselinux/src/libselinux.map
+++ b/libselinux/src/libselinux.map
@@ -262,3 +262,8 @@ LIBSELINUX_3.9 {
   global:
     context_to_str;
 } LIBSELINUX_3.8;
+
+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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2025-11-18 20:12 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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
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

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).