From: Vit Mojzis <vmojzis@redhat.com>
To: selinux@vger.kernel.org
Subject: [PATCH v3] restorecon: Add option to count relabeled files
Date: Tue, 18 Nov 2025 21:11:43 +0100 [thread overview]
Message-ID: <20251118201145.984733-1-vmojzis@redhat.com> (raw)
In-Reply-To: LINKIFYIHcIDCAFEcGaBIfJGIfGeEGGFGDIJaDeBEcacFIIwq>
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
next prev parent reply other threads:[~2025-11-18 20:12 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-10 18:05 [PATCH] restorecon: Add option to count number of relabeled files Vit Mojzis
2025-11-10 20:21 ` Stephen Smalley
2025-11-10 20:23 ` William Roberts
2025-11-10 20:37 ` William Roberts
2025-11-11 14:44 ` Daniel Burgener
[not found] ` <CAFftDdoTR5ae1qORSjPuOj5ea1O15qtgrRiadhTp2HMh926swg@mail.gmail.com>
2025-11-13 0:43 ` William Roberts
2025-11-13 17:11 ` [PATCH v2] restorecon: Add option to count " Vit Mojzis
2025-11-13 20:17 ` William Roberts
2025-11-18 8:59 ` Petr Lautrbach
2025-11-18 20:11 ` Vit Mojzis [this message]
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=20251118201145.984733-1-vmojzis@redhat.com \
--to=vmojzis@redhat.com \
--cc=selinux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).