/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #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; }