gawk

[old] Sed-like interface to the Gopher protocol
Log | Files | Refs | LICENSE

commit 2c622122971a43b7929db7cb745ef00066baaae1
parent c1fef69e2942757b90eb6ebe0c0cd231fceb57db
Author: Jacob R. Edwards <jacobouno@protonmail.com>
Date:   Mon,  4 Jan 2021 22:15:40 -0800

Rework command system

- Commands are all located in command.c and sorted alphabetically.

- There is no longer a distinction between filters and commands.

- Instead of a switch statement function commands are now easily
  put into an array to bind characters to functions.

- gawk() no longer processes the input buffer, it's all done in
  execute().

- Commands are given access to the string they were called with in
  argv[0].

- Commands are each separated from each other by a pipe `|' character.
  This makes the program much simpler since each command gets it's
  own arguments rather than the previous (which was reverted) method
  of trying to tell the caller how many arguments were used.

Diffstat:
MMakefile | 2+-
Acommand.c | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommand.h | 16++++++++++++++++
Mconfig.def.h | 29++++++++++++++++++++++++++---
Agawk.h | 22++++++++++++++++++++++
Mmain.c | 383+++++++++++++++++--------------------------------------------------------------
Mutil.c | 5+++--
7 files changed, 366 insertions(+), 308 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,5 +1,5 @@ TARGET = gawk -OBJS = main.o util.o +OBJS = main.o util.o command.o # paths PREFIX = /usr/local diff --git a/command.c b/command.c @@ -0,0 +1,217 @@ +#include <err.h> +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "gawk.h" +#include "util.h" + +#define LOCAL /* functions local to this file */ + +int +cextern(int argc, const char **argv, int ino, char **item) +{ + char url[MY_URL_MAX]; + int i; + + if (warg(1, -1, argc, argv)) + return FAIL; + + snprintf(url, sizeof(url), "%s%s", item[GI_HOST], item[GI_PATH]); + for (i = 0; i < argc; ++i) + if (strcmp(argv[i], "%") == 0) + argv[i] = url; + + switch (fork()) { + case -1: + warn("unable to fork"); + return FAIL; + case 0: + execvp(*argv, (char *const *)argv); + warn("execvp '%s'", *argv); + _exit(1); + } + + return PASS; +} + +LOCAL int +timid_gopher(const char **addr, const char *path) +{ + if (!exists(path)) + return gopher(addr, path); + warnc(EEXIST, "'%s'", path); + return 1; +} + +int +cfetch(int argc, const char **argv, int ino, char **item) +{ + const char *path; + + if (warg(1, 2, argc, argv)) + return FAIL; + + if (argv[1]) + path = argv[1]; + else { + path = basename(item[GI_PATH]); + if (path == NULL) { + warn("Unable to get basename '%s'", item[GI_PATH]); + return NEXT; + } + } + + if (timid_gopher(itemtoaddr(item), path)) + return NEXT; + warnx("'%s%s' written to '%s'", item[GI_HOST], item[GI_PATH], path); + return PASS; +} + +int +cgoto(int argc, const char **argv, int ino, char **item) +{ + warg(1, 1, argc, argv); + /* TODO: implement 7 type support */ + if (**item == '1') + return gawk(itemtoaddr(item)); + warnc(EFTYPE, "%d", ino); + return NEXT; +} + +int +cprint(int argc, const char **argv, int ino, char **item) +{ + warg(1, 2, argc, argv); + + if (**item == 'i') + printf(" %s\n", item[GI_INFO] + 1); + else { + /* TODO: Make simple getopt like function for these flags */ + if (argc == 2 && strchr(argv[1], 'n')) + printf("%4d", ino); + printf(" [%c] %s", item[GI_INFO][0], item[GI_INFO] + 1); + if (argc == 2 && strchr(argv[1], 'v')) + printf(" (%s%s)", item[GI_HOST], item[GI_PATH]); + putchar('\n'); + } + return PASS; +} + +int +findex(int argc, const char **argv, int ino, char **item) +{ + int i; + unsigned int n; + + if (warg(2, -1, argc, argv)) + return FAIL; + + for (i = 1; i < argc; ++i) { + if (strtorange(&n, 0, UINT_MAX, argv[i])) + return FAIL; + if (ino == n) + return PASS; + } + return NEXT; +} + +int +frange(int argc, const char **argv, int ino, char **item) +{ + int i, j; + unsigned int range[2]; + + if (warg(3, -1, argc, argv)) + return FAIL; + if (argc % 2 != 1) { + warn("%s: Invalid ranges", *argv); + return FAIL; + } + + for (i = 1; i < argc; i += 2) { + for (j = 0; j < 2; ++j) + if (strtorange(&range[j], 0, UINT_MAX, argv[i + j])) + return FAIL; + if (range[0] > range[1]) { + warn("%s: %d greater than %d", *argv, range[0], range[1]); + return FAIL; + } + if (ino < range[0] || ino > range[1]) + return NEXT; + } + + return PASS; +} + +int +fstring(int argc, const char **argv, int ino, char **item) +{ + int i; + + if (warg(2, -1, argc, argv)) + return FAIL; + for (i = 1; i < argc; ++i) + if (strcasestr(item[GI_INFO], argv[1]) == NULL) + return NEXT; + return PASS; +} + +int +ftype(int argc, const char **argv, int ino, char **item) +{ + if (warg(2, 2, argc, argv)) + return FAIL; + if (strchr(argv[1], *item[GI_INFO]) != NULL) + return PASS; + return NEXT; +} + +int +sexit(int argc, const char **argv, int depth, char **addr) +{ + warg(1, 1, argc, argv); + return depth; +} + +int +sgoto(int argc, const char **argv, int depth, char **addr) +{ + const char *newaddr[AR_NULL]; + + if (warg(1, 4, argc, argv)) + return ERROR; + + newaddr[AR_HOST] = addr[AR_HOST]; + newaddr[AR_PATH] = "/"; + newaddr[AR_PORT] = addr[AR_PORT]; + + if (argc > 1) { + newaddr[AR_PATH] = argv[1]; + if (argc > 2) { + newaddr[AR_HOST] = argv[2]; + if (argc > 3) + newaddr[AR_PORT] = argv[3]; + } + } + + return gawk(newaddr); +} + +int +sunwind(int argc, const char **argv, int depth, char **addr) +{ + unsigned int n; + + if (warg(1, 2, argc, argv)) + return ERROR; + + if (argc == 1) + n = 1; + else if (strtorange(&n, 1, INT_MAX, argv[1])) + return ERROR; + return n; +} diff --git a/command.h b/command.h @@ -0,0 +1,16 @@ +#ifndef _command_h +#define _command_h + +int cextern(int, const char **, int, char **); +int cfetch(int, const char **, int, char **); +int cgoto(int, const char **, int, char **); +int cprint(int, const char **, int, char **); +int findex(int, const char **, int, char **); +int frange(int, const char **, int, char **); +int fstring(int, const char **, int, char **); +int ftype(int, const char **, int, char **); +int sexit(int, const char **, int, char **); +int sgoto(int, const char **, int, char **); +int sunwind(int, const char **, int, char **); + +#endif /* _command_h */ diff --git a/config.def.h b/config.def.h @@ -1,10 +1,15 @@ -/* gawk config.h */ +#include "command.h" +#include "gawk.h" /* default gopher hole (path, host, port): */ static const char *default_address[] = { "/", "localhost", "70" }; -#define IFS ":\t " /* input field seporator */ -#define ISS ";\n" /* input statement seporator */ +/* how long to wait for response */ +static int timeout = 5 * 1000; + +#define IFS0 ";\n" /* statement sep */ +#define IFS1 "|" /* command sep */ +#define IFS2 " \t," /* argument sep */ /* arbitrary limits */ #define CHUNK_SIZE 256 /* for slurping data */ @@ -13,4 +18,22 @@ static const char *default_address[] = { "/", "localhost", "70" }; #define MY_INPUT_MAX 128 #define MY_LINE_MAX 256 #define MY_PATH_MAX 128 +#define MY_PIPE_MAX 12 #define MY_URL_MAX 72 + +static const struct command item_commands[] = { + { 'e', cextern }, + { 'f', cfetch }, + { 'g', cgoto }, + { 'i', findex }, + { 'p', cprint }, + { 'r', frange }, + { 's', fstring }, + { 't', ftype } +}; + +static const struct command commands[] = { + { 'u', sunwind }, + { 'G', sgoto }, + { 'q', sexit } +}; diff --git a/gawk.h b/gawk.h @@ -0,0 +1,22 @@ +#ifndef _gawk_h +#define _gawk_h + +#define LEN(X) (sizeof(X) / sizeof(*X)) + +enum address { AR_PATH, AR_HOST, AR_PORT, AR_NULL }; /* NOTE: see itemtoaddr() */ +enum gphitem { GI_INFO, GI_PATH, GI_HOST, GI_PORT, GI_PLUS, GI_NULL }; +enum status { ERROR = -1, OK, UNWIND }; +enum pipe_status { FAIL = -2, NEXT, PASS }; + +typedef int (command)(int, const char **, int, char **); + +struct command { + int c; + command *f; +}; + +char **const itemtoaddr(char **const); +int gopher(const char **, const char *); +int gawk(const char **); + +#endif /* _gawk_h */ diff --git a/main.c b/main.c @@ -19,40 +19,27 @@ #include <dirent.h> #include <err.h> #include <errno.h> -#include <libgen.h> -#include <limits.h> #include <netdb.h> #include <poll.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <unistd.h> +#include "command.h" #include "config.h" +#include "gawk.h" #include "util.h" -#define LEN(X) (sizeof(X) / sizeof(*X)) - -enum address { AR_PATH, AR_HOST, AR_PORT, AR_NULL }; /* NOTE: see itemtoaddr() */ -enum gphitem { GI_INFO, GI_PATH, GI_HOST, GI_PORT, GI_PLUS, GI_NULL }; -enum status { ERROR = -1, OK, UNWIND }; - -typedef int (item_command)(int, const char **, int, const char **); -typedef int (filter)(int, const char **, unsigned int, const char **, item_command *); -typedef int (command)(int, const char **, int, const char **); - -int gawk(const char **); +#define ENOCOM "Not a command" static char tmpdir[] = "/tmp/gawk-XXXXXXXXXXX"; static int fatal; -static int timeout = 5 * 1000; -/* This exists to make it easy to track down later */ -const char ** -itemtoaddr(const char **request) +char **const +itemtoaddr(char **const request) { - return request + 1; + return request + 1; } int @@ -234,311 +221,110 @@ gphsplit(char **item, int size, char *buf) return 0; } -int -run_command(const char *cache, filter *filter, int argc, const char **argv, item_command *command) -{ - FILE *fp; - char buf[MY_LINE_MAX]; - char *item[GI_NULL + 2]; - int error; - unsigned int i; - - fp = wfopen(cache, "r"); - if (fp == NULL) - return UNWIND; - - error = 0; - for (i = 0; !error && fgets(buf, sizeof(buf), fp) && *buf != '.'; ++i) { - if (gphsplit(item, sizeof(item), buf) == 0) { - if (filter) - error = filter(argc, argv, i, (const char **)item, command); - else - error = command(argc, argv, i, (const char **)item); - } - } - wfclose(fp); - return error; -} - -int -findex(int argc, const char **argv, unsigned int index, const char **item, - item_command *command) -{ - unsigned int n; - - if (warg(1, -1, argc, argv) || strtorange(&n, 0, UINT_MAX, argv[0])) - return ERROR; - if (index == n) - return command(argc - 1, argv + 1, index, item); - else if (index > n) - return ERROR; - return 0; -} - -/* NOTE: inclusive */ -int -frange(int argc, const char **argv, unsigned int index, const char **item, - item_command *command) +command * +get_command(const char *s, const struct command *commands, size_t size) { int i; - unsigned int range[2]; - if (warg(2, -1, argc, argv)) - return ERROR; - for (i = 0; i < 2; ++i) - if (strtorange(&range[i], 0, UINT_MAX, argv[i])) - return ERROR; - - if (index > range[1]) - return ERROR; - else if (index >= range[0]) - return command(argc - 2, argv + 2, index, item); - return 0; -} - -int -fstring(int argc, const char **argv, unsigned int index, const char **item, - item_command *command) -{ - if (warg(1, -1, argc, argv)) - return ERROR; - if (strcasestr(item[GI_INFO], argv[0]) != NULL) - return command(argc - 1, argv + 1, index, item); - return 0; -} - -int -ftype(int argc, const char **argv, unsigned int index, const char **item, - item_command *command) -{ - if (warg(1, -1, argc, argv)) - return ERROR; - if (strchr(argv[0], *item[GI_INFO]) != NULL) - return command(argc - 1, argv + 1, index, item); - return 0; + if (strlen(s) != 1) + return NULL; + for (i = 0; i < size; ++i) + if (*s == commands[i].c) + return commands[i].f; + return NULL; } int -sgoto(int argc, const char **argv, int depth, const char **addr) +runpipe(int count, command **commands, int *indexes, const int *acs, + const char *avs[MY_PIPE_MAX][MY_ARGV_MAX], char **item) { - const char *newaddr[AR_NULL]; - - if (warg(0, 3, argc, argv)) - return ERROR; - - newaddr[AR_HOST] = addr[AR_HOST]; - newaddr[AR_PATH] = "/"; - newaddr[AR_PORT] = addr[AR_PORT]; + int i; + int r; - if (argc > 0) { - newaddr[AR_PATH] = argv[0]; - if (argc > 1) { - newaddr[AR_HOST] = argv[1]; - if (argc > 2) - newaddr[AR_PORT] = argv[2]; - } + for (i = 0; i < count; ++i) { + r = commands[i](acs[i], avs[i], indexes[i]++, item); + if (r != PASS) + return r; } - - return gawk(newaddr); + return PASS; } int -sunwind(int argc, const char **argv, int depth, const char **addr) +pipeitems(const char *cache, int count, const command **commands, + const int *acs, const char *avs[MY_PIPE_MAX][MY_ARGV_MAX]) { - unsigned int n; - - if (warg(0, 1, argc, argv)) - return ERROR; - - if (argc == 0) - n = 1; - else if (strtorange(&n, 1, INT_MAX, argv[0])) - return ERROR; - return n; -} - -int -sexit(int argc, const char **argv, int depth, const char **addr) -{ - warg(0, 0, argc, argv); - return depth; -} + FILE *fp; + char buf[MY_LINE_MAX]; + char *item[GI_NULL + 2]; + int error; + int indexes[count]; -int -cprint(int argc, const char **argv, int i, const char **s) -{ - warg(0, 0, argc, argv); + memset(indexes, 0, sizeof(indexes)); - if (**s == 'i') - printf(" %s\n", s[GI_INFO] + 1); - else - printf(" [%c] %s (%s%s)\n", s[GI_INFO][0], s[GI_INFO] + 1, s[GI_HOST], s[GI_PATH]); - return 0; -} + fp = wfopen(cache, "r"); + if (fp == NULL) + return UNWIND; -int -cprintn(int argc, const char **argv, int i, const char **s) -{ - if (**s != 'i') - printf("%4d", i); - else printf(" "); - return cprint(argc, argv, i, s); -} + error = 0; + while (error != FAIL && fgets(buf, sizeof(buf), fp) && *buf != '.') + if (gphsplit(item, sizeof(item), buf) == 0) + error = runpipe(count, commands, indexes, acs, avs, item); -int -timid_gopher(const char **addr, const char *path) -{ - if (!exists(path)) - return gopher(addr, path); - warnc(EEXIST, "'%s'", path); - return 1; + wfclose(fp); + return OK; } int -cfetch(int argc, const char **argv, int i, const char **item) +run_command(int argc, const char **argv, int depth, const char **addr) { - const char *path; + command *c; - if (warg(0, 1, argc, argv)) + c = get_command(*argv, commands, LEN(commands)); + if (c == NULL) { + warnx("'%s': %s.", *argv, ENOCOM); return ERROR; - - if (argv[0]) - path = argv[0]; - else { - path = basename(item[GI_PATH]); - if (path == NULL) { - warn("Unable to get basename '%s'", item[GI_PATH]); - return ERROR; - } } - - if (timid_gopher(itemtoaddr(item), path)) - return ERROR; - warnx("'%s%s' written to '%s'", item[GI_HOST], item[GI_PATH], path); - return 0; + return c(argc, argv, depth, addr); } int -cextern(int argc, const char **argv, int index, const char **item) +execute(const char *cache, char *input, int depth, const char **addr) { - char url[MY_URL_MAX]; + char *avs[MY_PIPE_MAX][MY_ARGV_MAX]; + char *cbufs[MY_PIPE_MAX]; + command *commands[MY_PIPE_MAX]; + int acs[MY_PIPE_MAX]; + int cbufslen; int i; - if (warg(1, -1, argc, argv)) + if (*input == '\0') return ERROR; - snprintf(url, sizeof(url), "%s%s", item[GI_HOST], item[GI_PATH]); - for (i = 0; i < argc; ++i) - if (strcmp(argv[i], "%") == 0) - argv[i] = url; - - switch (fork()) { - case -1: - warn("unable to fork"); - return ERROR; - case 0: - execvp(*argv, (char *const *)argv); - warn("execvp '%s'", *argv); - _exit(1); - } - - return 0; -} - -int -cgoto(int argc, const char **argv, int index, const char **item) -{ - warg(0, 0, argc, argv); - /* TODO: implement 7 type support */ - if (**item == '1') - return gawk(itemtoaddr(item)); - warnc(EFTYPE, "%d", index); - return ERROR; -} - -item_command * -get_ic(int c) -{ - switch (c) { - case 'p': - return cprint; - case 'n': - return cprintn; - case 'f': - return cfetch; - case 'e': - return cextern; - case 'g': - return cgoto; - default: - return NULL; - } -} - -filter * -get_filter(int c) -{ - switch (c) { - case 't': - return ftype; - case 'i': - return findex; - case 's': - return fstring; - case 'r': - return frange; - default: - return NULL; - } -} - -command * -get_rc(int c) -{ - switch (c) { - case 'u': - return sunwind; - case 'G': - return sgoto; - case 'q': - return sexit; - default: - return NULL; + cbufslen = argsplit(cbufs, LEN(cbufs), input, IFS1, 0); + for (i = 0; i < cbufslen; ++i) { + if (i >= MY_PIPE_MAX) { + warnx("Pipeline too long."); + return ERROR; + } + acs[i] = argsplit(avs[i], LEN(avs[i]), cbufs[i], IFS2, 1); + if (acs[i] == 0) { + warnx("Empty pipe (#%d).", ++i); + return ERROR; + } } -} - -int -execute(int argc, const char **argv, int depth, const char *cache, - const char **addr) -{ - command *rc; - filter *filter; - item_command *ic; - if (argc == 0 || argv[0][0] == '\0') - return ERROR; - - filter = NULL; - switch (strlen(*argv)) { - case 1: - if ((rc = get_rc(**argv))) - return rc(argc - 1, argv + 1, depth, addr); - ic = get_ic(**argv); - break; - case 2: - if ((filter = get_filter(**argv)) == NULL) - goto invalid; - ic = get_ic(argv[0][1]); - break; - default: -invalid: - warnx("'%s': invalid statement.", *argv); - return ERROR; + for (i = 0; i < cbufslen; ++i) { + commands[i] = get_command(avs[i][0], + item_commands, LEN(item_commands)); + if (commands[i] == NULL) { + if (i == 0) + return run_command(acs[0], avs[0], depth, addr); + warn("'%s': %s.", avs[i][0], ENOCOM); + return ERROR; + } } - if (ic == NULL) - goto invalid; - return run_command(cache, filter, argc - 1, argv + 1, ic); + return pipeitems(cache, cbufslen, commands, acs, avs); } void @@ -554,11 +340,9 @@ int gawk(const char **addr) { static int depth; - char *argv[MY_ARGV_MAX]; char cache[MY_PATH_MAX]; char inbuf[MY_INPUT_MAX]; char prompt[64]; - int argc; int done; int mycache; @@ -569,7 +353,8 @@ gawk(const char **addr) mycache = 1; if (gopher(addr, cache) == 1) return ERROR; - done = run_command(cache, NULL, 0, NULL, cprintn); + /*done = run_command(cache, NULL, 0, NULL, cprintn); + */ } ++depth; @@ -577,16 +362,10 @@ gawk(const char **addr) snprintf(prompt, sizeof(prompt), "(%d) [%s] %s ", depth, addr[AR_HOST], addr[AR_PATH]); done = 0; - while (!done && input(inbuf, sizeof(inbuf), ISS, prompt, stdin) == 0) { - argc = argsplit(argv, LEN(argv), inbuf, IFS, 1); - if (argc < 0) - warnx("Too many arguments."); - else if (argc > 0) { - done = execute(argc, (const char **)argv, - depth, cache, addr); - if (done == ERROR && !fatal) - done = 0; - } + while (!done && input(inbuf, sizeof(inbuf), IFS0, prompt, stdin) == 0) { + done = execute(cache, inbuf, depth, addr); + if (done == ERROR && !fatal) + done = 0; } if (mycache) @@ -648,7 +427,7 @@ main(int argc, char *argv[]) if (mkdtemp(tmpdir) == NULL) err(1, "mkdtemp '%s'", tmpdir); - status = sgoto(argc - 1, (const char **)argv + 1, 0, default_address); + status = sgoto(argc, (const char **)argv, 0, default_address); if (wremove(tmpdir) == -1) return 1; diff --git a/util.c b/util.c @@ -51,9 +51,10 @@ int warg(int min, int max, int argc, const char **ap) { if (max > 0 && argc > max) { - warnx("warning: '%s' and %d more arguments unused.", ap[max], argc - (max + 1)); + warnx("%s: '%s' and %d more arguments unused.", + *ap, ap[max], argc - (max + 1)); } else if (argc < min) { - warnx("error: Not enough arguments."); + warnx("%s: Not enough arguments.", *ap); return 1; }