commit ee4f23354ec62a595e260ce92dd0fda4f9d839ac
Author: Jacob R. Edwards <n/a>
Date: Mon, 5 Dec 2022 11:58:22 -0600
Add functional walk utility
Walk is a utility in 9front to walk a path. This is one written for
UNIX.
Currently, it tries to conform to the behavior specified in 9front's
manual as much as possible, but it may be better to deviate from
that behavior in order to make it work better for UNIX.
Diffstat:
A | Makefile | | | 35 | +++++++++++++++++++++++++++++++++++ |
A | walk.1 | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | walk.c | | | 283 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 457 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,35 @@
+name = walk
+src = walk.c
+obj = ${src:.c=.o}
+objprereq = Makefile
+
+prefix = /usr/local
+manprefix = ${prefix}/man
+
+cc = ${CC}
+cflags = -Wall -Wno-write-strings ${CFLAGS}
+ldflags = ${LDFLAGS}
+
+all: ${name}
+
+.c.o:
+ ${cc} ${cflags} -c -o $@ $<
+
+${obj}: ${objprereq}
+
+${name}: ${obj}
+ ${cc} -o $@ ${obj} ${ldflags}
+
+clean:
+ rm -f ${name} ${obj}
+
+install: ${name}
+ cp -f ${name} ${prefix}/bin
+ cp -f ${name}.1 ${manprefix}/man1
+
+uninstall:
+ rm -f ${prefix}/bin/${name}
+ rm -f ${manprefix}/man1/${name}.1
+
+.SUFFIXES: .c .o
+.PHONY: clean install uninstall
diff --git a/walk.1 b/walk.1
@@ -0,0 +1,139 @@
+.\" Copied from 9front.org. Legal information (as far as I can tell,
+.\" from looking at /lib/legal/NOTICE):
+.\"
+.\" Copyright 20XX 9front authors
+.\" Licensed under the MIT license
+.\"
+.TH WALK 1
+.SH NAME
+walk \- walk a path
+.SH SYNOPSIS
+.B walk
+[
+.B -dftxu
+] [
+.B -n
+.I mind,maxd
+] [
+.B -e
+.I statfmt
+] [
+.I name ...
+]
+.SH DESCRIPTION
+.I Walk
+recursively descends any directory arguments,
+printing the name of each file on a separate line.
+When no arguments are given, the working directory
+is assumed.
+Non-directory arguments are checked for existence,
+then printed, if so.
+.PP
+Options are:
+.TP
+.B -d
+Print only directories.
+.TP
+.B -f
+Print only non-directories.
+.TP
+.B -t
+Print a file only if it has the temporary flag set.
+.TP
+.B -x
+Print a file only if it has any executable bits set.
+.TP
+.B -u
+Unbuffered output.
+.TP
+.B -n min,max
+Set the inclusive range of depths for filtering in results.
+Both
+.I min
+and
+.I max
+are optional.
+An argument of
+.I n
+with no comma is equivalent to
+.IR 0,n .
+.TP
+.B -e statfmt
+Specify the output format.
+Each character in
+.I statfmt
+specifies a file attribute to display.
+The printed attributes are separated by spaces.
+.RS \n(INu
+.PP
+The statfmt characters are as follows:
+.TF .
+.TP
+.B U
+owner name (uid)
+.TP
+.B G
+group name (gid)
+.TP
+.B M
+name of last user to modify (muid)
+.TP
+.B a
+last access time (atime)
+.TP
+.B m
+last modification time (mtime)
+.TP
+.B n
+final path element (name)
+.TP
+.B p
+path
+.TP
+.B q
+qid path.version.type (see
+.IR stat (2))
+.TP
+.B s
+size in bytes
+.TP
+.B x
+permissions
+.TP
+.B D
+server device
+.TP
+.B T
+server type (kernel device rune)
+.PD
+.PP
+The default statfmt is simply,
+.IR p .
+.SH EXAMPLES
+List files in a directory, sorted by modification time.
+.IP
+.EX
+walk -femp catpics | sort -n | sed 's/^[^ ]+ //'
+.EE
+.PP
+Print the size and path of files (excluding dirs)
+in the working directory.
+.IP
+.EX
+walk -fn1 -esp
+.EE
+.PD
+.SH SOURCE
+.B /sys/src/cmd/walk.c
+.SH SEE ALSO
+.IR ls (1),
+.IR du (1)
+.SH BUGS
+Manipulating ifs is a nuisance.
+.PP
+File names are assumed to not contain newlines.
+.PP
+Correct invocation requires too much thought.
+.SH HISTORY
+.I Walk
+first appeared in 9front (March, 2019).
diff --git a/walk.c b/walk.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2022 Jacob R. Edwards <jacob@jacobedwards.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This program tries to conform to the behavior defined in 9front's
+ * ls(1) (http://man.9front.org/1/ls) as far as possible. It could
+ * be good to adapt it to UNIX systems instead (i.e. removing
+ * impossible formats and file modes).
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fts.h>
+
+void
+die(char *s)
+{
+ perror(s);
+ exit(1);
+}
+
+int
+getnum(char *s, char **ep)
+{
+ long n;
+
+ n = strtol(s, ep, 10);
+ if (n < 0 || n > INT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ return n;
+}
+
+int
+parse_n(char *arg, int *min, int *max)
+{
+ char *ep;
+
+ *min = getnum(arg, &ep);
+ if (*min < 0)
+ return 1;
+
+ if (*ep == ',') {
+ *max = getnum(ep + 1, &ep);
+ return *max < 0;
+ } else if (!*ep) {
+ *max = *min;
+ *min = 0;
+ return 0;
+ }
+
+ errno = EINVAL;
+ return 1;
+}
+
+void
+xprintf(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (vprintf(fmt, ap) < 0)
+ die("printf");
+ va_end(ap);
+}
+
+void
+permstr(mode_t mode, char buf[10])
+{
+ int i = 0;
+
+ buf[i++] = (mode & S_IRUSR) ? 'r' : '-';
+ buf[i++] = (mode & S_IWUSR) ? 'w' : '-';
+ buf[i++] = (mode & S_IXUSR) ? 'x' : '-';
+
+ buf[i++] = (mode & S_IRGRP) ? 'r' : '-';
+ buf[i++] = (mode & S_IWGRP) ? 'w' : '-';
+ buf[i++] = (mode & S_IXGRP) ? 'x' : '-';
+
+ buf[i++] = (mode & S_IROTH) ? 'r' : '-';
+ buf[i++] = (mode & S_IWOTH) ? 'w' : '-';
+ buf[i++] = (mode & S_IXOTH) ? 'x' : '-';
+
+ buf[i] = '\0';
+}
+
+int
+printent(char *fmt, char *path, struct stat *sb)
+{
+ int first;
+ char perms[10];
+
+ first = 1;
+ for (; *fmt; ++fmt) {
+ if (first)
+ first = 0;
+ else if (putchar(' ') == EOF)
+ return 1;
+
+ /*
+ * NOTE: These are all guesses, I haven't looked
+ * at the 9front source code or ran the original
+ * program with the intent to study the formats.
+ */
+ switch (*fmt) {
+ case 'U':
+ xprintf("%d", sb->st_uid);
+ break;
+ case 'G':
+ xprintf("%d", sb->st_gid);
+ break;
+ case 'a':
+ xprintf("%ld", sb->st_atim.tv_sec);
+ break;
+ case 'm':
+ xprintf("%ld", sb->st_mtim.tv_sec);
+ break;
+ case 'n':
+ xprintf("%s", basename(path));
+ break;
+ case 'p':
+ xprintf("%s", path);
+ break;
+ case 's':
+ xprintf("%zu", sb->st_size);
+ break;
+ case 'x':
+ permstr(sb->st_mode, perms);
+ xprintf("%c-%s", S_ISDIR(sb->st_mode) ? 'd' : '-',
+ perms);
+ break;
+ case 'M':
+ case 'q':
+ case 'D':
+ case 'T':
+ xprintf("n/a");
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ return putchar('\n') != '\n';
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ FTS *fts;
+ FTSENT *ent;
+ int exec;
+ int vbuf;
+ int min, max;
+ char *fmt;
+ int show;
+ char *path;
+ int offset;
+ enum {
+ DIR = 1, NOTDIR
+ } type;
+
+ type = 0;
+ exec = 0;
+ vbuf = 0;
+ min = max = -1;
+ fmt = "p";
+ while ((c = getopt(argc, argv, "dftxuE:n:e:")) != EOF) {
+ switch (c) {
+ case 'd':
+ type = DIR;
+ break;
+ case 'f':
+ type = NOTDIR;
+ break;
+ case 't':
+ die("No temporary flag on this system");
+ break;
+ case 'x':
+ exec = 1;
+ break;
+ case 'u':
+ if (setvbuf(stdout, NULL, 0, _IONBF))
+ die("setvbuf _IONBF");
+ vbuf = _IONBF;
+ break;
+ case 'n':
+ if (parse_n(optarg, &min, &max))
+ perror(optarg);
+ break;
+ case 'e':
+ if (optarg[strcspn(optarg, "MqDT")]) {
+ errno = EOPNOTSUPP;
+ die(optarg);
+ }
+ case 'E':
+ /* fall-through */
+ if (optarg[strspn(optarg, "UGMamnpqsxDT")]) {
+ errno = EINVAL;
+ die(optarg);
+ }
+ fmt = optarg;
+ break;
+ default:
+ /*
+ * -E is undocumented because it took up
+ * too much space and wasn't in the original.
+ */
+ fputs("usage: walk [-dftxu] [-n mind,maxd] [-e statfmt] [name ...]\n", stderr);
+ exit(1);
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (!vbuf && setvbuf(stdout, NULL, 0, _IOFBF))
+ perror("setvbuf _IOFBF");
+
+ if (argc) {
+ offset = 0;
+ } else {
+ offset = 2;
+ argv = (char *[]){ ".", NULL };
+ }
+ fts = fts_open(argv, FTS_PHYSICAL | FTS_SEEDOT, NULL);
+ if (!fts)
+ die("fts open");
+
+ while ((ent = fts_read(fts))) {
+ path = ent->fts_path + offset;
+ switch (ent->fts_info) {
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ perror(path);
+ case FTS_DP:
+ continue;
+ }
+
+ if (strcmp(ent->fts_name, ".") == 0 || strcmp(ent->fts_name, "..") == 0)
+ continue;
+ if (min >= ent->fts_level && (max < 0 || ent->fts_level >= max))
+ continue;
+
+ if (exec)
+ show = access(ent->fts_accpath, X_OK);
+ else if (type == DIR)
+ show = S_ISDIR(ent->fts_statp->st_mode);
+ else if (type == NOTDIR)
+ show = !S_ISDIR(ent->fts_statp->st_mode);
+ else
+ show = 1;
+
+ if (show && printent(fmt, path, ent->fts_statp))
+ die(path);
+ }
+
+ return 0;
+}