From 1efa932e4aeb007eddb6424a90bf0fc05dba7e4d Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Sun, 23 Apr 2017 00:00:37 -0400
Subject: Implement -fstype

Fixes #6!
---
 Makefile              |   2 +-
 bfs.h                 |   5 ++
 eval.c                |  14 ++++
 mtab.c                | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++
 mtab.h                |  46 +++++++++++++
 parse.c               |  30 ++++++++-
 printf.c              |  23 ++++++-
 printf.h              |   7 +-
 tests.sh              |   6 ++
 tests/test_fstype.out |  19 ++++++
 10 files changed, 319 insertions(+), 7 deletions(-)
 create mode 100644 mtab.c
 create mode 100644 mtab.h
 create mode 100644 tests/test_fstype.out

diff --git a/Makefile b/Makefile
index 50148f5..a4f4cdd 100644
--- a/Makefile
+++ b/Makefile
@@ -44,7 +44,7 @@ ALL_LDFLAGS = $(ALL_CFLAGS) $(LDFLAGS)
 
 all: bfs
 
-bfs: bftw.o color.o dstring.o eval.o exec.o main.o parse.o printf.o typo.o util.o
+bfs: bftw.o color.o dstring.o eval.o exec.o main.o mtab.o parse.o printf.o typo.o util.o
 	$(CC) $(ALL_LDFLAGS) $^ -o $@
 
 release: CFLAGS := -O3 -flto $(WFLAGS) -DNDEBUG
diff --git a/bfs.h b/bfs.h
index 191cb9e..3fd66a5 100644
--- a/bfs.h
+++ b/bfs.h
@@ -15,6 +15,7 @@
 #include "color.h"
 #include "exec.h"
 #include "printf.h"
+#include "mtab.h"
 #include <regex.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -90,6 +91,9 @@ struct cmdline {
 	/** Colored stderr. */
 	CFILE *cerr;
 
+	/** Table of mounted file systems. */
+	struct bfs_mtab *mtab;
+
 	/** -mindepth option. */
 	int mindepth;
 	/** -maxdepth option. */
@@ -292,6 +296,7 @@ bool eval_nouser(const struct expr *expr, struct eval_state *state);
 
 bool eval_depth(const struct expr *expr, struct eval_state *state);
 bool eval_empty(const struct expr *expr, struct eval_state *state);
+bool eval_fstype(const struct expr *expr, struct eval_state *state);
 bool eval_hidden(const struct expr *expr, struct eval_state *state);
 bool eval_inum(const struct expr *expr, struct eval_state *state);
 bool eval_links(const struct expr *expr, struct eval_state *state);
diff --git a/eval.c b/eval.c
index 8c33d21..5de25ec 100644
--- a/eval.c
+++ b/eval.c
@@ -13,6 +13,7 @@
 #include "bftw.h"
 #include "color.h"
 #include "dstring.h"
+#include "mtab.h"
 #include "util.h"
 #include <assert.h>
 #include <dirent.h>
@@ -366,6 +367,19 @@ done:
 	return ret;
 }
 
+/**
+ * -fstype test.
+ */
+bool eval_fstype(const struct expr *expr, struct eval_state *state) {
+	const struct stat *statbuf = fill_statbuf(state);
+	if (!statbuf) {
+		return false;
+	}
+
+	const char *type = bfs_fstype(state->cmdline->mtab, statbuf);
+	return strcmp(type, expr->sdata) == 0;
+}
+
 /**
  * -hidden test.
  */
