glob

A simple filepath globber
git clone https://git.sinitax.com/sinitax/glob
Log | Files | Refs | LICENSE | sfeed.txt

commit 57ff969ae4100c65304e53a045b062270cb38756
parent 5722be886a1886244a5bd3c5a66f38673607e53f
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 21 Jun 2024 18:19:39 +0200

Fix matching algorithm and add -a flag

Diffstat:
MMakefile | 2+-
Mglob.c | 205++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
2 files changed, 148 insertions(+), 59 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ PREFIX ?= /usr/local BINDIR ?= /bin -CFLAGS = -Wunused-variable -Wunused-function -Wconversion +CFLAGS += -Wunused-variable -Wunused-function -Wconversion all: glob diff --git a/glob.c b/glob.c @@ -1,27 +1,44 @@ -#include <limits.h> -#include <errno.h> -#include <dirent.h> -#include <fcntl.h> +#include <sys/stat.h> #include <linux/limits.h> #include <sys/types.h> +#include <dirent.h> +#include <fcntl.h> #include <unistd.h> + +#include <errno.h> #include <string.h> #include <stdio.h> -#include <stdint.h> #include <stdbool.h> -#include <stdlib.h> -static char * -esc_strchr(const char *esc, char c) -{ - const char *e; +static bool show_hidden = false; - for (e = esc; *e; e++) { - if (*e == '\\') e++; - else if (*e == c) return (char *) e; +static void glob_r(int fd, char *buf, size_t off, size_t cap, const char *pat); + +static bool +append(char *path, const char *component, size_t *len, size_t cap, size_t off, bool final) +{ + if (*len + !final + 1 > cap - off) { + fprintf(stderr, "glob: len > PATH_MAX\n"); + return false; } + strncpy(path + off, component, *len); + while (*len > 0 && path[off+*len-1] == '/') + (*len)--; + if (!final) path[off+(*len)++] = '/'; + path[off+*len] = '\0'; + return true; +} - return NULL; +static int +opendirat(int pfd, const char *full, const char *child) +{ + int fd = openat(pfd, child, O_DIRECTORY); + if (fd < 0) { + fprintf(stderr, "glob: openat '%s': %s\n", + full, strerror(errno)); + return -1; + } + return fd; } static bool @@ -31,7 +48,7 @@ esc_strcmp(const char **esc, const char **str, const char *except) for (e = *esc, s = *str; *s && *e; s++, e++) { if (*e == '\\') e++; - else if (strchr(except, *e)) break; + else if (strchr(except, *e)) return true; if (*s != *e) return false; } *esc = e; @@ -73,43 +90,80 @@ match(const char *pat, const char *str) return true; } +static bool +addprefix(char *buf, const char *pat, size_t cap, + size_t off, size_t *consumed, size_t *added) +{ + const char *slash; + const char *star; + const char *tok; + size_t len; + + *added = *consumed = 0; + + star = strchr(pat, '*'); + if (!star) { + *consumed = *added = strlen(pat); + return append(buf, pat, added, cap, off, true); + } + + slash = strchr(pat, '/'); + if (!slash || slash > star) + return true; + + tok = pat; + do { + len = (size_t) (slash + 1 - tok); + *consumed += len; + if (slash == pat || len > 1) { + if (!append(buf, tok, &len, cap, off + *added, !slash)) + return false; + *added += len; + } + tok = slash + 1; + slash = strchr(slash + 1, '/'); + } while (slash && slash < star); + + return true; +} + static void -glob_r(int fd, char *buf, size_t off, size_t n, const char *pat) +glob_r_match(int fd, char *buf, size_t off, size_t cap, const char *pat) { + const char *slash; struct dirent *ent; + DIR *d; size_t len; - char *sep; int sfd; - DIR *d; - d = fdopendir(fd); - if (!d) return; + if (!(d = fdopendir(fd))) { + fprintf(stderr, "glob: opendir '%s': %s\n", + *buf ? buf : ".", strerror(errno)); + return; + } - sep = esc_strchr(pat, '/'); + slash = strchr(pat, '/'); while ((ent = readdir(d))) { if (!strcmp(ent->d_name, ".")) continue; if (!strcmp(ent->d_name, "..")) continue; - if (match(pat, ent->d_name)) { - if (sep && ent->d_type == DT_DIR) { - sfd = openat(fd, ent->d_name, O_DIRECTORY); - if (sfd < 0) { - fprintf(stderr, "glob: openat '%s%s': %s\n", - buf, ent->d_name, strerror(errno)); - continue; - } - len = strlen(ent->d_name); - if (len + 2 > n - off) { - fprintf(stderr, "glob: len > PATH_MAX\n"); - exit(1); - } - strcpy(buf + off, ent->d_name); - strcpy(buf + off + len, "/"); - glob_r(sfd, buf, off + len + 1, n, sep + 1); - close(sfd); - } else { - fputs(buf, stdout); - puts(ent->d_name); - } + + if (*ent->d_name == '.' && !show_hidden) continue; + + if (!match(pat, ent->d_name)) continue; + + if (slash && ent->d_type == DT_DIR) { + len = strlen(ent->d_name); + if (!append(buf, ent->d_name, &len, cap, off, false)) + continue; + if ((sfd = opendirat(fd, buf, ent->d_name)) < 0) + continue; + glob_r(sfd, buf, off + len, cap, slash + 1); + close(sfd); + } else if (!slash) { + len = strlen(ent->d_name); + if (!append(buf, ent->d_name, &len, cap, off, true)) + continue; + puts(buf); } } @@ -117,31 +171,66 @@ glob_r(int fd, char *buf, size_t off, size_t n, const char *pat) } static void -glob(const char *pat) +glob_r(int fd, char *buf, size_t off, size_t cap, const char *pat) +{ + struct stat stat; + size_t plen; + size_t len; + int sfd; + + if (!addprefix(buf, pat, cap, off, &plen, &len)) + return; + + if (len) { + if (!pat[plen]) { + if (!fstatat(fd, buf + off, &stat, 0)) { + puts(buf); + } + } else { + if ((sfd = opendirat(fd, buf, buf + off)) < 0) + return; + glob_r_match(sfd, buf, off + len, cap, pat + plen); + close(sfd); + } + } else { + glob_r_match(fd, buf, off, cap, pat + plen); + } +} + +static void +glob(char *pat) { char buf[PATH_MAX]; - const char *dirname; - int fd; + char *end; buf[0] = '\0'; - dirname = pat[0] == '/' ? "/" : "."; - fd = open(dirname, O_DIRECTORY); - if (fd < 0) { - fprintf(stderr, "glob: open '%s': %s\n", - dirname, strerror(errno)); - exit(1); - } - glob_r(fd, buf, 0, PATH_MAX, pat); - close(fd); + + end = pat + strlen(pat); + while (end > pat && end[-1] == '/') + end--; + *end = '\0'; + + glob_r(AT_FDCWD, buf, 0, PATH_MAX, pat); } int -main(int argc, const char **argv) +main(int argc, char **argv) { - const char **arg; + char **arg; if (!argc) return 1; - for (arg = argv + 1; *arg; arg++) - glob(*arg); + for (arg = argv + 1; *arg; arg++) { + if (!strcmp(*arg, "-h")) { + printf("Usage: glob [-h] [-a]\n"); + return 0; + } else if (!strcmp(*arg, "-a")) { + show_hidden = true; + } else { + if (!strcmp(*arg, "--")) arg++; + break; + } + } + + for (; *arg; arg++) glob(*arg); }