gawk

Sed-like interface to the Gopher protocol
git clone git://jacobedwards.org/gawk
Log | Files | Refs | LICENSE

main.c (6078B)


      1 /* Copyright 2020, 2021 Jacob R. Edwards
      2  *
      3  * This file is part of gawk.
      4  *
      5  * This program is free software: you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation, either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include <dirent.h>
     20 #include <errno.h>
     21 #include <signal.h>
     22 #include <stdlib.h>
     23 #include <unistd.h>
     24 #include <string.h>
     25 
     26 #include "command.h"
     27 #include "gawk.h"
     28 #include "net.h"
     29 #include "util.h"
     30 #include "config.h"	/* must be included last */
     31 
     32 static char tmpdir[] = "/tmp/gawk-XXXXXXXXXXX";
     33 static int fatal;
     34 
     35 void
     36 putprompt(char const *prompt)
     37 {
     38 	if (isatty(STDIN_FILENO))
     39 		fputs(prompt, stderr);
     40 	else if (errno == ENOTTY)
     41 		errno = 0;
     42 }
     43 
     44 int
     45 input(char *buf, int size, char const *delims, char const *prompt, FILE *fp)
     46 {
     47 	int next;
     48 	static unsigned int len;
     49 	static char bb[MY_INPUT_MAX];
     50 
     51 	if (len <= 0) {
     52 		putprompt(prompt);
     53 refill_bufbuf:
     54 		if (fgets(bb + len, sizeof(bb) - len, fp) == NULL)
     55 			return 1;
     56 		len = strlen(bb);
     57 	}
     58 
     59 	next = strcspn(bb, delims);
     60 	if (bb[next] == '\0') {
     61 		if (len >= sizeof(buf) - 1) {
     62 too_big:
     63 			warn(0, "input too large");
     64 			return 1;
     65 		}
     66 		goto refill_bufbuf;
     67 	}
     68 	if (next >= size)
     69 		goto too_big;
     70 
     71 	bb[next++] = '\0';
     72 	strlcpy(buf, bb, size);
     73 	memmove(bb, bb + next, len - next);
     74 	len -= next;
     75 
     76 	return 0;
     77 }
     78 
     79 int
     80 runpipe(struct command *cmds, int *indexes, int count, char **item)
     81 {
     82 	int i;
     83 	int r;
     84 
     85 	for (i = 0; i < count; ++i) {
     86 		r = cmds[i].f(cmds[i].argc, cmds[i].argv, indexes[i]++, item);
     87 		if (r != PASS && r != NEXT)
     88 			return r;
     89 		if (cmds[i].negate)
     90 			r = !r;
     91 		if (r)
     92 			return NEXT;
     93 	}
     94 	return PASS;
     95 }
     96 
     97 int
     98 gphsplit(char **item, int size, char *buf)
     99 {
    100 	int n;
    101 
    102 	n = argsplit(item, size, buf, "\t\r", 0) - 1;
    103 	if (n != 4 && (n < 4 || n > 5 || strcmp(item[GI_PLUS], "+") != 0))
    104 		return ERROR;
    105 	return 0;
    106 }
    107 
    108 int
    109 runpipes(char const *cache, struct command *cmds, int count)
    110 {
    111 	FILE *fp;
    112 	char buf[MY_LINE_MAX];
    113 	char *item[GI_NULL + 2];
    114 	int error;
    115 	int indexes[count];
    116 
    117 	memset(indexes, 0, sizeof(indexes));
    118 
    119 	fp = wfopen(cache, "r");
    120 	if (fp == NULL)
    121 		return UNWIND;
    122 
    123 	error = 0;
    124 	while (error != FAIL && fgets(buf, sizeof(buf), fp) && *buf != '.')
    125 		if (gphsplit(item, sizeof(item), buf) == 0)
    126 			error = runpipe(cmds, indexes, count, item);
    127 	wfclose(fp);
    128 	return error;
    129 }
    130 
    131 command *
    132 getcom(char const *s, struct bind const *const binds, int size)
    133 {
    134 	int i;
    135 
    136 	if (strlen(s) != 1)
    137 		return NULL;
    138 	for (i = 0; i < size; ++i)
    139 		if (*s == binds[i].c)
    140 			return binds[i].f;
    141 	return NULL;
    142 }
    143 
    144 int
    145 execute(char const *cache, char *input, int depth, char **addr)
    146 {
    147 	int i;
    148 	int nbufs;
    149 	char *bufs[MY_PIPE_MAX];
    150 	struct command cmds[MY_PIPE_MAX];
    151 
    152 	nbufs = argsplit(bufs, LEN(bufs), input, separators[1], 0);
    153 	if (nbufs < 0) {
    154 		warn(0, "pipeline too long");
    155 		return ERROR;
    156 	}
    157 		
    158 	for (i = 0; i < nbufs; ++i) {
    159 		cmds[i].argc = argsplit(cmds[i].argv, LEN(cmds[i].argv),
    160 		    bufs[i], separators[2], 1);
    161 		if (cmds[i].argc < 1) {
    162 			if (cmds[i].argc < 0)
    163 				warn(0, "command %d: Too many arguments", i);
    164 			else if (nbufs > 1)
    165 				warn(0, "command %d: Empty command", i);
    166 			return ERROR;
    167 		}
    168 		if (cmds[i].argv[0][0] != negate) {
    169 			cmds[i].negate = 0;
    170 		} else {
    171 			cmds[i].negate = 1;
    172 			cmds[i].argv[0]++;
    173 		}
    174 	}
    175 
    176 	for (i = 0; i < nbufs; ++i) {
    177 		cmds[i].f = getcom(cmds[i].argv[0], itembinds, LEN(itembinds));
    178 		if (cmds[i].f == NULL) {
    179 			if (i > 0) {
    180 				warn(0, "'%s': Not a pipeline command",
    181 				    cmds[i].argv[0]);
    182 				return ERROR;
    183 			}
    184 			cmds[i].f = getcom(cmds[i].argv[0], binds, LEN(binds));
    185 			if (cmds[i].f == NULL) {
    186 				warn(0, "'%s': Not a command", cmds[i].argv[0]);
    187 				return ERROR;
    188 			}
    189 			return cmds[i].f(cmds[i].argc, cmds[i].argv, depth, addr);
    190 		}
    191 	}
    192 
    193 	return runpipes(cache, cmds, nbufs);
    194 }
    195 
    196 void
    197 tmp_mkpath(char *cache, char **addr)
    198 {
    199 	char tmp[MY_PATH_MAX];
    200 
    201 	tr(tmp, addr[AR_PATH], '/', '-');
    202 	snprintf(cache, MY_PATH_MAX, "%s/%s_%s_%s",
    203 	    tmpdir, addr[AR_HOST], addr[AR_PORT], tmp);
    204 }
    205 
    206 int
    207 gawk(char **addr)
    208 {
    209 	static int depth;
    210 	char cache[MY_PATH_MAX];
    211 	char inbuf[MY_INPUT_MAX];
    212 	char prompt[MY_PROMPT_MAX];
    213 	int done;
    214 	int mycache;
    215 
    216 	tmp_mkpath(cache, addr);
    217 	if (exists(cache)) {
    218 		mycache = 0;
    219 	} else {
    220 		if (gopher(addr, cache, 0, timeout) == 1)
    221 			return ERROR;
    222 		mycache = 1;
    223 	}
    224 
    225 	++depth;
    226 
    227 	snprintf(prompt, sizeof(prompt), "(%d) [%s] %s ",
    228 	    depth, addr[AR_HOST], addr[AR_PATH]);
    229 	done = 0;
    230 	while (!fatal && done <= 0 &&
    231 	    input(inbuf, sizeof(inbuf), separators[0], prompt, stdin) == 0) {
    232 		done = execute(cache, inbuf, depth, addr);
    233 	}
    234 
    235 	if (mycache)
    236 		wremove(cache);
    237 	if (feof(stdin))
    238 		done = depth;
    239 	else if (ferror(stdin) && !fatal) {
    240 		warn(errno, "stdin");
    241 		return fatal = -1;
    242 	}
    243 
    244 	--depth;
    245 	return done - 1;
    246 }
    247 
    248 void
    249 cleanup(void)
    250 {
    251 	DIR *dir;
    252 	char path[MY_PATH_MAX];
    253 	struct dirent *ent;
    254 
    255 	dir = opendir(tmpdir);
    256 	if (dir == NULL)
    257 		return;	/* ENOTDIR */
    258 	while ((ent = readdir(dir)) != NULL) {
    259 		if (ent->d_type == DT_REG) {
    260 			snprintf(path, sizeof(path), "%s/%s", tmpdir, ent->d_name);
    261 			wremove(path);
    262 		}
    263 	}
    264 	wremove(tmpdir);
    265 }
    266 
    267 void
    268 sighandler(int sig)
    269 {
    270 	cleanup();
    271 	exit(EXIT_FAILURE);
    272 }
    273 
    274 int
    275 main(int argc, char *argv[])
    276 {
    277 	int status;
    278 
    279 	if (argc > 1 && *argv[1] == '-') {
    280 		fprintf(stderr, "usage: %s [path [host [port]]]\n", getprogname());
    281 		return 1;
    282 	}
    283 
    284 #ifdef __OpenBSD__
    285 	if (pledge("stdio rpath wpath cpath inet dns proc exec", NULL) == -1)
    286 		die(errno, "pledge");
    287 #endif /* __OpenBSD__ */
    288 
    289 	signal(SIGCHLD, SIG_IGN);
    290 	signal(SIGINT, sighandler);
    291 
    292 	if (mkdtemp(tmpdir) == NULL)
    293 		die(errno, "mkdtemp");
    294 	status = sgoto(argc, argv, 0, default_address);
    295 	if (wremove(tmpdir) == -1)
    296 		return 1;
    297 
    298 	return status;
    299 }