aboutsummaryrefslogtreecommitdiffstats
path: root/bfind.c
diff options
context:
space:
mode:
Diffstat (limited to 'bfind.c')
-rw-r--r--bfind.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/bfind.c b/bfind.c
new file mode 100644
index 0000000..323ee73
--- /dev/null
+++ b/bfind.c
@@ -0,0 +1,306 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arg.h"
+
+#define N 500
+
+
+typedef struct queue {
+ char *nodes[N];
+ size_t head;
+ size_t tail;
+ struct queue *next;
+ struct queue *prev;
+} Queue;
+
+typedef union inode {
+ union inode *node[256];
+ uint64_t leaf[256 / 64];
+} INode;
+
+typedef struct device {
+ dev_t dev;
+ INode visited;
+} Device;
+
+
+char *argv0;
+
+static Queue queue_head;
+static Queue queue_tail;
+static Queue queue_pool;
+static Device *devices = NULL;
+static size_t ndevices = 0;
+
+static int status = 0;
+static int xdev = 0;
+static int hardlinks = 0;
+static int symlinks = 0;
+static int visible = 0;
+
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-0hsvx] [directory]\n", argv0);
+ exit(1);
+}
+
+
+static void
+enqueue(const char *dir, size_t dir_len, const char *file)
+{
+ Queue *node;
+ size_t file_len;
+
+ node = queue_tail.prev;
+ if (!node->prev || node->tail == N) {
+ if (queue_pool.next) {
+ node = queue_pool.next;
+ queue_pool.next = node->next;
+ } else {
+ node = calloc(1, sizeof(*node));
+ if (!node) {
+ perror(argv0);
+ exit(1);
+ }
+ }
+ node->prev = queue_tail.prev;
+ queue_tail.prev->next = node;
+ queue_tail.prev = node;
+ node->next = &queue_tail;
+ }
+
+ file_len = strlen(file);
+ node->nodes[node->tail] = malloc(dir_len + file_len + 2);
+ if (!node->nodes[node->tail]) {
+ perror(argv0);
+ exit(1);
+ }
+ if (dir) {
+ memcpy(&node->nodes[node->tail][0], dir, dir_len);
+ node->nodes[node->tail][dir_len++] = '/';
+ }
+ memcpy(&node->nodes[node->tail][dir_len], file, file_len + 1);
+ node->tail += 1;
+}
+
+
+static char *
+dequeue(void)
+{
+ Queue *node;
+ char *ret;
+
+again:
+ node = queue_head.next;
+ if (!node->next)
+ return NULL;
+
+ if (node->head == node->tail) {
+ node->head = node->tail = 0;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->prev = NULL;
+ node->next = queue_pool.next;
+ queue_pool.next = node;
+ goto again;
+ }
+
+ ret = node->nodes[node->head++];
+ if (node->head == node->tail) {
+ node->head = node->tail = 0;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->prev = NULL;
+ node->next = queue_pool.next;
+ queue_pool.next = node;
+ }
+
+ return ret;
+}
+
+
+static void
+enqueue_dir(const char *path)
+{
+ DIR *dir;
+ struct dirent *f;
+ size_t path_len = path ? strlen(path) : 0;
+ struct stat st;
+
+ if (path && !symlinks) {
+ if (lstat(path ? path : ".", &st)) {
+ if (errno == ENOENT)
+ return;
+ fprintf(stderr, "%s: lstat %s: %s\n", argv0, path ? path : ".", strerror(errno));
+ status = 1;
+ return;
+ }
+ if (S_ISLNK(st.st_mode))
+ return;
+ }
+
+ dir = opendir(path ? path : ".");
+ if (!dir) {
+ if (errno == ENOTDIR || errno == ENOENT || errno == ELOOP)
+ return;
+ fprintf(stderr, "%s: opendir %s: %s\n", argv0, path ? path : ".", strerror(errno));
+ status = 1;
+ return;
+ }
+
+ errno = 0;
+ while ((f = readdir(dir))) {
+ if (f->d_name[0] == '.')
+ if (visible || !f->d_name[1 + (f->d_name[1] == '.')])
+ continue;
+ enqueue(path, path_len, f->d_name);
+ }
+
+ if (errno) {
+ fprintf(stderr, "%s: readdir %s: %s\n", argv0, path ? path : ".", strerror(errno));
+ status = 1;
+ }
+
+ closedir(dir);
+}
+
+
+static int
+visit_inode(Device *dev, ino_t inode)
+{
+ size_t levels = sizeof(inode);
+ INode *tree = &dev->visited;
+
+ while (levels) {
+ if (!tree->node[inode & 255])
+ goto not_found;
+ tree = tree->node[inode & 255];
+ inode >>= 8;
+ levels--;
+ }
+
+ if (tree->leaf[inode / 64] & ((uint64_t)1 << (inode & 63)))
+ return 1;
+
+ goto not_visited;
+
+not_found:
+ while (levels > 1) {
+ tree = tree->node[inode & 255] = calloc(1, levels > 1 ? sizeof(tree->node) : sizeof(tree->leaf));
+ if (!tree) {
+ fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+ inode >>= 8;
+ levels--;
+ }
+
+not_visited:
+ tree->leaf[inode / 64] |= (uint64_t)1 << (inode & 63);
+ return 0;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ char ending = '\n', *path;
+ struct stat st;
+ dev_t start_dev;
+ size_t i;
+
+ ARGBEGIN {
+ case '0':
+ ending = '\0';
+ break;
+ case 'h':
+ hardlinks = 1;
+ break;
+ case 's':
+ hardlinks = 1;
+ symlinks = 1;
+ break;
+ case 'v':
+ visible = 1;
+ break;
+ case 'x':
+ xdev = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+
+ queue_head.next = &queue_tail;
+ queue_tail.prev = &queue_head;
+
+ if (!xdev) {
+ if (stat((argc && **argv) ? *argv : ".", &st)) {
+ fprintf(stderr, "%s: stat %s: %s\n", argv0, (argc && **argv) ? *argv : ".", strerror(errno));
+ return 1;
+ }
+ start_dev = st.st_dev;
+ }
+
+ if (argc && **argv)
+ enqueue(NULL, 0, *argv);
+ else
+ enqueue_dir(NULL);
+
+ while ((path = dequeue())) {
+ printf("%s%c", path, ending);
+ if (stat(path, &st)) {
+ if (errno != ENOENT && errno != ELOOP) {
+ fprintf(stderr, "%s: stat %s: %s\n", argv0, path, strerror(errno));
+ status = 1;
+ }
+ continue;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ if (!xdev && st.st_dev != start_dev)
+ continue;
+ if (hardlinks) {
+ for (i = 0; i < ndevices; i++)
+ if (devices[i].dev == st.st_dev)
+ break;
+ if (i == ndevices) {
+ devices = realloc(devices, (ndevices + 1) * sizeof(*devices));
+ if (!devices) {
+ fprintf(stderr, "%s: realloc: %s\n", argv0, strerror(errno));
+ status = 1;
+ }
+ memset(&devices[ndevices], 0, sizeof(*devices));
+ devices[ndevices++].dev = st.st_dev;
+ }
+ if (visit_inode(&devices[i], st.st_ino))
+ continue;
+ }
+ enqueue_dir(path);
+ }
+ free(path);
+ }
+
+ if (fflush(stdout) || ferror(stdout) || fclose(stdout)) {
+ fprintf(stderr, "%s: printf: %s\n", argv0, strerror(errno));
+ return 1;
+ }
+ while (queue_pool.next) {
+ queue_pool.prev = queue_pool.next;
+ queue_pool.next = queue_pool.next->next;
+ free(queue_pool.prev);
+ }
+ return status;
+}