aboutsummaryrefslogblamecommitdiffstats
path: root/bfind.c
blob: 7ca2001d26c037248842703dcee3289c74734e4d (plain) (tree)


























































































































































































































                                                                                                                 
                                                                                                       





















































































                                                                                                                     
/* 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 = 0; /* compiler incorrectly thinks its may be uninitialised if not assigned */
	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;
}