glob

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

glob.c (4488B)


      1#include <sys/stat.h>
      2#include <linux/limits.h>
      3#include <sys/types.h>
      4#include <dirent.h>
      5#include <fcntl.h>
      6#include <unistd.h>
      7
      8#include <errno.h>
      9#include <string.h>
     10#include <stdio.h>
     11#include <stdbool.h>
     12
     13static bool show_hidden = false;
     14
     15static void glob_r(int fd, char *buf, size_t off, size_t cap, const char *pat);
     16
     17static bool
     18append(char *path, const char *component, size_t *len, size_t cap, size_t off, bool final)
     19{
     20	if (*len + !final + 1 > cap - off) {
     21		fprintf(stderr, "glob: len > PATH_MAX\n");
     22		return false;
     23	}
     24	strncpy(path + off, component, *len);
     25	while (*len > 0 && path[off+*len-1] == '/')
     26		(*len)--;
     27	if (!final) path[off+(*len)++] = '/';
     28	path[off+*len] = '\0';
     29	return true;
     30}
     31
     32static int
     33opendirat(int pfd, const char *full, const char *child)
     34{
     35	int fd = openat(pfd, child, O_DIRECTORY);
     36	if (fd < 0) {
     37		fprintf(stderr, "glob: openat '%s': %s\n",
     38			full, strerror(errno));
     39		return -1;
     40	}
     41	return fd;
     42}
     43
     44static bool
     45esc_strcmp(const char **esc, const char **str, const char *except)
     46{
     47	const char *e, *s;
     48
     49	for (e = *esc, s = *str; *s && *e; s++, e++) {
     50		if (*e == '\\') e++;
     51		else if (strchr(except, *e)) return true;
     52		if (*s != *e) return false;
     53	}
     54	*esc = e;
     55	*str = s;
     56
     57	return (!*s && !*e) || strchr(except, *e);
     58}
     59
     60static bool
     61esc_strstr(const char **esc, const char **str, const char *except)
     62{
     63	for (; **str; (*str)++) {
     64		if (esc_strcmp(esc, str, except))
     65			return true;
     66	}
     67
     68	return false;
     69}
     70
     71static bool
     72match(const char *pat, const char *str)
     73{
     74	const char *p;
     75
     76	p = pat;
     77	while (1) {
     78		if (p == pat) {
     79			if (!esc_strcmp(&p, &str, "*/"))
     80				return false;
     81		} else {
     82			if (!esc_strstr(&p, &str, "*/"))
     83				return false;
     84		}
     85		if (*p == '/' || !*p)
     86			break;
     87		p += 1;
     88	}
     89
     90	return true;
     91}
     92
     93static bool
     94addprefix(char *buf, const char *pat, size_t cap,
     95	size_t off, size_t *consumed, size_t *added)
     96{
     97	const char *slash;
     98	const char *star;
     99	const char *tok;
    100	size_t len;
    101
    102	*added = *consumed = 0;
    103
    104	star = strchr(pat, '*');
    105	if (!star) {
    106		*consumed = *added = strlen(pat);
    107		return append(buf, pat, added, cap, off, true);
    108	}
    109
    110	slash = strchr(pat, '/');
    111	if (!slash || slash > star)
    112		return true;
    113
    114	tok = pat;
    115	do {
    116		len = (size_t) (slash + 1 - tok);
    117		*consumed += len;
    118		if (slash == pat || len > 1) {
    119			if (!append(buf, tok, &len, cap, off + *added, !slash))
    120				return false;
    121			*added += len;
    122		}
    123		tok = slash + 1;
    124		slash = strchr(slash + 1, '/');
    125	} while (slash && slash < star);
    126
    127	return true;
    128}
    129
    130static void
    131glob_r_match(int fd, char *buf, size_t off, size_t cap, const char *pat)
    132{
    133	const char *slash;
    134	struct dirent *ent;
    135	DIR *d;
    136	size_t len;
    137	int sfd;
    138
    139	if (!(d = fdopendir(fd))) {
    140		fprintf(stderr, "glob: opendir '%s': %s\n",
    141			*buf ? buf : ".", strerror(errno));
    142		return;
    143	}
    144
    145	slash = strchr(pat, '/');
    146	while ((ent = readdir(d))) {
    147		if (!strcmp(ent->d_name, ".")) continue;
    148		if (!strcmp(ent->d_name, "..")) continue;
    149
    150		if (*ent->d_name == '.' && !show_hidden) continue;
    151
    152		if (!match(pat, ent->d_name)) continue;
    153
    154		if (slash && ent->d_type == DT_DIR) {
    155			len = strlen(ent->d_name);
    156			if (!append(buf, ent->d_name, &len, cap, off, false))
    157				continue;
    158			if ((sfd = opendirat(fd, buf, ent->d_name)) < 0)
    159				continue;
    160			glob_r(sfd, buf, off + len, cap, slash + 1);
    161			close(sfd);
    162		} else if (!slash) {
    163			len = strlen(ent->d_name);
    164			if (!append(buf, ent->d_name, &len, cap, off, true))
    165				continue;
    166			puts(buf);
    167		}
    168	}
    169
    170	closedir(d);
    171}
    172
    173static void
    174glob_r(int fd, char *buf, size_t off, size_t cap, const char *pat)
    175{
    176	struct stat stat;
    177	size_t plen;
    178	size_t len;
    179	int sfd;
    180
    181	if (!addprefix(buf, pat, cap, off, &plen, &len))
    182		return;
    183
    184	if (len) {
    185		if (!pat[plen]) {
    186			if (!fstatat(fd, buf + off, &stat, 0)) {
    187				puts(buf);
    188			}
    189		} else {
    190			if ((sfd = opendirat(fd, buf, buf + off)) < 0)
    191				return;
    192			glob_r_match(sfd, buf, off + len, cap, pat + plen);
    193			close(sfd);
    194		}
    195	} else {
    196		glob_r_match(fd, buf, off, cap, pat + plen);
    197	}
    198}
    199
    200static void
    201glob(char *pat)
    202{
    203	char buf[PATH_MAX];
    204	char *end;
    205
    206	buf[0] = '\0';
    207
    208	end = pat + strlen(pat);
    209	while (end > pat && end[-1] == '/')
    210		end--;
    211	*end = '\0';
    212
    213	glob_r(AT_FDCWD, buf, 0, PATH_MAX, pat);
    214}
    215
    216int
    217main(int argc, char **argv)
    218{
    219	char **arg;
    220
    221	if (!argc) return 1;
    222
    223	for (arg = argv + 1; *arg; arg++) {
    224		if (!strcmp(*arg, "-h")) {
    225			printf("Usage: glob [-h] [-a]\n");
    226			return 0;
    227		} else if (!strcmp(*arg, "-a")) {
    228			show_hidden = true;
    229		} else {
    230			if (!strcmp(*arg, "--")) arg++;
    231			break;
    232		}
    233	}
    234
    235	for (; *arg; arg++) glob(*arg);
    236}