diff --git a/mtab.c b/mtab.c
new file mode 100644
index 0000000..0311c50
--- /dev/null
+++ b/mtab.c
@@ -0,0 +1,174 @@
+/*********************************************************************
+ * bfs                                                               *
+ * Copyright (C) 2017 Tavian Barnes <tavianator@tavianator.com>      *
+ *                                                                   *
+ * This program is free software. It comes without any warranty, to  *
+ * the extent permitted by applicable law. You can redistribute it   *
+ * and/or modify it under the terms of the Do What The Fuck You Want *
+ * To Public License, Version 2, as published by Sam Hocevar. See    *
+ * the COPYING file or http://www.wtfpl.net/ for more details.       *
+ *********************************************************************/
+
+#include "mtab.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#if __linux__
+#	include <mntent.h>
+#elif BSD
+#	include <sys/mount.h>
+#	include <sys/ucred.h>
+#endif
+
+/**
+ * A mount point in the mount table.
+ */
+struct bfs_mtab_entry {
+	/** The device number for this mount point. */
+	dev_t dev;
+	/** The file system type of this mount point. */
+	char *type;
+};
+
+struct bfs_mtab {
+	/** The array of mtab entries. */
+	struct bfs_mtab_entry *table;
+	/** The size of the array. */
+	size_t size;
+	/** Capacity of the array. */
+	size_t capacity;
+};
+
+/**
+ * Add an entry to the mount table.
+ */
+static int bfs_mtab_push(struct bfs_mtab *mtab, dev_t dev, const char *type) {
+	size_t size = mtab->size + 1;
+
+	if (size >= mtab->capacity) {
+		size_t capacity = 2*size;
+		struct bfs_mtab_entry *table = realloc(mtab->table, capacity*sizeof(*table));
+		if (!table) {
+			return -1;
+		}
+		mtab->table = table;
+		mtab->capacity = capacity;
+	}
+
+	struct bfs_mtab_entry *entry = mtab->table + (size - 1);
+	entry->dev = dev;
+	entry->type = strdup(type);
+	if (!entry->type) {
+		return -1;
+	}
+
+	mtab->size = size;
+	return 0;
+}
+
+struct bfs_mtab *parse_bfs_mtab() {
+#if __linux__
+
+	FILE *file = setmntent("/etc/mtab", "r");
+	if (!file) {
+		goto fail;
+	}
+
+	struct bfs_mtab *mtab = malloc(sizeof(*mtab));
+	if (!mtab) {
+		goto fail_file;
+	}
+	mtab->table = NULL;
+	mtab->size = 0;
+	mtab->capacity = 0;
+
+	struct mntent *mnt;
+	while ((mnt = getmntent(file))) {
+		struct stat sb;
+		if (stat(mnt->mnt_dir, &sb) != 0) {
+			continue;
+		}
+
+		if (bfs_mtab_push(mtab, sb.st_dev, mnt->mnt_type) != 0) {
+			goto fail_mtab;
+		}
+	}
+
+	endmntent(file);
+	return mtab;
+
+fail_mtab:
+	free_bfs_mtab(mtab);
+fail_file:
+	endmntent(file);
+fail:
+	return NULL;
+
+#elif BSD
+
+	struct statfs *mntbuf;
+	int size = getmntinfo(&mntbuf, MNT_WAIT);
+	if (size < 0) {
+		return NULL;
+	}
+
+	struct bfs_mtab *mtab = malloc(sizeof(*mtab));
+	if (!mtab) {
+		goto fail;
+	}
+
+	mtab->size = 0;
+	mtab->table = malloc(size*sizeof(*mtab->table));
+	if (!mtab->table) {
+		goto fail_mtab;
+	}
+	mtab->capacity = size;
+
+	for (struct statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) {
+		struct stat sb;
+		if (stat(mnt->f_mntonname, &sb) != 0) {
+			continue;
+		}
+
+		if (bfs_mtab_push(mtab, sb.st_dev, mnt->f_fstypename) != 0) {
+			goto fail_mtab;
+		}
+	}
+
+	return mtab;
+
+fail_mtab:
+	free_bfs_mtab(mtab);
+fail:
+	return NULL;
+
+#else
+
+	errno = ENOTSUP;
+	return NULL;
+#endif
+}
+
+const char *bfs_fstype(const struct bfs_mtab *mtab, const struct stat *statbuf) {
+	for (struct bfs_mtab_entry *mnt = mtab->table; mnt < mtab->table + mtab->size; ++mnt) {
+		if (statbuf->st_dev == mnt->dev) {
+			return mnt->type;
+		}
+	}
+
+	return "unknown";
+}
+
+void free_bfs_mtab(struct bfs_mtab *mtab) {
+	if (mtab) {
+		for (struct bfs_mtab_entry *mnt = mtab->table; mnt < mtab->table + mtab->size; ++mnt) {
+			free(mnt->type);
+		}
+		free(mtab->table);
+		free(mtab);
+	}
+}
diff --git a/mtab.h b/mtab.h
new file mode 100644
index 0000000..38407fc
--- /dev/null
+++ b/mtab.h
@@ -0,0 +1,46 @@
+/*********************************************************************
+ * bfs                                                               *
+ * Copyright (C) 2017 Tavian Barnes <tavianator@tavianator.com>      *
+ *                                                                   *
+ * This program is free software. It comes without any warranty, to  *
+ * the extent permitted by applicable law. You can redistribute it   *
+ * and/or modify it under the terms of the Do What The Fuck You Want *
+ * To Public License, Version 2, as published by Sam Hocevar. See    *
+ * the COPYING file or http://www.wtfpl.net/ for more details.       *
+ *********************************************************************/
+
+#ifndef BFS_MTAB_H
+#define BFS_MTAB_H
+
+#include <sys/stat.h>
+
+/**
+ * A file system mount table.
+ */
+struct bfs_mtab;
+
+/**
+ * Parse the mount table.
+ *
+ * @return The parsed mount table, or NULL on error.
+ */
+struct bfs_mtab *parse_bfs_mtab(void);
+
+/**
+ * Determine the file system type that a file is on.
+ *
+ * @param mtab
+ *         The current mount table.
+ * @param statbuf
+ *         The stat() buffer for the file in question.
+ * @return The type of file system containing this file, "unknown" if not known,
+ *         or NULL on error.
+ */
+const char *bfs_fstype(const struct bfs_mtab *mtab, const struct stat *statbuf);
+
+/**
+ * Free a mount table.
+ */
+void free_bfs_mtab(struct bfs_mtab *mtab);
+
+#endif // BFS_MTAB_H
diff --git a/parse.c b/parse.c
index f8ed09c..25791cf 100644
--- a/parse.c
+++ b/parse.c
@@ -191,6 +191,8 @@ void free_cmdline(struct cmdline *cmdline) {
 	if (cmdline) {
 		free_expr(cmdline->expr);
 
+		free_bfs_mtab(cmdline->mtab);
+
 		cfclose(cmdline->cerr);
 		cfclose(cmdline->cout);
 		free_colors(cmdline->colors);
@@ -1039,7 +1041,7 @@ static struct expr *parse_fprintf(struct parser_state *state, int arg1, int arg2
 		goto fail;
 	}
 
-	expr->printf = parse_bfs_printf(format, state->cmdline->cerr);
+	expr->printf = parse_bfs_printf(format, state->cmdline);
 	if (!expr->printf) {
 		goto fail;
 	}
@@ -1051,6 +1053,18 @@ fail:
 	return NULL;
 }
 
+/**
+ * Parse -fstype TYPE.
+ */
+static struct expr *parse_fstype(struct parser_state *state, int arg1, int arg2) {
+	if (!state->cmdline->mtab) {
+		cfprintf(state->cmdline->cerr, "%{er}error: %s: Couldn't parse the mount table.%{rs}\n", state->argv[0]);
+		return NULL;
+	}
+
+	return parse_unary_test(state, eval_fstype);
+}
+
 /**
  * Parse -gid/-group.
  */
@@ -1598,7 +1612,7 @@ static struct expr *parse_printf(struct parser_state *state, int arg1, int arg2)
 
 	expr->cfile = state->cmdline->cout;
 
-	expr->printf = parse_bfs_printf(expr->sdata, state->cmdline->cerr);
+	expr->printf = parse_bfs_printf(expr->sdata, state->cmdline);
 	if (!expr->printf) {
 		free_expr(expr);
 		return NULL;
@@ -1993,6 +2007,8 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) {
 	cfprintf(cout, "  %{blu}-false%{rs}\n");
 	cfprintf(cout, "  %{blu}-true%{rs}\n");
 	cfprintf(cout, "      Always false/true\n");
+	cfprintf(cout, "  %{blu}-fstype%{rs} %{bld}TYPE%{rs}\n");
+	cfprintf(cout, "      Find files on file systems with the given %{bld}TYPE%{rs}\n");
 	cfprintf(cout, "  %{blu}-gid%{rs} %{bld}[-+]N%{rs}\n");
 	cfprintf(cout, "  %{blu}-uid%{rs} %{bld}[-+]N%{rs}\n");
 	cfprintf(cout, "      Find files owned by group/user ID %{bld}N%{rs}\n");
@@ -2149,6 +2165,7 @@ static const struct table_entry parse_table[] = {
 	{"fprint", false, parse_fprint},
 	{"fprint0", false, parse_fprint0},
 	{"fprintf", false, parse_fprintf},
+	{"fstype", false, parse_fstype},
 	{"gid", false, parse_group},
 	{"group", false, parse_group},
 	{"help", false, parse_help},
@@ -2801,6 +2818,10 @@ struct cmdline *parse_cmdline(int argc, char *argv[]) {
 	}
 
 	cmdline->roots = NULL;
+	cmdline->colors = NULL;
+	cmdline->cout = NULL;
+	cmdline->cerr = NULL;
+	cmdline->mtab = NULL;
 	cmdline->mindepth = 0;
 	cmdline->maxdepth = INT_MAX;
 	cmdline->flags = BFTW_RECOVER;
@@ -2819,6 +2840,11 @@ struct cmdline *parse_cmdline(int argc, char *argv[]) {
 		goto fail;
 	}
 
+	cmdline->mtab = parse_bfs_mtab();
+	if (!cmdline->mtab) {
+		cfprintf(cmdline->cerr, "%{wr}warning: Couldn't parse the mount table: %s.%{rs}\n\n", strerror(errno));
+	}
+
 	struct parser_state state = {
 		.cmdline = cmdline,
 		.argv = argv + 1,
diff --git a/printf.c b/printf.c
index cc8989f..50a9dcf 100644
--- a/printf.c
+++ b/printf.c
@@ -13,6 +13,7 @@
 #include "bfs.h"
 #include "color.h"
 #include "dstring.h"
+#include "mtab.h"
 #include "util.h"
 #include <assert.h>
 #include <errno.h>
@@ -39,6 +40,8 @@ struct bfs_printf_directive {
 	enum time_field time_field;
 	/** Character data associated with this directive. */
 	char c;
+	/** The current mount table. */
+	const struct bfs_mtab *mtab;
 	/** The next printf directive in the chain. */
 	struct bfs_printf_directive *next;
 };
@@ -175,6 +178,12 @@ static int bfs_printf_f(FILE *file, const struct bfs_printf_directive *directive
 	return fprintf(file, directive->str, ftwbuf->path + ftwbuf->nameoff);
 }
 
+/** %F: file system type */
+static int bfs_printf_F(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) {
+	const char *type = bfs_fstype(directive->mtab, ftwbuf->statbuf);
+	return fprintf(file, directive->str, type);
+}
+
 /** %G: gid */
 static int bfs_printf_G(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) {
 	BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_gid);
@@ -393,6 +402,7 @@ static struct bfs_printf_directive *new_directive() {
 	}
 	directive->time_field = 0;
 	directive->c = 0;
+	directive->mtab = NULL;
 	directive->next = NULL;
 	return directive;
 
@@ -433,7 +443,9 @@ static int append_literal(struct bfs_printf_directive ***tail, struct bfs_printf
 	return 0;
 }
 
-struct bfs_printf *parse_bfs_printf(const char *format, CFILE *cerr) {
+struct bfs_printf *parse_bfs_printf(const char *format, const struct cmdline *cmdline) {
+	CFILE *cerr = cmdline->cerr;
+
 	struct bfs_printf *command = malloc(sizeof(*command));
 	if (!command) {
 		perror("malloc()");
@@ -584,6 +596,15 @@ struct bfs_printf *parse_bfs_printf(const char *format, CFILE *cerr) {
 			case 'f':
 				directive->fn = bfs_printf_f;
 				break;
+			case 'F':
+				if (!cmdline->mtab) {
+					cfprintf(cerr, "%{er}error: '%s': Couldn't parse the mount table.%{rs}\n", format);
+					goto directive_error;
+				}
+				directive->fn = bfs_printf_F;
+				directive->mtab = cmdline->mtab;
+				command->needs_stat = true;
+				break;
 			case 'g':
 				directive->fn = bfs_printf_g;
 				command->needs_stat = true;
diff --git a/printf.h b/printf.h
index 32171ca..d57921f 100644
--- a/printf.h
+++ b/printf.h
@@ -17,6 +17,7 @@
 #include <stdbool.h>
 #include <stdio.h>
 
+struct cmdline;
 struct bfs_printf_directive;
 
 /**
@@ -34,11 +35,11 @@ struct bfs_printf {
  *
  * @param format
  *         The format string to parse.
- * @param cerr
- *         For error messages.
+ * @param cmdline
+ *         The command line.
  * @return The parsed printf command, or NULL on failure.
  */
-struct bfs_printf *parse_bfs_printf(const char *format, CFILE *cerr);
+struct bfs_printf *parse_bfs_printf(const char *format, const struct cmdline *cmdline);
 
 /**
  * Evaluate a parsed format string.
diff --git a/tests.sh b/tests.sh
index 5fed800..e3cdb52 100755
--- a/tests.sh
+++ b/tests.sh
@@ -292,6 +292,7 @@ gnu_tests=(
     test_printf_leak
     test_quit_after_print
     test_quit_before_print
+    test_fstype
     test_not
     test_and
     test_or
@@ -1019,6 +1020,11 @@ function test_printf_leak() {
     bfs_diff basic -maxdepth 0 -printf '%p'
 }
 
+function test_fstype() {
+    fstype="$($BFS -printf '%F\n' | head -n1)"
+    bfs_diff basic -fstype "$fstype"
+}
+
 function test_path_flag_expr() {
     bfs_diff links/h -H -type l
 }
diff --git a/tests/test_fstype.out b/tests/test_fstype.out
new file mode 100644
index 0000000..bb3cd8d
--- /dev/null
+++ b/tests/test_fstype.out
@@ -0,0 +1,19 @@
+basic
+basic/a
+basic/b
+basic/c
+basic/e
+basic/g
+basic/i
+basic/j
+basic/k
+basic/l
+basic/c/d
+basic/e/f
+basic/g/h
+basic/j/foo
+basic/k/foo
+basic/l/foo
+basic/k/foo/bar
+basic/l/foo/bar
+basic/l/foo/bar/baz
-- 
cgit v1.2.3