sacc

sacc (saccomys): simple gopher client.
Log | Files | Refs | LICENSE

sacc.c (15223B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <errno.h>
      3 #include <fcntl.h>
      4 #include <limits.h>
      5 #include <locale.h>
      6 #include <netdb.h>
      7 #include <netinet/in.h>
      8 #include <signal.h>
      9 #include <stdarg.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <unistd.h>
     14 #include <wchar.h>
     15 #include <sys/socket.h>
     16 #include <sys/stat.h>
     17 #include <sys/types.h>
     18 #include <sys/wait.h>
     19 
     20 #include "common.h"
     21 
     22 #include "config.h"
     23 
     24 static char *mainurl;
     25 static Item *mainentry;
     26 static int devnullfd;
     27 static int parent = 1;
     28 static int interactive;
     29 
     30 void
     31 die(const char *fmt, ...)
     32 {
     33 	va_list arg;
     34 
     35 	va_start(arg, fmt);
     36 	vfprintf(stderr, fmt, arg);
     37 	va_end(arg);
     38 	fputc('\n', stderr);
     39 
     40 	exit(1);
     41 }
     42 
     43 #ifdef NEED_ASPRINTF
     44 int
     45 asprintf(char **s, const char *fmt, ...)
     46 {
     47 	va_list ap;
     48 	int n;
     49 
     50 	va_start(ap, fmt);
     51 	n = vsnprintf(NULL, 0, fmt, ap);
     52 	va_end(ap);
     53 
     54 	if (n == INT_MAX || !(*s = malloc(++n)))
     55 		return -1;
     56 
     57 	va_start(ap, fmt);
     58 	vsnprintf(*s, n, fmt, ap);
     59 	va_end(ap);
     60 
     61 	return n;
     62 }
     63 #endif /* NEED_ASPRINTF */
     64 
     65 /* print `len' columns of characters. */
     66 size_t
     67 mbsprint(const char *s, size_t len)
     68 {
     69 	wchar_t wc;
     70 	size_t col = 0, i, slen;
     71 	int rl, w;
     72 
     73 	if (!len)
     74 		return col;
     75 
     76 	slen = strlen(s);
     77 	for (i = 0; i < slen; i += rl) {
     78 		if ((rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4)) <= 0)
     79 			break;
     80 		if ((w = wcwidth(wc)) == -1)
     81 			continue;
     82 		if (col + w > len || (col + w == len && s[i + rl])) {
     83 			fputs("\xe2\x80\xa6", stdout);
     84 			col++;
     85 			break;
     86 		}
     87 		fwrite(s + i, 1, rl, stdout);
     88 		col += w;
     89 	}
     90 	return col;
     91 }
     92 
     93 static void *
     94 xreallocarray(void *m, const size_t n, const size_t s)
     95 {
     96 	void *nm;
     97 
     98 	if (n == 0 || s == 0) {
     99 		free(m);
    100 		return NULL;
    101 	}
    102 	if (s && n > (size_t)-1/s)
    103 		die("realloc: overflow");
    104 	if (!(nm = realloc(m, n * s)))
    105 		die("realloc: %s", strerror(errno));
    106 
    107 	return nm;
    108 }
    109 
    110 static void *
    111 xmalloc(const size_t n)
    112 {
    113 	void *m = malloc(n);
    114 
    115 	if (!m)
    116 		die("malloc: %s", strerror(errno));
    117 
    118 	return m;
    119 }
    120 
    121 static void *
    122 xcalloc(size_t n)
    123 {
    124 	char *m = calloc(1, n);
    125 
    126 	if (!m)
    127 		die("calloc: %s", strerror(errno));
    128 
    129 	return m;
    130 }
    131 
    132 static char *
    133 xstrdup(const char *str)
    134 {
    135 	char *s;
    136 
    137 	if (!(s = strdup(str)))
    138 		die("strdup: %s", strerror(errno));
    139 
    140 	return s;
    141 }
    142 
    143 static void
    144 usage(void)
    145 {
    146 	die("usage: sacc URL");
    147 }
    148 
    149 static void
    150 clearitem(Item *item)
    151 {
    152 	Dir *dir;
    153 	Item *items;
    154 	char *tag;
    155 	size_t i;
    156 
    157 	if (!item)
    158 		return;
    159 
    160 	if (dir = item->dat) {
    161 		items = dir->items;
    162 		for (i = 0; i < dir->nitems; ++i)
    163 			clearitem(&items[i]);
    164 		free(items);
    165 		clear(&item->dat);
    166 	}
    167 
    168 	if (parent && (tag = item->tag) &&
    169 	    !strncmp(tag, tmpdir, strlen(tmpdir)))
    170 		unlink(tag);
    171 
    172 	clear(&item->tag);
    173 	clear(&item->raw);
    174 }
    175 
    176 const char *
    177 typedisplay(char t)
    178 {
    179 	switch (t) {
    180 	case '0':
    181 		return "Text+";
    182 	case '1':
    183 		return "Dir +";
    184 	case '2':
    185 		return "CSO |";
    186 	case '3':
    187 		return "Err |";
    188 	case '4':
    189 		return "Macf+";
    190 	case '5':
    191 		return "DOSf+";
    192 	case '6':
    193 		return "UUEf+";
    194 	case '7':
    195 		return "Find+";
    196 	case '8':
    197 		return "Tlnt|";
    198 	case '9':
    199 		return "Binf+";
    200 	case '+':
    201 		return "Mirr+";
    202 	case 'T':
    203 		return "IBMt|";
    204 	case 'g':
    205 		return "GIF +";
    206 	case 'I':
    207 		return "Img +";
    208 	case 'h':
    209 		return "HTML+";
    210 	case 'i':
    211 		return "    |";
    212 	case 's':
    213 		return "Snd |";
    214 	default:
    215 		return "!   |";
    216 	}
    217 }
    218 
    219 static void
    220 printdir(Item *item)
    221 {
    222 	Dir *dir;
    223 	Item *items;
    224 	size_t i, nitems;
    225 
    226 	if (!item || !(dir = item->dat))
    227 		return;
    228 
    229 	items = dir->items;
    230 	nitems = dir->nitems;
    231 
    232 	for (i = 0; i < nitems; ++i) {
    233 		printf("%s%s\n",
    234 		       typedisplay(items[i].type), items[i].username);
    235 	}
    236 }
    237 
    238 static void
    239 displaytextitem(Item *item)
    240 {
    241 	FILE *pagerin;
    242 	int pid, wpid;
    243 
    244 	uicleanup();
    245 	switch (pid = fork()) {
    246 	case -1:
    247 		uistatus("Couldn't fork.");
    248 		return;
    249 	case 0:
    250 		parent = 0;
    251 		if (!(pagerin = popen("$PAGER", "we")))
    252 			_exit(1);
    253 		fputs(item->raw, pagerin);
    254 		exit(pclose(pagerin));
    255 	default:
    256 		while ((wpid = wait(NULL)) >= 0 && wpid != pid)
    257 			;
    258 	}
    259 	uisetup();
    260 }
    261 
    262 static char *
    263 pickfield(char **raw, char sep)
    264 {
    265 	char *c, *f = *raw;
    266 
    267 	for (c = *raw; *c && *c != sep; ++c)
    268 		;
    269 
    270 	*c = '\0';
    271 	*raw = c+1;
    272 
    273 	return f;
    274 }
    275 
    276 static char *
    277 invaliditem(char *raw)
    278 {
    279 	char c;
    280 	int tabs;
    281 
    282 	for (tabs = 0; (c = *raw) && c != '\n'; ++raw) {
    283 		if (c == '\t')
    284 			++tabs;
    285 	}
    286 	if (c)
    287 		*raw++ = '\0';
    288 
    289 	return (tabs == 3) ? NULL : raw;
    290 }
    291 
    292 static void
    293 molditem(Item *item, char **raw)
    294 {
    295 	char *next;
    296 
    297 	if (!*raw)
    298 		return;
    299 
    300 	if ((next = invaliditem(*raw))) {
    301 		item->username = *raw;
    302 		*raw = next;
    303 		return;
    304 	}
    305 
    306 	item->type = *raw[0]++;
    307 	item->username = pickfield(raw, '\t');
    308 	item->selector = pickfield(raw, '\t');
    309 	item->host = pickfield(raw, '\t');
    310 	item->port = pickfield(raw, '\r');
    311 	if (!*raw[0])
    312 		++*raw;
    313 }
    314 
    315 static Dir *
    316 molddiritem(char *raw)
    317 {
    318 	Item *item, *items = NULL;
    319 	char *s, *nl, *p;
    320 	Dir *dir;
    321 	size_t i, n, nitems;
    322 
    323 	for (s = nl = raw, nitems = 0; p = strchr(nl, '\n'); ++nitems) {
    324 		s = nl;
    325 		nl = p+1;
    326 	}
    327 	if (!strcmp(s, ".\r\n") || !strcmp(s, ".\n"))
    328 		--nitems;
    329 	if (!nitems) {
    330 		uistatus("Couldn't parse dir item");
    331 		return NULL;
    332 	}
    333 
    334 	dir = xmalloc(sizeof(Dir));
    335 	items = xreallocarray(items, nitems, sizeof(Item));
    336 	memset(items, 0, nitems * sizeof(Item));
    337 
    338 	for (i = 0; i < nitems; ++i) {
    339 		item = &items[i];
    340 		molditem(item, &raw);
    341 		if (item->type == '+') {
    342 			for (n = i - 1; n < (size_t)-1; --n) {
    343 				if (items[n].type != '+') {
    344 					item->redtype = items[n].type;
    345 					break;
    346 				}
    347 			}
    348 		}
    349 	}
    350 
    351 	dir->items = items;
    352 	dir->nitems = nitems;
    353 	dir->printoff = dir->curline = 0;
    354 
    355 	return dir;
    356 }
    357 
    358 static char *
    359 getrawitem(int sock)
    360 {
    361 	char *raw, *buf;
    362 	size_t bn, bs;
    363 	ssize_t n;
    364 
    365 	raw = buf = NULL;
    366 	bn = bs = n = 0;
    367 
    368 	do {
    369 		bs -= n;
    370 		buf += n;
    371 		if (bs < 1) {
    372 			raw = xreallocarray(raw, ++bn, BUFSIZ);
    373 			buf = raw + (bn-1) * BUFSIZ;
    374 			bs = BUFSIZ;
    375 		}
    376 	} while ((n = read(sock, buf, bs)) > 0);
    377 
    378 	*buf = '\0';
    379 
    380 	if (n < 0) {
    381 		uistatus("Can't read socket: %s", strerror(errno));
    382 		clear(&raw);
    383 	}
    384 
    385 	return raw;
    386 }
    387 
    388 static int
    389 sendselector(int sock, const char *selector)
    390 {
    391 	char *msg, *p;
    392 	size_t ln;
    393 	ssize_t n;
    394 
    395 	ln = strlen(selector) + 3;
    396 	msg = p = xmalloc(ln);
    397 	snprintf(msg, ln--, "%s\r\n", selector);
    398 
    399 	while ((n = write(sock, p, ln)) > 0) {
    400 		ln -= n;
    401 		p += n;
    402 	}
    403 
    404 	free(msg);
    405 	if (n == -1)
    406 		uistatus("Can't send message: %s", strerror(errno));
    407 
    408 	return n;
    409 }
    410 
    411 static int
    412 connectto(const char *host, const char *port)
    413 {
    414 	static const struct addrinfo hints = {
    415 	    .ai_family = AF_UNSPEC,
    416 	    .ai_socktype = SOCK_STREAM,
    417 	    .ai_protocol = IPPROTO_TCP,
    418 	};
    419 	struct addrinfo *addrs, *addr;
    420 	int sock, r;
    421 
    422 	if (r = getaddrinfo(host, port, &hints, &addrs)) {
    423 		uistatus("Can't resolve hostname \"%s\": %s",
    424 		         host, gai_strerror(r));
    425 		return -1;
    426 	}
    427 
    428 	for (addr = addrs; addr; addr = addr->ai_next) {
    429 		if ((sock = socket(addr->ai_family, addr->ai_socktype,
    430 		                   addr->ai_protocol)) < 0)
    431 			continue;
    432 		if ((r = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0) {
    433 			close(sock);
    434 			continue;
    435 		}
    436 		break;
    437 	}
    438 	if (sock < 0) {
    439 		uistatus("Can't open socket: %s", strerror(errno));
    440 		return -1;
    441 	}
    442 	if (r < 0) {
    443 		uistatus("Can't connect to: %s:%s: %s",
    444 		         host, port, strerror(errno));
    445 		return -1;
    446 	}
    447 
    448 	freeaddrinfo(addrs);
    449 
    450 	return sock;
    451 }
    452 
    453 static int
    454 download(Item *item, int dest)
    455 {
    456 	char buf[BUFSIZ];
    457 	ssize_t r, w;
    458 	int src;
    459 
    460 	if (!item->tag) {
    461 		if ((src = connectto(item->host, item->port)) < 0 ||
    462 		    sendselector(src, item->selector) < 0)
    463 			return 0;
    464 	} else if ((src = open(item->tag, O_RDONLY)) < 0) {
    465 		printf("Can't open source file %s: %s",
    466 		       item->tag, strerror(errno));
    467 		errno = 0;
    468 		return 0;
    469 	}
    470 
    471 	w = 0;
    472 	while ((r = read(src, buf, BUFSIZ)) > 0) {
    473 		while ((w = write(dest, buf, r)) > 0)
    474 			r -= w;
    475 	}
    476 
    477 	if (r < 0 || w < 0) {
    478 		printf("Error downloading file %s: %s",
    479 		       item->selector, strerror(errno));
    480 		errno = 0;
    481 	}
    482 
    483 	close(src);
    484 
    485 	return (r == 0 && w == 0);
    486 }
    487 
    488 static void
    489 downloaditem(Item *item)
    490 {
    491 	char *file, *path, *tag;
    492 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
    493 	int dest;
    494 
    495 	if (file = strrchr(item->selector, '/'))
    496 		++file;
    497 	else
    498 		file = item->selector;
    499 
    500 	if (!(path = uiprompt("Download to [%s] (^D cancel): ", file)))
    501 		return;
    502 
    503 	if (!path[0])
    504 		path = xstrdup(file);
    505 
    506 	if (tag = item->tag) {
    507 		if (access(tag, R_OK) < 0) {
    508 			clear(&item->tag);
    509 		} else if (!strcmp(tag, path)) {
    510 			goto cleanup;
    511 		}
    512 	}
    513 
    514 	if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
    515 		uistatus("Can't open destination file %s: %s",
    516 		         path, strerror(errno));
    517 		errno = 0;
    518 		goto cleanup;
    519 	}
    520 
    521 	if (!download(item, dest))
    522 		goto cleanup;
    523 
    524 	if (!item->tag)
    525 		item->tag = path;
    526 	return;
    527 cleanup:
    528 	free(path);
    529 	return;
    530 }
    531 
    532 static int
    533 fetchitem(Item *item)
    534 {
    535 	int sock;
    536 
    537 	if ((sock = connectto(item->host, item->port)) < 0 ||
    538 	    sendselector(sock, item->selector) < 0)
    539 		return 0;
    540 	item->raw = getrawitem(sock);
    541 	close(sock);
    542 
    543 	if (item->raw && !*item->raw) {
    544 		uistatus("Empty response from server");
    545 		clear(&item->raw);
    546 	}
    547 
    548 	return (item->raw != NULL);
    549 }
    550 
    551 static void
    552 plumb(char *url)
    553 {
    554 	switch (fork()) {
    555 	case -1:
    556 		uistatus("Couldn't fork.");
    557 		return;
    558 	case 0:
    559 		parent = 0;
    560 		dup2(devnullfd, 1);
    561 		dup2(devnullfd, 2);
    562 		if (execlp(plumber, plumber, url, NULL) < 0)
    563 			_exit(1);
    564 	}
    565 
    566 	uistatus("Plumbed \"%s\"", url);
    567 }
    568 
    569 static void
    570 plumbitem(Item *item)
    571 {
    572 	char *file, *path, *tag;
    573 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
    574 	int n, dest, plumbitem;
    575 
    576 	if (file = strrchr(item->selector, '/'))
    577 		++file;
    578 	else
    579 		file = item->selector;
    580 
    581 	path = uiprompt("Download %s to (^D cancel, <empty> plumb): ",
    582 	                file);
    583 	if (!path)
    584 		return;
    585 
    586 	if ((tag = item->tag) && access(tag, R_OK) < 0) {
    587 		clear(&item->tag);
    588 		tag = NULL;
    589 	}
    590 
    591 	plumbitem = path[0] ? 0 : 1;
    592 
    593 	if (!path[0]) {
    594 		clear(&path);
    595 		if (!tag) {
    596 			if (asprintf(&path, "%s/%s", tmpdir, file) < 0)
    597 				die("Can't generate tmpdir path: %s/%s: %s",
    598 				    tmpdir, file, strerror(errno));
    599 		}
    600 	}
    601 
    602 	if (path && (!tag || strcmp(tag, path))) {
    603 		if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
    604 			uistatus("Can't open destination file %s: %s",
    605 			         path, strerror(errno));
    606 			errno = 0;
    607 			goto cleanup;
    608 		}
    609 		if (!download(item, dest) || tag)
    610 			goto cleanup;
    611 	}
    612 
    613 	if (!tag)
    614 		item->tag = path;
    615 
    616 	if (plumbitem)
    617 		plumb(item->tag);
    618 
    619 	return;
    620 cleanup:
    621 	free(path);
    622 	return;
    623 }
    624 
    625 static int
    626 dig(Item *entry, Item *item)
    627 {
    628 	char *plumburi = NULL;
    629 
    630 	if (item->raw) /* already in cache */
    631 		return item->type;
    632 	if (!item->entry)
    633 		item->entry = entry ? entry : item;
    634 
    635 	switch (item->redtype ? item->redtype : item->type) {
    636 	case 'h': /* fallthrough */
    637 		if (!strncmp(item->selector, "URL:", 4)) {
    638 			plumb(item->selector+4);
    639 			return 0;
    640 		}
    641 	case '0':
    642 		if (!fetchitem(item))
    643 			return 0;
    644 		break;
    645 	case '1':
    646 	case '7':
    647 		if (!fetchitem(item) || !(item->dat = molddiritem(item->raw)))
    648 			return 0;
    649 		break;
    650 	case '4':
    651 	case '5':
    652 	case '6':
    653 	case '9':
    654 		downloaditem(item);
    655 		return 0;
    656 	case '8':
    657 		if (asprintf(&plumburi, "telnet://%s@%s:%s", item->selector,
    658 		             item->host, item->port) < 0)
    659 			return 0;
    660 		plumb(plumburi);
    661 		free(plumburi);
    662 		return 0;
    663 	case 'g':
    664 	case 'I':
    665 		plumbitem(item);
    666 		return 0;
    667 	case 'T':
    668 		if (asprintf(&plumburi, "tn3270://%s@%s:%s", item->selector,
    669 		             item->host, item->port) < 0)
    670 			return 0;
    671 		plumb(plumburi);
    672 		free(plumburi);
    673 		return 0;
    674 	default:
    675 		uistatus("Type %c (%s) not supported",
    676 		        item->type, typedisplay(item->type));
    677 		return 0;
    678 	}
    679 
    680 	return item->type;
    681 }
    682 
    683 static char *
    684 searchselector(Item *item)
    685 {
    686 	char *pexp, *exp, *tag, *selector = item->selector;
    687 	size_t n = strlen(selector);
    688 
    689 	if ((tag = item->tag) && !strncmp(tag, selector, n))
    690 		pexp = tag + n+1;
    691 	else
    692 		pexp = "";
    693 
    694 	if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp)))
    695 		return NULL;
    696 
    697 	if (exp[0] && strcmp(exp, pexp)) {
    698 		n += strlen(exp) + 2;
    699 		tag = xmalloc(n);
    700 		snprintf(tag, n, "%s\t%s", selector, exp);
    701 	}
    702 
    703 	free(exp);
    704 	return tag;
    705 }
    706 
    707 static int
    708 searchitem(Item *entry, Item *item)
    709 {
    710 	char *sel, *selector;
    711 
    712 	if (!(sel = searchselector(item)))
    713 		return 0;
    714 
    715 	if (sel != item->tag) {
    716 		clearitem(item);
    717 		selector = item->selector;
    718 		item->selector = item->tag = sel;
    719 		dig(entry, item);
    720 		item->selector = selector;
    721 	}
    722 	return (item->dat != NULL);
    723 }
    724 
    725 static void
    726 printout(Item *hole)
    727 {
    728 	if (!hole)
    729 		return;
    730 
    731 	switch (hole->redtype ? hole->redtype : hole->type) {
    732 	case '0':
    733 		if (dig(hole, hole))
    734 			fputs(hole->raw, stdout);
    735 		return;
    736 	case '1':
    737 		if (dig(hole, hole))
    738 			printdir(hole);
    739 	default:
    740 		return;
    741 	}
    742 }
    743 
    744 static void
    745 delve(Item *hole)
    746 {
    747 	Item *entry = NULL;
    748 
    749 	while (hole) {
    750 		switch (hole->redtype ? hole->redtype : hole->type) {
    751 		case 'h':
    752 		case '0':
    753 			if (dig(entry, hole))
    754 				displaytextitem(hole);
    755 			break;
    756 		case '1':
    757 		case '+':
    758 			if (dig(entry, hole) && hole->dat)
    759 				entry = hole;
    760 			break;
    761 		case '7':
    762 			if (searchitem(entry, hole))
    763 				entry = hole;
    764 			break;
    765 		case '4':
    766 		case '5':
    767 		case '6': /* TODO decode? */
    768 		case '8':
    769 		case '9':
    770 		case 'g':
    771 		case 'I':
    772 		case 'T':
    773 			dig(entry, hole);
    774 			break;
    775 		case 0:
    776 			uistatus("Couldn't get %s:%s/%c%s", hole->host,
    777 			         hole->port, hole->type, hole->selector);
    778 		}
    779 
    780 		if (!entry)
    781 			return;
    782 
    783 		do {
    784 			uidisplay(entry);
    785 			hole = uiselectitem(entry);
    786 		} while (hole == entry);
    787 	}
    788 }
    789 
    790 static Item *
    791 moldentry(char *url)
    792 {
    793 	Item *entry;
    794 	char *p, *host = url, *port = "70", *gopherpath = "1";
    795 	int parsed, ipv6;
    796 
    797 	if (p = strstr(url, "://")) {
    798 		if (strncmp(url, "gopher", p - url))
    799 			die("Protocol not supported: %.*s", p - url, url);
    800 		host = p + 3;
    801 	}
    802 
    803 	if (*host == '[') {
    804 		ipv6 = 1;
    805 		++host;
    806 	} else {
    807 		ipv6 = 0;
    808 	}
    809 
    810 	for (parsed = 0, p = host; !parsed && *p; ++p) {
    811 		switch (*p) {
    812 		case ']':
    813 			if (ipv6) {
    814 				*p = '\0';
    815 				ipv6 = 0;
    816 			}
    817 			continue;
    818 		case ':':
    819 			if (!ipv6) {
    820 				*p = '\0';
    821 				port = p+1;
    822 			}
    823 			continue;
    824 		case '/':
    825 			*p = '\0';
    826 			parsed = 1;
    827 			continue;
    828 		}
    829 	}
    830 
    831 	if (*host == '\0' || *port == '\0' || ipv6)
    832 		die("Can't parse url");
    833 
    834 	if (*p != '\0')
    835 		gopherpath = p;
    836 
    837 	entry = xcalloc(sizeof(Item));
    838 	entry->type = gopherpath[0];
    839 	entry->username = entry->selector = ++gopherpath;
    840 	entry->host = host;
    841 	entry->port = port;
    842 	entry->entry = entry;
    843 
    844 	return entry;
    845 }
    846 
    847 static void
    848 cleanup(void)
    849 {
    850 	clearitem(mainentry);
    851 	if (parent)
    852 		rmdir(tmpdir);
    853 	free(mainentry);
    854 	free(mainurl);
    855 	if (interactive)
    856 		uicleanup();
    857 }
    858 
    859 static void
    860 setup(void)
    861 {
    862 	struct sigaction sa;
    863 	int fd;
    864 
    865 	setlocale(LC_CTYPE, "");
    866 	setenv("PAGER", "more", 0);
    867 	atexit(cleanup);
    868 	/* reopen stdin in case we're reading from a pipe */
    869 	if ((fd = open("/dev/tty", O_RDONLY)) < 0)
    870 		die("open: /dev/tty: %s", strerror(errno));
    871 	if (dup2(fd, 0) < 0)
    872 		die("dup2: /dev/tty, stdin: %s", strerror(errno));
    873 	close(fd);
    874 	if ((devnullfd = open("/dev/null", O_WRONLY)) < 0)
    875 		die("open: /dev/null: %s", strerror(errno));
    876 
    877 	sigemptyset(&sa.sa_mask);
    878 	sa.sa_flags = SA_RESTART;
    879 	sa.sa_handler = exit;
    880 	sigaction(SIGINT, &sa, NULL);
    881 
    882 	if (mkdir(tmpdir, S_IRWXU) < 0 && errno != EEXIST)
    883 		die("mkdir: %s: %s", tmpdir, strerror(errno));
    884 	if(interactive = isatty(1)) {
    885 		uisetup();
    886 		sa.sa_handler = uisigwinch;
    887 		sigaction(SIGWINCH, &sa, NULL);
    888 	}
    889 }
    890 
    891 int
    892 main(int argc, char *argv[])
    893 {
    894 	if (argc != 2)
    895 		usage();
    896 
    897 	setup();
    898 
    899 	mainurl = xstrdup(argv[1]);
    900 
    901 	mainentry = moldentry(mainurl);
    902 	if (interactive)
    903 		delve(mainentry);
    904 	else
    905 		printout(mainentry);
    906 
    907 	exit(0);
    908 }