walk

UNIX port of the 9front walk(1) utility
git clone git://jacobedwards.org/walk
Log | Files | Refs

walk.c (6089B)


      1 /*
      2  * Copyright 2022 Jacob R. Edwards <jacob@jacobedwards.org>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 /*
     18  * This program tries to conform to the behavior defined in 9front's
     19  * ls(1) (http://man.9front.org/1/ls) as far as possible. It could
     20  * be good to adapt it to UNIX systems instead (i.e. removing
     21  * impossible formats and file modes).
     22  */
     23 
     24 #include <sys/stat.h>
     25 #include <sys/types.h>
     26 
     27 #include <errno.h>
     28 #include <libgen.h>
     29 #include <limits.h>
     30 #include <stdarg.h>
     31 #include <stdio.h>
     32 #include <stdlib.h>
     33 #include <string.h>
     34 #include <unistd.h>
     35 
     36 #include <fts.h>
     37 
     38 void
     39 die(char *s)
     40 {
     41 	perror(s);
     42 	exit(1);
     43 }
     44 
     45 int
     46 getnum(char *s, char **ep)
     47 {
     48 	long n;
     49 
     50 	n = strtol(s, ep, 10);
     51 	if (n < 0 || n > INT_MAX) {
     52 		errno = ERANGE;
     53 		return -1;
     54 	}
     55 	return n;
     56 }
     57 
     58 int
     59 parse_n(char *arg, int *min, int *max)
     60 {
     61 	int n;
     62 
     63 	if (*arg == ',') {
     64 		*min = 0;
     65 	} else {
     66 		*min = getnum(arg, &arg);
     67 		if (*min < 0)
     68 			return 1;
     69 	}
     70 
     71 	if (*arg == ',') {
     72 		if (!arg[1]) {
     73 			*max = -1;
     74 			return 0;
     75 		}
     76 		*max = getnum(arg + 1, &arg);
     77 		if (*arg) {
     78 			errno = EINVAL;
     79 			return 1;
     80 		}
     81 		return *max < 0;
     82 	} else if (!*arg) {
     83 		*max = *min;
     84 		*min = 0;
     85 		return 0;
     86 	}
     87 
     88 	errno = EINVAL;
     89 	return 1;
     90 }
     91 
     92 void
     93 xprintf(char *fmt, ...)
     94 {
     95 	va_list ap;
     96 
     97 	va_start(ap, fmt);
     98 	if (vprintf(fmt, ap) < 0)
     99 		die("printf");
    100 	va_end(ap);
    101 }
    102 
    103 void
    104 permstr(mode_t mode, char buf[10])
    105 {
    106 	int i = 0;
    107 
    108 	buf[i++] = (mode & S_IRUSR) ? 'r' : '-';
    109 	buf[i++] = (mode & S_IWUSR) ? 'w' : '-';
    110 	buf[i++] = (mode & S_IXUSR) ? 'x' : '-';
    111 
    112 	buf[i++] = (mode & S_IRGRP) ? 'r' : '-';
    113 	buf[i++] = (mode & S_IWGRP) ? 'w' : '-';
    114 	buf[i++] = (mode & S_IXGRP) ? 'x' : '-';
    115 
    116 	buf[i++] = (mode & S_IROTH) ? 'r' : '-';
    117 	buf[i++] = (mode & S_IWOTH) ? 'w' : '-';
    118 	buf[i++] = (mode & S_IXOTH) ? 'x' : '-';
    119 
    120 	buf[i] = '\0';
    121 }
    122 
    123 int
    124 printent(char *fmt, char *path, struct stat *sb)
    125 {
    126 	int first;
    127 	char perms[10];
    128 
    129 	first = 1;
    130 	for (; *fmt; ++fmt) {
    131 		if (first)
    132 			first = 0;
    133 		else if (putchar(' ') == EOF)
    134 			return 1;
    135 
    136                 /*
    137 		 * NOTE: These are all guesses, I haven't looked
    138                  * at the 9front source code or ran the original
    139                  * program with the intent to study the formats.
    140 		 */
    141 		switch (*fmt) {
    142 		case 'U':
    143 			xprintf("%d", sb->st_uid);
    144 			break;
    145 		case 'G':
    146 			xprintf("%d", sb->st_gid);
    147 			break;
    148 		case 'a':
    149 			xprintf("%ld", sb->st_atim.tv_sec);
    150 			break;
    151 		case 'm':
    152 			xprintf("%ld", sb->st_mtim.tv_sec);
    153 			break;
    154 		case 'n':
    155 			xprintf("%s", basename(path));
    156 			break;
    157 		case 'p':
    158 			xprintf("%s", path);
    159 			break;
    160 		case 's':
    161 			xprintf("%zu", sb->st_size);
    162 			break;
    163 		case 'x':
    164 			permstr(sb->st_mode, perms);
    165 			xprintf("%c-%s", S_ISDIR(sb->st_mode) ? 'd' : '-',
    166 			    perms);
    167 			break;
    168 		case 'M':
    169 		case 'q':
    170 		case 'D':
    171 		case 'T':
    172 			xprintf("n/a");
    173 			break;
    174 		default:
    175 			return 1;
    176 		}
    177 	}
    178 
    179 	return putchar('\n') != '\n';
    180 }
    181 
    182 int
    183 main(int argc, char *argv[])
    184 {
    185 	int c;
    186 	FTS *fts;
    187 	FTSENT *ent;
    188 	int exec;
    189 	int vbuf;
    190 	int min, max;
    191 	char *fmt;
    192 	int show;
    193 	char *path;
    194 	int offset;
    195 	int depth;
    196 	enum {
    197 		DIR = 1, NOTDIR
    198 	} type;
    199 #ifdef __OpenBSD__
    200 	int i;
    201 
    202 	if (pledge("stdio rpath unveil", NULL))
    203 		die("pledge");
    204 #endif
    205 
    206 	type = 0;
    207 	exec = 0;
    208 	vbuf = 0;
    209 	min = max = -1;
    210 	fmt = "p";
    211 	while ((c = getopt(argc, argv, "dftxuE:n:e:")) != EOF) {
    212 		switch (c) {
    213 		case 'd':
    214 			type = DIR;
    215 			break;
    216 		case 'f':
    217 			type = NOTDIR;
    218 			break;
    219 		case 't':
    220 			die("No temporary flag on this system");
    221 			break;
    222 		case 'x':
    223 			exec = 1;
    224 			break;
    225 		case 'u':
    226 			if (setvbuf(stdout, NULL, 0, _IONBF))
    227 				die("setvbuf _IONBF");
    228 			vbuf = _IONBF;
    229 			break;
    230 		case 'n':
    231 			if (parse_n(optarg, &min, &max))
    232 				die(optarg);
    233 			break;
    234 		case 'e':
    235 			if (optarg[strcspn(optarg, "MqDT")]) {
    236 				errno = EOPNOTSUPP;
    237 				die(optarg);
    238 			}
    239 			/* fall-through */
    240 		case 'E':
    241 			if (optarg[strspn(optarg, "UGMamnpqsxDT")]) {
    242 				errno = EINVAL;
    243 				die(optarg);
    244 			}
    245 			fmt = optarg;
    246 			break;
    247 		default:
    248                         /*
    249 			 * -E is undocumented because it took up
    250                          * too much space and wasn't in the original.
    251                          */
    252 			fputs("usage: walk [-dftxu] [-n mind,maxd] [-e statfmt] [name ...]\n", stderr);
    253 			exit(1);
    254 		}
    255 	}
    256 	argv += optind;
    257 	argc -= optind;
    258 
    259 	if (argc) {
    260 		offset = 0;
    261 	} else {
    262 		offset = 2;
    263 		argv = (char *[]){ ".", NULL };
    264 		++argc;
    265 	}
    266 
    267 #ifdef __OpenBSD__
    268 	/* NOTE: the error for ENOENT is more helpful when running OpenBSD */
    269 	for (i = 0; i < argc; ++i)
    270 		if (unveil(argv[i], "r"))
    271 			die(argv[i]);
    272 	if (unveil(NULL, NULL))
    273 		die("unveil");
    274 #endif
    275 
    276 	if (!vbuf && setvbuf(stdout, NULL, 0, _IOFBF))
    277 		perror("setvbuf _IOFBF");
    278 
    279 	fts = fts_open(argv, FTS_PHYSICAL, NULL);
    280 	if (!fts)
    281 		die("fts open");
    282 
    283 	while ((ent = fts_read(fts))) {
    284 		if (offset && strcmp(ent->fts_path, ".") == 0)
    285 			continue;
    286 		path = ent->fts_path + offset;
    287 
    288 		switch (ent->fts_info) {
    289 		case FTS_DNR:
    290 		case FTS_ERR:
    291 		case FTS_NS:
    292 			perror(path);
    293 		case FTS_DP:
    294 			continue;
    295 		}
    296 
    297 		depth = ent->fts_level - 1;
    298 		if (max >= 0 && depth > max) {
    299 			if (fts_set(fts, ent, FTS_SKIP))
    300 				die("set skip");
    301 			continue;
    302 		}
    303 		if (depth < min)
    304 			continue;
    305 
    306 		if (type == DIR)
    307 			show = S_ISDIR(ent->fts_statp->st_mode);
    308 		else if (type == NOTDIR)
    309 			show = !S_ISDIR(ent->fts_statp->st_mode);
    310 		else
    311 			show = 1;
    312 
    313 		if (exec)
    314 			show = show && access(ent->fts_accpath, X_OK) == 0;
    315 
    316 		if (show && printent(fmt, path, ent->fts_statp))
    317 			die(path);
    318 	}
    319 	fts_close(fts);
    320 
    321 	return 0;
    322 }