Annotation of pgp/contrib/langtool/language.c, revision 1.1.1.2

1.1.1.2 ! root        1: /*
        !             2:  *     language.c - Foreign language translation for PGP
        !             3:  *     Finds foreign language "subtitles" for English phrases 
        !             4:  *     in external foriegn language text file.
        !             5:  */
        !             6: 
        !             7: #include <stdio.h>
        !             8: #include <stdlib.h>
        !             9: #include <string.h>
        !            10: #include <ctype.h>
        !            11: #include "usuals.h"
        !            12: #ifndef LANGTOOL
        !            13: #include "fileio.h"
        !            14: #include "language.h"
        !            15: #include "pgp.h"
        !            16: #else
        !            17: #define MAX_PATH       255
        !            18: boolean verbose;
        !            19: long fsize();
        !            20: #endif
        !            21: 
        !            22: char langfile[80] = "language.txt";
        !            23: #define LANG_INDEXFILE "language.idx"
        !            24: 
        !            25: #define        STRBUFSIZE              2048
        !            26: 
        !            27: char language[16] = "en";      /* The language code, defaults to English */
        !            28: static char    *strbuf;
        !            29: static char    lang[16];       /* readstr sets this to the language id of the msg it last read */
        !            30: static int     subtitles_available = 0;
        !            31: static int line = 0;
        !            32: static int errcount = 0;
        !            33: /*     subtitles_available is used to determine if we know whether the special
        !            34:        subtitles_file exists.  subtitles_available has the following values:
        !            35:        0  = first time thru, we don't yet know if subtitles_file exists.
        !            36:        1  = we have already determined that subtitles_file exists.
        !            37:        -1 = we have already determined that subtitles_file does not exist.
        !            38: */
        !            39: 
        !            40: static void error(char *);
        !            41: 
        !            42: #define        NEWLINE         0
        !            43: #define        COMMENT         1
        !            44: #define        INSTRING        2
        !            45: #define        ESCAPE          3
        !            46: #define        IDENT           4
        !            47: #define        DONE            5
        !            48: #define        ERROR           6
        !            49: #define        ERR1            7
        !            50: 
        !            51: /* Look for and return a quoted string from the file.
        !            52:  * If nlabort is true, return failure if we find a blank line
        !            53:  * before we find the opening quote.
        !            54:  */
        !            55: static char    *
        !            56: readstr (FILE *f, char *buf, int nlabort)
        !            57: {
        !            58:        int     c, d;
        !            59:        char *p = buf;
        !            60:        int state = NEWLINE;
        !            61:        int i = 0;
        !            62:        
        !            63:        while ((c = getc(f)) != EOF) {
        !            64:                if (c == '\r')
        !            65:                        continue;
        !            66:                /* line numbers are only incremented when creating index file */
        !            67:                if (line && c == '\n')
        !            68:                        ++line;
        !            69:                switch (state) {
        !            70:                  case NEWLINE:
        !            71:                        switch(c) {
        !            72:                          case '#': state = COMMENT; break;
        !            73:                          case '"': state = INSTRING; break;
        !            74:                          case '\n':
        !            75:                                if (nlabort) {
        !            76:                                        *buf = '\0';
        !            77:                                        return(buf);
        !            78:                                }
        !            79:                          default:
        !            80:                                if (i == 0 && isalnum(c)) {
        !            81:                                        state = IDENT;
        !            82:                                        lang[i++] = c;
        !            83:                                        break;
        !            84:                                }
        !            85:                                if (!isspace(c)) {
        !            86:                                        error("syntax error\n");
        !            87:                                        state = ERROR;
        !            88:                                }
        !            89:                        }
        !            90:                        break;
        !            91:                  case COMMENT:
        !            92:                        if (c == '\n')
        !            93:                                state = NEWLINE;
        !            94:                        break;
        !            95:                  case INSTRING:
        !            96:                        switch(c) {
        !            97:                          case '\\': state = ESCAPE; break;
        !            98:                          case '"': state = DONE; break;
        !            99:                          default: *p++ = c;
        !           100:                        }
        !           101:                        break;
        !           102:                case ESCAPE:
        !           103:                        switch (c) {
        !           104:                          case 'n':     *p++ = '\n';    break;
        !           105:                          case 'r':     *p++ = '\r';    break;
        !           106:                          case 't':     *p++ = '\t';    break;
        !           107:                          case 'e':     *p++ = '\033';  break;
        !           108:                          case 'a':     *p++ = '\007';  break;
        !           109:                          case '#':
        !           110:                          case '"':
        !           111:                          case '\\':    *p++ = c; break;
        !           112:                          case '\n':    break;
        !           113:                          case '0':
        !           114:                          case '1':
        !           115:                          case '2':
        !           116:                          case '3':
        !           117:                          case '4':
        !           118:                          case '5':
        !           119:                          case '6':
        !           120:                          case '7':
        !           121:                                /* ANSI C rules: up to 3 octal digits */
        !           122:                                d = c - '0';
        !           123:                                if ((c = getc(f)) >= '0' && c <= '7') {
        !           124:                                        d = (d<<3) + (c-'0');
        !           125:                                        if ((c = getc(f)) >= '0' && c <= '7')
        !           126:                                                d = (d<<3) + (c-'0');
        !           127:                                        else
        !           128:                                                ungetc(c, f);
        !           129:                                } else {
        !           130:                                        ungetc(c, f);
        !           131:                                }
        !           132:                                *p++ = d;
        !           133:                                break;
        !           134:                          default:
        !           135:                                error("illegal escape sequence: ");
        !           136:                                fprintf(stderr, "'\\%c'\n", c);
        !           137:                                break;
        !           138:                        }
        !           139:                        state = INSTRING;
        !           140:                        break;
        !           141:                  case IDENT:           /* language identifier */
        !           142:                        if (c == ':') {
        !           143:                                state = NEWLINE;
        !           144:                                break;
        !           145:                        }
        !           146:                        if (c == '\n' && strncmp(lang, "No translation", 14) == 0)
        !           147:                        {
        !           148:                                i = 0;
        !           149:                                state = NEWLINE;
        !           150:                                break;
        !           151:                        }
        !           152:                        lang[i++] = c;
        !           153:                        if (i == 15 || !isalnum(c) && !isspace(c)) {
        !           154:                                lang[i] = '\0';
        !           155:                                error("bad language identifier\n");
        !           156:                                state = ERROR;
        !           157:                                i = 0;
        !           158:                        }
        !           159:                        break;
        !           160:                  case DONE:
        !           161:                        if (c == '\n') {
        !           162:                                lang[i] = '\0';
        !           163:                                *p = '\0';
        !           164:                                return(buf);
        !           165:                        }
        !           166:                        if (!isspace(c)) {
        !           167:                                error("extra characters after '\"'\n");
        !           168:                                state = ERROR;
        !           169:                        }
        !           170:                        break;
        !           171:                  case ERROR:
        !           172:                        if (c == '\n')
        !           173:                                state = ERR1;
        !           174:                        break;
        !           175:                  case ERR1:
        !           176:                        state = (c == '\n' ? NEWLINE : ERROR);
        !           177:                        break;
        !           178:                }
        !           179:        }
        !           180:        if (state != NEWLINE)
        !           181:                error("unexpected EOF\n");
        !           182:        return(NULL);
        !           183: }
        !           184: 
        !           185: 
        !           186: static struct indx_ent {
        !           187:        word32  crc;
        !           188:        long    offset;
        !           189: } *indx_tbl = NULL;
        !           190: 
        !           191: static int max_msgs = 0;
        !           192: static int nmsg = 0;
        !           193: 
        !           194: static FILE *langf;
        !           195: 
        !           196: static struct {
        !           197:        long lang_fsize;        /* size of language.txt */
        !           198:        char lang[16];          /* language identifier */
        !           199:        int nmsg;               /* number of messages */
        !           200: } indx_hdr;
        !           201: 
        !           202: 
        !           203: static int make_indexfile(char *);
        !           204: word32 crcupdate(byte, word32);
        !           205: void init_crc();
        !           206: 
        !           207: /*
        !           208:  * uses 24-bit CRC function from armor.c
        !           209:  */
        !           210: static word32
        !           211: message_crc(char *s)
        !           212: {
        !           213:        word32 crc = 0;
        !           214: 
        !           215:        while (*s)
        !           216:                crc = crcupdate(*s++, crc);
        !           217:        return(crc);
        !           218: }
        !           219: 
        !           220: /*
        !           221:  * lookup file offset in indx_tbl
        !           222:  */
        !           223: static long
        !           224: lookup_offset(word32 crc)
        !           225: {
        !           226:        int i;
        !           227:        
        !           228:        for (i = 0; i < nmsg; ++i)
        !           229:                if (indx_tbl[i].crc == crc)
        !           230:                        return(indx_tbl[i].offset);
        !           231:        return(-1);
        !           232: }
        !           233: 
        !           234: 
        !           235: 
        !           236: #ifndef LANGTOOL
        !           237: static void init_lang();
        !           238: 
        !           239: /*
        !           240:  * return foreign translation of s
        !           241:  */
        !           242: char *
        !           243: PSTR (char *s)
        !           244: {
        !           245:        long filepos;
        !           246: 
        !           247:        if (subtitles_available == 0)
        !           248:                init_lang();
        !           249:        if (subtitles_available < 0)
        !           250:                return(s);
        !           251: 
        !           252:        filepos = lookup_offset(message_crc(s));
        !           253:        if (filepos == -1) {
        !           254:                return(s);
        !           255:        } else {
        !           256:                fseek(langf, filepos, SEEK_SET);
        !           257:                readstr(langf, strbuf, 1);
        !           258:        }
        !           259: 
        !           260:        if (strbuf[0] == '\0')
        !           261:                return(s);
        !           262: 
        !           263:        for (s = strbuf; *s; ++s)
        !           264:                *s = EXT_C(*s);
        !           265:        return(strbuf);
        !           266: }
        !           267: 
        !           268: /*
        !           269:  * initialize the index table: read it from language.idx or create
        !           270:  * a new one and write it to the index file. A new index file is
        !           271:  * created if the language set in config.pgp doesn't match the one
        !           272:  * in language.idx or if the size of language.txt has changed.
        !           273:  */
        !           274: static void
        !           275: init_lang()
        !           276: {
        !           277:        char indexfile[MAX_PATH];
        !           278:        char subtitles_file[MAX_PATH];
        !           279:        FILE *indexf;
        !           280: 
        !           281:        if (strcmp(language, "en") == 0) {
        !           282:                subtitles_available = -1;
        !           283:                return;         /* use default messages */
        !           284:        }
        !           285: 
        !           286:        buildfilename (subtitles_file, langfile);
        !           287:        if ((langf = fopen(subtitles_file, "rb")) == NULL) {
        !           288:                subtitles_available = -1;
        !           289:                return;
        !           290:        }
        !           291:        init_crc();
        !           292:        if ((strbuf = (char *) malloc(STRBUFSIZE)) == NULL) {
        !           293:                fprintf(stderr, "Not enough memory for foreign subtitles\n");
        !           294:                fclose(langf);
        !           295:                subtitles_available = -1;
        !           296:                return;
        !           297:        }
        !           298:        buildfilename(indexfile, LANG_INDEXFILE);
        !           299:        if ((indexf = fopen(indexfile, "rb")) != NULL) {
        !           300:                if (fread(&indx_hdr, sizeof(indx_hdr), 1, indexf) == 1 &&
        !           301:                        indx_hdr.lang_fsize == fsize(langf) &&
        !           302:                        strcmp(indx_hdr.lang, language) == 0)
        !           303:                {
        !           304:                        nmsg = indx_hdr.nmsg;
        !           305:                        indx_tbl = (struct indx_ent *) malloc(nmsg * sizeof(struct indx_ent));
        !           306:                        if (indx_tbl == NULL) {
        !           307:                                fprintf(stderr, "Not enough memory for foreign subtitles\n");
        !           308:                                fclose(indexf);
        !           309:                                fclose(langf);
        !           310:                                subtitles_available = -1;
        !           311:                                return;
        !           312:                        }
        !           313:                        if (fread(indx_tbl, sizeof(struct indx_ent), nmsg, indexf) != nmsg)
        !           314:                        {
        !           315:                                free(indx_tbl); /* create a new one */
        !           316:                                indx_tbl = NULL;
        !           317:                        }
        !           318:                }
        !           319:                fclose(indexf);
        !           320:        }
        !           321:        if (indx_tbl == NULL && make_indexfile(indexfile) < 0) {
        !           322:                fclose(langf);
        !           323:                subtitles_available = -1;
        !           324:        } else {
        !           325:                subtitles_available = 1;
        !           326:        }
        !           327: }
        !           328: #endif /* !LANGTOOL */
        !           329: 
        !           330: 
        !           331: /*
        !           332:  * build the index table in memory, and if indexfile is not NULL,
        !           333:  * write it to this file
        !           334:  */
        !           335: static int
        !           336: make_indexfile(char *indexfile)
        !           337: {
        !           338:        FILE *indexf;
        !           339:        long filepos;
        !           340:        int total_msgs = 0;
        !           341:        char *res;
        !           342: 
        !           343:        rewind(langf);
        !           344:        indx_hdr.lang_fsize = fsize(langf);
        !           345:        strncpy(indx_hdr.lang, language, 15);
        !           346:        init_crc();
        !           347:        line = 1;
        !           348:        nmsg = 0;
        !           349:        while (readstr(langf, strbuf, 0)) {
        !           350:                if (nmsg == max_msgs) {
        !           351:                        if (max_msgs) {
        !           352:                                max_msgs *= 2;
        !           353:                                indx_tbl = (struct indx_ent *) realloc(indx_tbl, max_msgs *
        !           354:                                                        sizeof(struct indx_ent));
        !           355:                        } else {
        !           356:                                max_msgs = 400;
        !           357:                                indx_tbl = (struct indx_ent *) malloc(max_msgs *
        !           358:                                                        sizeof(struct indx_ent));
        !           359:                        }
        !           360:                        if (indx_tbl == NULL) {
        !           361:                                fprintf(stderr, "Not enough memory for foreign subtitles\n");
        !           362:                                return(-1);
        !           363:                        }
        !           364:                }
        !           365:                ++total_msgs;
        !           366:                indx_tbl[nmsg].crc = message_crc(strbuf);
        !           367:                if (lookup_offset(indx_tbl[nmsg].crc) != -1)
        !           368:                        error("message CRC not unique.\n");
        !           369:                do {
        !           370:                        filepos = ftell(langf);
        !           371:                        res = readstr (langf, strbuf, 1);               /* Abort if find newline first */
        !           372:                        if (*language == '\0')          /* use first language found */
        !           373:                                strcpy(language, lang);
        !           374:                } while (res && strbuf[0] != '\0' && strcmp(language, lang) != 0);
        !           375: 
        !           376:                if (res == NULL)
        !           377:                        break;
        !           378:                if (strbuf[0] == '\0')  /* No translation */
        !           379:                        continue;
        !           380: 
        !           381:                indx_tbl[nmsg].offset = filepos;
        !           382:                ++nmsg;
        !           383:                do
        !           384:                        res = readstr (langf, strbuf, 1);               /* Abort if find newline first */
        !           385:                while (res && strbuf[0] != '\0');
        !           386:        }
        !           387:        line = 0;
        !           388:        indx_hdr.nmsg = nmsg;
        !           389:        if (verbose)
        !           390:                fprintf(stderr, "%s: %d messages, %d translations for language \"%s\"\n",
        !           391:                                langfile, total_msgs, nmsg, language);
        !           392:        if (nmsg == 0) {
        !           393:                fprintf(stderr, "No translations available for language \"%s\"\n\n",
        !           394:                                language);
        !           395:                return(-1);
        !           396:        }
        !           397: 
        !           398:        if (indexfile) {
        !           399:                if ((indexf = fopen(indexfile, "wb")) == NULL)
        !           400:                        fprintf(stderr, "Cannot create %s\n", indexfile);
        !           401:                else {
        !           402:                        fwrite(&indx_hdr, 1, sizeof(indx_hdr), indexf);
        !           403:                        fwrite(indx_tbl, sizeof(struct indx_ent), nmsg, indexf);
        !           404:                        if (ferror(indexf) || fclose(indexf))
        !           405:                                fprintf(stderr, "error writing %s\n", indexfile);
        !           406:                }
        !           407:        }
        !           408:        return(0);
        !           409: }
        !           410: 
        !           411: static void
        !           412: error(char *s)
        !           413: {
        !           414:        ++errcount;
        !           415:        if (langfile[0])
        !           416:                fprintf(stderr, "%s:", langfile);
        !           417:        if (line)
        !           418:                fprintf(stderr, "%d:", line);
        !           419:        fprintf(stderr, " %s", s);
        !           420: }
        !           421: 
        !           422: #ifdef LANGTOOL
        !           423: /*
        !           424:  * language string tool for manipulating language files
        !           425:  * link with CRC routines from armor.c
        !           426:  */
        !           427: 
        !           428: #define CMD_EXTRACT    1
        !           429: #define CMD_CHECK      2
        !           430: #define CMD_MERGE      3
        !           431: 
        !           432: extern char *optarg;
        !           433: extern int optind;
        !           434: 
        !           435: main(int argc, char **argv)
        !           436: {
        !           437:        int opt, cmd = 0, rc = 0;
        !           438:        char *langIDs[16];
        !           439:        char *outfile = NULL;
        !           440: 
        !           441:        init_crc();
        !           442:        if ((strbuf = (char *) malloc(STRBUFSIZE)) == NULL) {
        !           443:                perror(argv[0]);
        !           444:                exit(1);
        !           445:        }
        !           446:        while ((opt = getopt(argc, argv, "cxmo:")) != EOF) {
        !           447:                switch (opt) {
        !           448:                  case 'c': cmd = CMD_CHECK; break;
        !           449:                  case 'x': cmd = CMD_EXTRACT; break;
        !           450:                  case 'm': cmd = CMD_MERGE; break;
        !           451:                  case 'o': outfile = optarg; break;
        !           452:                  default: usage();
        !           453:                }
        !           454:        }
        !           455:        argc -= optind; argv += optind;
        !           456:        switch (cmd) {
        !           457:                case CMD_EXTRACT:
        !           458:                        if (argc < 2)
        !           459:                                usage();
        !           460:                        rc = extract(argv[0], outfile, &argv[1]);
        !           461:                        break;
        !           462:                case CMD_MERGE:
        !           463:                        if (argc < 2)
        !           464:                                usage();
        !           465:                        rc = merge(argv[0], argv[1], outfile, argv[2]);
        !           466:                        break;
        !           467:                case CMD_CHECK:
        !           468:                        verbose = 1;
        !           469:                        if (argc == 0)
        !           470:                                checkfile("language.txt");
        !           471:                        else
        !           472:                                while (--argc >= 0)
        !           473:                                        checkfile(*argv++);
        !           474:                        break;
        !           475:                default: usage();
        !           476:        }
        !           477:        exit(rc);
        !           478: }
        !           479: 
        !           480: usage()
        !           481: {
        !           482:        fprintf(stderr, "usage: langtool -[x|c|m] [-o outputfile] ...\n\n\
        !           483: To extract one or more languages from a merged file:\n\
        !           484:        langtool -x [-o outputfile] file langID...\n\n\
        !           485: To check a language file for syntax errors:\n\
        !           486:        langtool -c file...\n\n\
        !           487: To merge language \"lang\" from lang_file with source_file:\n\
        !           488:        langtool -m [-o outputfile] source_file lang_file [lang]\n");
        !           489:        exit(1);
        !           490: }
        !           491: 
        !           492: merge(char *base_file, char *lang_file, char *outfile, char *langID)
        !           493: {
        !           494:        FILE *fp, *outf;
        !           495:        long fpos = 0, filepos;
        !           496:        int newmsgs = 0;
        !           497: 
        !           498:        if ((langf = fopen(lang_file, "r")) == NULL) {
        !           499:                perror(lang_file);
        !           500:                return -1;
        !           501:        }
        !           502:        strcpy(langfile, lang_file);
        !           503:        if (langID)
        !           504:                strcpy(language, langID);
        !           505:        else
        !           506:                language[0] = '\0';     /* use first language found */
        !           507: 
        !           508:        errcount = 0;
        !           509:        make_indexfile(NULL);
        !           510:        if (errcount)
        !           511:                return -1;
        !           512: 
        !           513:        langfile[0] = '\0';     /* don't print filename in error msgs */
        !           514: 
        !           515:        if ((fp = fopen(base_file, "r")) == NULL) {
        !           516:                perror(base_file);
        !           517:                return -1;
        !           518:        }
        !           519:        if (outfile == NULL)
        !           520:                outf = stdout;
        !           521:        else {
        !           522:                if ((outf = fopen(outfile, "w")) == NULL) {
        !           523:                        perror(outfile);
        !           524:                        return(-1);
        !           525:                }
        !           526:        }
        !           527: 
        !           528:        while (readstr(fp, strbuf, 0)) {
        !           529:                copypos(fp, outf, fpos);
        !           530:                fpos = ftell(fp);
        !           531: 
        !           532:                filepos = lookup_offset(message_crc(strbuf));
        !           533:                if (filepos == -1) {
        !           534:                        fprintf(outf, "No translation\n");
        !           535:                        ++newmsgs;
        !           536:                } else {
        !           537:                        fseek(langf, filepos, SEEK_SET);
        !           538:                        readstr(langf, strbuf, 1);
        !           539:                        copypos(langf, outf, filepos);
        !           540:                }
        !           541: 
        !           542:                while (readstr(fp, strbuf, 1))
        !           543:                        if (*strbuf == '\0')
        !           544:                                break;
        !           545:        }
        !           546:        copypos(fp, outf, fpos);
        !           547:        fflush(outf);
        !           548:        if (ferror(outf)) {
        !           549:                perror(outfile);
        !           550:                return -1;
        !           551:        }
        !           552:        if (newmsgs)
        !           553:                fprintf(stderr, "%d untranslated messages\n", newmsgs);
        !           554:        return errcount;
        !           555: }
        !           556: 
        !           557: extract(char *infile, char *outfile, char **langIDs)
        !           558: {
        !           559:        FILE *fp, *outf;
        !           560:        long fpos = 0;
        !           561:        char **langID;
        !           562: 
        !           563:        if ((fp = fopen(infile, "r")) == NULL) {
        !           564:                perror(infile);
        !           565:                return -1;
        !           566:        }
        !           567:        if (outfile == NULL) {
        !           568:                outf = stdout;
        !           569:        } else {
        !           570:                if ((outf = fopen(outfile, "w")) == NULL) {
        !           571:                        perror(outfile);
        !           572:                        fclose(fp);
        !           573:                        return(-1);
        !           574:                }
        !           575:        }
        !           576: 
        !           577:        while (readstr(fp, strbuf, 0)) {
        !           578:                copypos(fp, outf, fpos);
        !           579:                fpos = ftell(fp);
        !           580:                while (readstr(fp, strbuf, 1)) {
        !           581:                        if (*strbuf == '\0')
        !           582:                                break;
        !           583:                        for (langID = langIDs; *langID; ++langID) {
        !           584:                                if (strcmp(lang, *langID) == 0)
        !           585:                                        copypos(fp, outf, fpos);
        !           586:                        }
        !           587:                        fpos = ftell(fp);
        !           588:                }
        !           589:        }
        !           590:        copypos(fp, outf, fpos);
        !           591:        fflush(outf);
        !           592:        if (ferror(outf)) {
        !           593:                perror(outfile);
        !           594:                return -1;
        !           595:        }
        !           596:        return 0;
        !           597: }
        !           598: 
        !           599: checkfile(char *name)
        !           600: {
        !           601:        if ((langf = fopen(name, "rb")) == NULL) {
        !           602:                perror(name);
        !           603:                return -1;
        !           604:        }
        !           605:        strcpy(langfile, name);
        !           606:        language[0] = '\0';     /* count messages for first language */
        !           607:        errcount = 0;
        !           608:        make_indexfile(NULL);
        !           609:        fclose(langf);
        !           610:        return errcount;
        !           611: }
        !           612: 
        !           613: copypos(FILE *f, FILE *g, long pos)
        !           614: {
        !           615:        long size;
        !           616:        size = ftell(f) - pos;
        !           617:        fseek(f, pos, SEEK_SET);
        !           618:        copyfile(f, g, size);
        !           619: }
        !           620: 
        !           621: copyfile(FILE *f, FILE *g, long n)
        !           622: {
        !           623:        int c;
        !           624: 
        !           625:        while (--n >= 0 && (c = getc(f)) != EOF)
        !           626:                putc(c, g);
        !           627: }
        !           628: 
        !           629: long
        !           630: fsize(FILE *f)
        !           631: {
        !           632:        long len, pos;
        !           633:        pos = ftell(f);
        !           634:        fseek(f, 0L, SEEK_END);
        !           635:        len = ftell(f);
        !           636:        fseek(f, pos, SEEK_SET);
        !           637:        return len;
        !           638: }
        !           639: #endif /* LANGTOOL */

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.