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 }