From fc63f1b33fb438e8f7690db9bd84e3c69fe86b51 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Wed, 9 Nov 2022 11:35:53 -0500
Subject: ctx: Flush the user/group caches when executing commands

This fixes (admittedly uncommon) commands like

    $ bfs -nouser -exec add-missing-user.sh {} \;
---
 src/ctx.c     |  5 +++++
 src/pwcache.c | 46 ++++++++++++++++++++++------------------------
 src/pwcache.h | 16 ++++++++++++++++
 3 files changed, 43 insertions(+), 24 deletions(-)

diff --git a/src/ctx.c b/src/ctx.c
index 8d80691..4b373a1 100644
--- a/src/ctx.c
+++ b/src/ctx.c
@@ -186,6 +186,11 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) {
 	// We do not check errors here, but they will be caught at cleanup time
 	// with ferror().
 	fflush(NULL);
+
+	// Flush the user/group caches, in case the executed command edits the
+	// user/group tables
+	bfs_users_flush(ctx->users);
+	bfs_groups_flush(ctx->groups);
 }
 
 /** Flush a file and report any errors. */
diff --git a/src/pwcache.c b/src/pwcache.c
index 7071f59..868ec8f 100644
--- a/src/pwcache.c
+++ b/src/pwcache.c
@@ -64,6 +64,16 @@ static void *bfs_getent(struct trie_leaf *leaf, bfs_getent_fn *fn, const void *k
 	}
 }
 
+/** Flush a single cache. */
+static void bfs_pwcache_flush(struct trie *trie) {
+	TRIE_FOR_EACH(trie, leaf) {
+		if (leaf->value != MISSING) {
+			free(leaf->value);
+		}
+		trie_remove(trie, leaf);
+	}
+}
+
 struct bfs_users {
 	/** Initial buffer size for getpw*_r(). */
 	size_t bufsize;
@@ -123,22 +133,16 @@ const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid) {
 	return bfs_getent(leaf, bfs_getpwuid_impl, &uid, sizeof(struct passwd), users->bufsize);
 }
 
+void bfs_users_flush(struct bfs_users *users) {
+	bfs_pwcache_flush(&users->by_name);
+	bfs_pwcache_flush(&users->by_uid);
+}
+
 void bfs_users_free(struct bfs_users *users) {
 	if (users) {
-		TRIE_FOR_EACH(&users->by_uid, leaf) {
-			if (leaf->value != MISSING) {
-				free(leaf->value);
-			}
-		}
+		bfs_users_flush(users);
 		trie_destroy(&users->by_uid);
-
-		TRIE_FOR_EACH(&users->by_name, leaf) {
-			if (leaf->value != MISSING) {
-				free(leaf->value);
-			}
-		}
 		trie_destroy(&users->by_name);
-
 		free(users);
 	}
 }
@@ -202,22 +206,16 @@ const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid) {
 	return bfs_getent(leaf, bfs_getgrgid_impl, &gid, sizeof(struct group), groups->bufsize);
 }
 
+void bfs_groups_flush(struct bfs_groups *groups) {
+	bfs_pwcache_flush(&groups->by_name);
+	bfs_pwcache_flush(&groups->by_gid);
+}
+
 void bfs_groups_free(struct bfs_groups *groups) {
 	if (groups) {
-		TRIE_FOR_EACH(&groups->by_gid, leaf) {
-			if (leaf->value != MISSING) {
-				free(leaf->value);
-			}
-		}
+		bfs_groups_flush(groups);
 		trie_destroy(&groups->by_gid);
-
-		TRIE_FOR_EACH(&groups->by_name, leaf) {
-			if (leaf->value != MISSING) {
-				free(leaf->value);
-			}
-		}
 		trie_destroy(&groups->by_name);
-
 		free(groups);
 	}
 }
diff --git a/src/pwcache.h b/src/pwcache.h
index 6ae8bea..f1ca0bf 100644
--- a/src/pwcache.h
+++ b/src/pwcache.h
@@ -63,6 +63,14 @@ const struct passwd *bfs_getpwnam(struct bfs_users *users, const char *name);
  */
 const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid);
 
+/**
+ * Flush a user cache.
+ *
+ * @param users
+ *         The cache to flush.
+ */
+void bfs_users_flush(struct bfs_users *users);
+
 /**
  * Free a user cache.
  *
@@ -110,6 +118,14 @@ const struct group *bfs_getgrnam(struct bfs_groups *groups, const char *name);
  */
 const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid);
 
+/**
+ * Flush a group cache.
+ *
+ * @param groups
+ *         The cache to flush.
+ */
+void bfs_groups_flush(struct bfs_groups *groups);
+
 /**
  * Free a group cache.
  *
-- 
cgit v1.2.3