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