修訂 | 7 (tree) |
---|---|
時間 | 2020-12-24 18:49:42 |
作者 | ![]() |
imported ver 20200113 to tags
@@ -0,0 +1,43 @@ | ||
1 | +/* | |
2 | + * CSV-reading C module | |
3 | + * | |
4 | + * Copyright (C) 2019 Mateusz Viste | |
5 | + * | |
6 | + */ | |
7 | + | |
8 | +/* reads a csv line from file. returns 0 on success, -1 on EOF or error. */ | |
9 | +static int csvreadline(char *buf, unsigned short maxlinelen, char **ptrs, int maxptrs, FILE *fd) { | |
10 | + int c; | |
11 | + int i; | |
12 | + int t; | |
13 | + int blen = 0; | |
14 | + /* read the line into buf */ | |
15 | + for (;;) { | |
16 | + c = fgetc(fd); | |
17 | + if ((c < 0) && (blen == 0)) return(-1); | |
18 | + if ((c < 0) || (c == '\n')) break; | |
19 | + if (c == '\r') continue; | |
20 | + buf[blen++] = (char)c; | |
21 | + if (blen == maxlinelen) return(-1); | |
22 | + } | |
23 | + buf[blen] = 0; | |
24 | + | |
25 | + /* set up pointers and terminate fields */ | |
26 | + for (i = 0; i < maxptrs; i++) ptrs[i] = buf + blen; /* preinit to empty */ | |
27 | + ptrs[0] = buf; | |
28 | + t = 1; | |
29 | + for (i = 0;; i++) { | |
30 | + switch (buf[i]) { | |
31 | + case ',': | |
32 | + buf[i] = 0; | |
33 | + ptrs[t] = buf + i + 1; | |
34 | + if ((t + 1) < maxptrs) t++; | |
35 | + break; | |
36 | + case '\r': | |
37 | + case '\n': | |
38 | + case 0: | |
39 | + buf[i] = 0; | |
40 | + return(0); | |
41 | + } | |
42 | + } | |
43 | +} |
@@ -0,0 +1,90 @@ | ||
1 | +/* | |
2 | + * OGUP "recently down" servers list gophermap | |
3 | + * | |
4 | + * Copyright (C) 2019 Mateusz Viste | |
5 | + */ | |
6 | + | |
7 | + | |
8 | +#include <stdio.h> | |
9 | +#include <stdlib.h> | |
10 | +#include <sys/stat.h> | |
11 | +#include <time.h> | |
12 | +#include <unistd.h> | |
13 | + | |
14 | + | |
15 | +#include "../../cmods/csv.c" | |
16 | + | |
17 | + | |
18 | +static char *howold(long s) { | |
19 | + static char buf[64]; | |
20 | + if (s < 120) { | |
21 | + sprintf(buf, "%ld seconds", s); | |
22 | + } else if (s < 3600) { | |
23 | + sprintf(buf, "%ld minutes", s / 60); | |
24 | + } else if (s < 7200) { | |
25 | + sprintf(buf, "%ld h and %ld minutes", s / 3600, (s % 3600) / 60); | |
26 | + } else if (s < 172800) { | |
27 | + sprintf(buf, "%ld hours", s / 3600); | |
28 | + } else { | |
29 | + sprintf(buf, "%ld days", s / 86400); | |
30 | + } | |
31 | + return(buf); | |
32 | +} | |
33 | + | |
34 | + | |
35 | +static char *printtime(struct tm *t) { | |
36 | + static char buf[64]; | |
37 | + sprintf(buf, "%d-%02d-%02d %02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min); | |
38 | + return(buf); | |
39 | +} | |
40 | + | |
41 | + | |
42 | +int main(void) { | |
43 | + time_t now, then; | |
44 | + struct tm *t; | |
45 | + char csvbuff[128]; | |
46 | + char *ptrs[4]; | |
47 | + char serv[32]; | |
48 | + long downcount = 0; | |
49 | + FILE *f; | |
50 | + | |
51 | + printf("i\n" | |
52 | + "iThis page lists gopherspace nodes that have been observed by the OGUP to\n" | |
53 | + "icollapse recently. All timestamps are given in terran UTC time.\n" | |
54 | + "i\n"); | |
55 | + | |
56 | + f = fopen("../ogupdb.dat", "rb"); | |
57 | + if (f == NULL) { | |
58 | + printf("3ERROR: fopen() failure\n"); | |
59 | + return(0); | |
60 | + } | |
61 | + | |
62 | + now = time(NULL); | |
63 | + for (;;) { | |
64 | + char *howlongago; | |
65 | + int i = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f); | |
66 | + if (i != 0) break; | |
67 | + then = atol(ptrs[2]); | |
68 | + if (then <= 1) continue; | |
69 | + howlongago = howold(now - then); | |
70 | + t = gmtime(&then); | |
71 | + if (atol(ptrs[1]) != 70) { | |
72 | + snprintf(serv, sizeof(serv), "%s:%s", ptrs[0], ptrs[1]); | |
73 | + } else { | |
74 | + snprintf(serv, sizeof(serv), "%s", ptrs[0]); | |
75 | + } | |
76 | + printf("i%-24s went down %s ago (%s)\n", ptrs[0], howlongago, printtime(t)); | |
77 | + downcount++; | |
78 | + } | |
79 | + | |
80 | + fclose(f); | |
81 | + | |
82 | + if (downcount == 0) { | |
83 | + printf("iNothing to list! All known gopherspace servers seem healthy.\n"); | |
84 | + } | |
85 | + | |
86 | + printf("i\n" | |
87 | + "i--- [EOF] --------------------------------------------------------------\n"); | |
88 | + | |
89 | + return(0); | |
90 | +} |
@@ -0,0 +1,100 @@ | ||
1 | +/* | |
2 | + * OGUP servers list gophermap | |
3 | + * | |
4 | + * Copyright (C) 2019 Mateusz Viste | |
5 | + */ | |
6 | + | |
7 | + | |
8 | +#include <stdio.h> | |
9 | +#include <stdlib.h> | |
10 | +#include <sys/stat.h> | |
11 | +#include <time.h> | |
12 | +#include <unistd.h> | |
13 | + | |
14 | + | |
15 | +#include "../../cmods/csv.c" | |
16 | + | |
17 | + | |
18 | +unsigned long getcount(const char *fname, time_t *modtime, char *errstr, unsigned long *countactive, unsigned long *countpending, unsigned long *countdown) { | |
19 | + FILE *f; | |
20 | + unsigned long count; | |
21 | + char buff[64]; | |
22 | + char *ptrs[4]; | |
23 | + struct stat sb; | |
24 | + | |
25 | + f = fopen(fname, "rb"); | |
26 | + if (f == NULL) { | |
27 | + sprintf(errstr, "failed to open db file"); | |
28 | + return(0); | |
29 | + } | |
30 | + if (fstat(fileno(f), &sb) != 0) { | |
31 | + fclose(f); | |
32 | + sprintf(errstr, "fstat() failed on db count file"); | |
33 | + return(0); | |
34 | + } | |
35 | + *modtime = sb.st_mtime; | |
36 | + csvreadline(buff, sizeof(buff), ptrs, 4, f); | |
37 | + fclose(f); | |
38 | + count = atol(ptrs[0]); | |
39 | + *countactive = atol(ptrs[1]); | |
40 | + *countpending = atol(ptrs[2]); | |
41 | + *countdown = atol(ptrs[3]); | |
42 | + | |
43 | + if (count == 0) sprintf(errstr, "empty db"); | |
44 | + | |
45 | + return(count); | |
46 | +} | |
47 | + | |
48 | + | |
49 | +int main(void) { | |
50 | + unsigned long count, countactive, countpending, countdown; | |
51 | + time_t lastupdate; | |
52 | + struct tm *lastupdatetm; | |
53 | + char errstr[64]; | |
54 | + char csvbuff[128]; | |
55 | + char *ptrs[4]; | |
56 | + FILE *f; | |
57 | + int i; | |
58 | + | |
59 | + printf("i\n"); | |
60 | + | |
61 | + count = getcount("../ogupdb.cnt", &lastupdate, errstr, &countactive, &countpending, &countdown); | |
62 | + lastupdatetm = gmtime(&lastupdate); | |
63 | + | |
64 | + if (count == 0) { | |
65 | + printf("3ERROR: %s\n", errstr); | |
66 | + } else { | |
67 | + printf("iThe Observable Gopherspace Universe Project knows about:\n" | |
68 | + "i%5lu servers total, of which:\n" | |
69 | + "i%5lu have been validated as operational\n" | |
70 | + "i%5lu are down\n" | |
71 | + "i%5lu are pending validation\n" | |
72 | + "i\n" | |
73 | + "iLast update: %04d-%02d-%02d %02d:%02d UTC\n", count, countactive, countdown, countpending, lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, lastupdatetm->tm_hour, lastupdatetm->tm_min); | |
74 | + } | |
75 | + | |
76 | + printf("i\n"); | |
77 | + | |
78 | + f = fopen("../ogupdb.dat", "rb"); | |
79 | + if (f == NULL) { | |
80 | + printf("3ERROR: fopen() failure\n"); | |
81 | + return(0); | |
82 | + } | |
83 | + | |
84 | + for (;;) { | |
85 | + i = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f); | |
86 | + if (i != 0) break; | |
87 | + if (atol(ptrs[2]) != 0) continue; /* ignore hosts that were offline last time I checked */ | |
88 | + if (atol(ptrs[1]) != 70) { | |
89 | + printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]); | |
90 | + } else { | |
91 | + printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]); | |
92 | + } | |
93 | + } | |
94 | + | |
95 | + fclose(f); | |
96 | + | |
97 | + printf("i\ni--- [EOF] -----------------------------\n"); | |
98 | + | |
99 | + return(0); | |
100 | +} |
@@ -0,0 +1,92 @@ | ||
1 | +/* | |
2 | + * OGUP servers list gophermap | |
3 | + * | |
4 | + * Copyright (C) 2019 Mateusz Viste | |
5 | + */ | |
6 | + | |
7 | + | |
8 | +#include <stdio.h> | |
9 | +#include <stdlib.h> | |
10 | +#include <time.h> | |
11 | +#include <unistd.h> | |
12 | + | |
13 | + | |
14 | +#include "../../cmods/csv.c" | |
15 | + | |
16 | + | |
17 | +#define DEBUG 0 | |
18 | + | |
19 | + | |
20 | +/* rewinds file fd to nearest start of line */ | |
21 | +static void rewindtosol(FILE *fd) { | |
22 | + int c; | |
23 | + int iterations = 0; | |
24 | + for (;;) { | |
25 | + iterations++; | |
26 | + if (ftell(fd) == 0) break; | |
27 | + c = fgetc(fd); | |
28 | + if (c == '\n') break; | |
29 | + fseek(fd, -2, SEEK_CUR); | |
30 | + if (iterations > 400) break; /* you never know */ | |
31 | + } | |
32 | +} | |
33 | + | |
34 | + | |
35 | +int main(void) { | |
36 | + char csvbuff[128]; | |
37 | + char *ptrs[4]; | |
38 | + FILE *f; | |
39 | + int i, count, csvres; | |
40 | + long fsize, randpos; | |
41 | + | |
42 | + printf("i\n"); | |
43 | + | |
44 | + f = fopen("../ogupdb.dat", "rb"); | |
45 | + if (f == NULL) { | |
46 | + printf("3ERROR: fopen() failure\n"); | |
47 | + return(0); | |
48 | + } | |
49 | + fseek(f, 0, SEEK_END); | |
50 | + fsize = ftell(f); | |
51 | + | |
52 | + if (fsize < 16) { | |
53 | + printf("3ERROR: fsize too low (%ld)\n", fsize); | |
54 | + fclose(f); | |
55 | + return(0); | |
56 | + } | |
57 | + | |
58 | + srand(time(NULL)); | |
59 | + | |
60 | + count = 0; | |
61 | + for (i = 0; i < 50; i++) { | |
62 | + | |
63 | + randpos = rand() % (fsize - 2); | |
64 | + fseek(f, randpos, SEEK_SET); | |
65 | + | |
66 | + /* rewind back to nearest \n or start of file */ | |
67 | + rewindtosol(f); | |
68 | + | |
69 | + /* read entry */ | |
70 | + csvres = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f); | |
71 | + if (csvres != 0) { | |
72 | + if (DEBUG) printf("icsvreadline() != 0\n"); | |
73 | + continue; | |
74 | + } | |
75 | + /* ignore hosts that were offline last time I checked */ | |
76 | + if (atol(ptrs[2]) != 0) { | |
77 | + if (DEBUG) printf("iatol(ptrs[2]) != 0 (%ld, '%s')\n", atol(ptrs[2]), ptrs[0]); | |
78 | + continue; | |
79 | + } | |
80 | + if (atol(ptrs[1]) != 70) { | |
81 | + printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]); | |
82 | + } else { | |
83 | + printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]); | |
84 | + } | |
85 | + count++; | |
86 | + if (count == 5) break; | |
87 | + } | |
88 | + | |
89 | + fclose(f); | |
90 | + | |
91 | + return(0); | |
92 | +} |
@@ -0,0 +1,79 @@ | ||
1 | +/* | |
2 | + * OGUP main gophermap | |
3 | + * | |
4 | + * Copyright (C) 2019 Mateusz Viste | |
5 | + */ | |
6 | + | |
7 | + | |
8 | +#include <stdio.h> | |
9 | +#include <stdlib.h> | |
10 | +#include <sys/stat.h> | |
11 | +#include <time.h> | |
12 | +#include <unistd.h> | |
13 | + | |
14 | + | |
15 | +#include "../cmods/csv.c" | |
16 | + | |
17 | + | |
18 | +unsigned long getcount(const char *fname, time_t *modtime, char *errstr, unsigned long *countactive, unsigned long *countpending, unsigned long *countdown) { | |
19 | + FILE *f; | |
20 | + unsigned long count; | |
21 | + char buff[64]; | |
22 | + char *ptrs[4]; | |
23 | + struct stat sb; | |
24 | + | |
25 | + f = fopen(fname, "rb"); | |
26 | + if (f == NULL) { | |
27 | + sprintf(errstr, "failed to open db file"); | |
28 | + return(0); | |
29 | + } | |
30 | + if (fstat(fileno(f), &sb) != 0) { | |
31 | + fclose(f); | |
32 | + sprintf(errstr, "fstat() failed on db count file"); | |
33 | + return(0); | |
34 | + } | |
35 | + *modtime = sb.st_mtime; | |
36 | + csvreadline(buff, sizeof(buff), ptrs, 4, f); | |
37 | + fclose(f); | |
38 | + count = atol(ptrs[0]); | |
39 | + *countactive = atol(ptrs[1]); | |
40 | + *countpending = atol(ptrs[2]); | |
41 | + *countdown = atol(ptrs[3]); | |
42 | + | |
43 | + if (count == 0) sprintf(errstr, "empty db"); | |
44 | + | |
45 | + return(count); | |
46 | +} | |
47 | + | |
48 | + | |
49 | +int main(void) { | |
50 | + unsigned long count, countactive, countpending, countdown; | |
51 | + time_t lastupdate; | |
52 | + struct tm *lastupdatetm; | |
53 | + char errstr[64]; | |
54 | + | |
55 | + printf("i\n" | |
56 | + "i Observable Gopherspace Universe Project\n" | |
57 | + "i\n" | |
58 | + "iWelcome to the gopher site of the Observable Gopherspace Universe\n" | |
59 | + "iProject (OGUP)! The OGUP is about maintaining and publishing an\n" | |
60 | + "iindependent list of servers available in the gopherspace.\n" | |
61 | + "i\n" | |
62 | + "1List of servers\tlist\n" | |
63 | + "1Show 5 random servers\trnd\n" | |
64 | + "1Recently down servers\tdown\n" | |
65 | + "1About the OGUP\tabout\n" | |
66 | + "1Support the project!\tsupport\n" | |
67 | + "i\n"); | |
68 | + | |
69 | + count = getcount("ogupdb.cnt", &lastupdate, errstr, &countactive, &countpending, &countdown); | |
70 | + lastupdatetm = gmtime(&lastupdate); | |
71 | + | |
72 | + if (count == 0) { | |
73 | + printf("3ERROR: %s\n", errstr); | |
74 | + } else { | |
75 | + printf("iAs of %04d-%02d-%02d, the OGUP knows about %lu active servers.\n", lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, countactive); | |
76 | + } | |
77 | + | |
78 | + return(0); | |
79 | +} |
@@ -0,0 +1,17 @@ | ||
1 | +# | |
2 | +# build gopherjoker | |
3 | +# | |
4 | +# Observable Gopherspace Universe Project | |
5 | +# Copyright (C) 2019-2020 Mateusz Viste | |
6 | +# | |
7 | + | |
8 | +CC = clang | |
9 | +CFLAGS = -g -O2 -Wall -Wextra -pedantic -std=gnu89 | |
10 | +CFLAGS += -Weverything -Wno-disabled-macro-expansion -Wno-padded | |
11 | + | |
12 | +all: gopherjoker | |
13 | + | |
14 | +gopherjoker: gopherjoker.o | |
15 | + | |
16 | +clean: | |
17 | + rm -f gopherjoker *.o |
@@ -0,0 +1,666 @@ | ||
1 | +/* | |
2 | + * gopherjoker, part of the Observable Gopherspace Universe Project | |
3 | + * Copyright (C) 2019-2020 Mateusz Viste | |
4 | + * | |
5 | + * 2020-01-13: added recent history so joker won't revisit places too often | |
6 | + * 2020-01-13: variety of minor fixes and casts to shut clang warnings | |
7 | + * 2019-03-09: hosts with no menu entries are kept in db - but as IP only | |
8 | + * 2019-02-27: host is removed if its main menu has no reference to itself | |
9 | + * 2019-02-20: fixed null ptr dereference, added a 'down' count to countfile | |
10 | + * 2019-02-19: countfile contains 3 values: total, active and pending servers | |
11 | + * 2019-02-18: first public release | |
12 | + * | |
13 | + * Redistribution and use in source and binary forms, with or without | |
14 | + * modification, are permitted provided that the following conditions are met: | |
15 | + * 1. Redistributions of source code must retain the above copyright notice, | |
16 | + * this list of conditions and the following disclaimer. | |
17 | + * | |
18 | + * 2. Redistributions in binary form must reproduce the above copyright | |
19 | + * notice, this list of conditions and the following disclaimer in the | |
20 | + * documentati.on and/or other materials provided with the distribution. | |
21 | + * | |
22 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
23 | + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
24 | + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
25 | + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
26 | + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
27 | + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
28 | + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
29 | + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
30 | + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
31 | + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
32 | + * POSSIBILITY OF SUCH DAMAGE. | |
33 | + */ | |
34 | + | |
35 | +#include <arpa/inet.h> /* inet_pton() */ | |
36 | +#include <ctype.h> /* tolower() */ | |
37 | +#include <errno.h> | |
38 | +#include <fcntl.h> | |
39 | +#include <netdb.h> /* gethostbyname() */ | |
40 | +#include <stdio.h> /* printf(), fopen(), ... */ | |
41 | +#include <stdlib.h> /* calloc(), rand() */ | |
42 | +#include <string.h> /* strcpy() */ | |
43 | +#include <sys/types.h> | |
44 | +#include <sys/socket.h> | |
45 | +#include <time.h> /* ctime(), time_t */ | |
46 | +#include <unistd.h> /* sleep() */ | |
47 | + | |
48 | +#include "../cmods/csv.c" | |
49 | + | |
50 | +struct gopherlist { | |
51 | + time_t failedsince; | |
52 | + struct gopherlist *next; | |
53 | + struct gopherlist *prev; | |
54 | + unsigned short port; | |
55 | + char *selector; | |
56 | + char fqdn[1]; | |
57 | +}; | |
58 | + | |
59 | +#define WAITPERIOD 20 | |
60 | +#define SAVEPERIOD 3600 | |
61 | +#define NEWHOSTSLIST "/tmp/gjoker_newhosts.csv" | |
62 | +#define MAXFAILTIME (3600 * 24 * 60) | |
63 | +#define TTLINIT 64 | |
64 | + | |
65 | + | |
66 | +/* used to avoid going back to same url too often */ | |
67 | +#define RECENT_HISTORY_SZ 64 | |
68 | +static uint32_t glob_recenthistory[RECENT_HISTORY_SZ]; | |
69 | +static int glob_recenthistoryptr = 0; | |
70 | + | |
71 | + | |
72 | +/**************** FUNCTIONS ****************/ | |
73 | + | |
74 | + | |
75 | +/* rotate an uint32_t value by 1 bit left */ | |
76 | +static void rotl(uint32_t *u) { | |
77 | + uint32_t bit; | |
78 | + bit = *u & 1; | |
79 | + *u <<= 1; | |
80 | + *u |= bit; | |
81 | +} | |
82 | + | |
83 | + | |
84 | +/* compute hash of a glist node (ie. a host, port, selector tuple) - used to | |
85 | + * determine the identity of a glist entry. this is a somewhat specialized | |
86 | + * version of a BSD sum. */ | |
87 | +static uint32_t gnodehash(const struct gopherlist *node) { | |
88 | + uint32_t res = 0; | |
89 | + int i; | |
90 | + /* start with port */ | |
91 | + res = node->port; | |
92 | + /* add host (case-insensitive) */ | |
93 | + for (i = 0; node->fqdn[i] != 0; i++) { | |
94 | + rotl(&res); | |
95 | + res ^= (uint32_t)tolower(node->fqdn[i]); | |
96 | + } | |
97 | + /* add selector */ | |
98 | + for (i = 0; (node->selector != NULL) && (node->selector[i] != 0); i++) { | |
99 | + rotl(&res); | |
100 | + res ^= (uint32_t)node->selector[i]; | |
101 | + } | |
102 | + return(res); | |
103 | +} | |
104 | + | |
105 | + | |
106 | +/* returns 0 on success (glist added to recent history), -1 on error (entry | |
107 | + * already present) */ | |
108 | +static int add_glist_to_recent_history_if_not_present(const struct gopherlist *node) { | |
109 | + uint32_t hash; | |
110 | + int i; | |
111 | + if (node == NULL) return(-1); | |
112 | + hash = gnodehash(node); | |
113 | + /* is it in history already? */ | |
114 | + for (i = 0; i < RECENT_HISTORY_SZ; i++) { | |
115 | + if (glob_recenthistory[i] == hash) return(-1); | |
116 | + } | |
117 | + /* add it */ | |
118 | + glob_recenthistory[glob_recenthistoryptr] = hash; | |
119 | + glob_recenthistoryptr++; /* advance ptr */ | |
120 | + glob_recenthistoryptr %= RECENT_HISTORY_SZ; | |
121 | + return(0); | |
122 | +} | |
123 | + | |
124 | + | |
125 | +static void glist_free(struct gopherlist *glist) { | |
126 | + struct gopherlist *victim; | |
127 | + while (glist) { | |
128 | + free(glist->selector); | |
129 | + victim = glist; | |
130 | + glist = glist->next; | |
131 | + free(victim); | |
132 | + } | |
133 | +} | |
134 | + | |
135 | + | |
136 | +/* returns pointer to dbfile name */ | |
137 | +static int parseargs(int argc, char **argv, char **dbfile, char **dbcount) { | |
138 | + int i; | |
139 | + *dbfile = NULL; | |
140 | + *dbcount = NULL; | |
141 | + for (i = 1; i < argc; i++) { | |
142 | + if ((argv[i][0] != '-') && ((*dbfile == NULL) || (*dbcount == NULL))) { | |
143 | + if (*dbfile == NULL) { | |
144 | + *dbfile = argv[i]; | |
145 | + } else { | |
146 | + *dbcount = argv[i]; | |
147 | + } | |
148 | + } else { | |
149 | + return(-1); | |
150 | + } | |
151 | + } | |
152 | + return(0); | |
153 | +} | |
154 | + | |
155 | + | |
156 | +static struct gopherlist *glist_findhost(struct gopherlist *glist, const char *host, const unsigned short port) { | |
157 | + struct gopherlist *node; | |
158 | + for (node = glist; node != NULL; node = node->next) { | |
159 | + if ((node->port == port) && (strcasecmp(node->fqdn, host) == 0)) return(node); | |
160 | + } | |
161 | + return(NULL); | |
162 | +} | |
163 | + | |
164 | + | |
165 | +/* add new host to glist, unless said host:port pair already exists there. | |
166 | + * returns pointer to the new (or already existing) struct. NULL on error. */ | |
167 | +static struct gopherlist *glist_addnewhost(struct gopherlist **glist, unsigned long *glistlen, char *newhost, unsigned short newport, time_t failedsince) { | |
168 | + struct gopherlist *node; | |
169 | + | |
170 | + /* is entry in list already? */ | |
171 | + node = glist_findhost(*glist, newhost, newport); | |
172 | + if (node != NULL) return(node); | |
173 | + | |
174 | + /* add it */ | |
175 | + node = calloc(1, sizeof(struct gopherlist) + strlen(newhost)); | |
176 | + if (node == NULL) return(NULL); | |
177 | + /* */ | |
178 | + strcpy(node->fqdn, newhost); | |
179 | + node->port = newport; | |
180 | + node->failedsince = failedsince; | |
181 | + node->prev = NULL; | |
182 | + node->next = *glist; | |
183 | + if (*glist != NULL) (*glist)->prev = node; | |
184 | + *glist = node; | |
185 | + *glistlen += 1; | |
186 | + return(*glist); | |
187 | +} | |
188 | + | |
189 | + | |
190 | +static struct gopherlist *loaddb(const char *fname, unsigned long *glistlen) { | |
191 | + FILE *fd; | |
192 | + int i; | |
193 | + char *ptrs[4]; | |
194 | + char lbuf[256]; | |
195 | + struct gopherlist *res = NULL; | |
196 | + *glistlen = 0; | |
197 | + fd = fopen(fname, "rb"); | |
198 | + if (fd == NULL) return(NULL); | |
199 | + for (;;) { | |
200 | + i = csvreadline(lbuf, sizeof(lbuf), ptrs, 4, fd); | |
201 | + if (i != 0) break; | |
202 | + /* TSV line structure | |
203 | + * 0 hostname | |
204 | + * 1 port | |
205 | + * 2 failedsince (time_t) */ | |
206 | + | |
207 | + if (glist_addnewhost(&res, glistlen, ptrs[0], (unsigned short)atoi(ptrs[1]), atol(ptrs[2])) == NULL) { | |
208 | + /* on error, free list and quit */ | |
209 | + glist_free(res); | |
210 | + *glistlen = 0; | |
211 | + return(NULL); | |
212 | + } | |
213 | + } | |
214 | + fclose(fd); | |
215 | + return(res); | |
216 | +} | |
217 | + | |
218 | + | |
219 | +static unsigned long savedb(const char *fname, const char *fcount, struct gopherlist *glist) { | |
220 | + unsigned long count = 0, countactive = 0, countpending = 0, countdown = 0; | |
221 | + FILE *f; | |
222 | + struct gopherlist *node; | |
223 | + f = fopen(fname, "wb"); | |
224 | + if (f == NULL) return(0); | |
225 | + for (node = glist; node != NULL; node = node->next) { | |
226 | + count++; | |
227 | + if (node->failedsince == 0) countactive++; | |
228 | + if (node->failedsince == 1) countpending++; | |
229 | + if (node->failedsince > 1) countdown++; | |
230 | + fprintf(f, "%s,%u,%ld\n", node->fqdn, node->port, node->failedsince); | |
231 | + } | |
232 | + fclose(f); | |
233 | + f = fopen(fcount, "wb"); | |
234 | + if (f == NULL) return(0); | |
235 | + fprintf(f, "%lu,%lu,%lu,%lu\n", count, countactive, countpending, countdown); | |
236 | + fclose(f); | |
237 | + return(count); | |
238 | +} | |
239 | + | |
240 | + | |
241 | +static struct gopherlist *dropnode(struct gopherlist *glist, struct gopherlist *node, unsigned long *glistlen) { | |
242 | + if (node->prev == NULL) { | |
243 | + glist = node->next; | |
244 | + glist->prev = NULL; | |
245 | + *glistlen -= 1; | |
246 | + return(glist); | |
247 | + } | |
248 | + node->prev->next = node->next; | |
249 | + if (node->next != NULL) { | |
250 | + node->next->prev = node->prev; | |
251 | + } | |
252 | + *glistlen -= 1; | |
253 | + return(glist); | |
254 | +} | |
255 | + | |
256 | + | |
257 | +static struct gopherlist *loadextrahosts(struct gopherlist *glist, const char *fname, unsigned long *glistlen) { | |
258 | + FILE *f; | |
259 | + char *ptrs[2]; | |
260 | + char buff[64]; | |
261 | + f = fopen(fname, "rb"); | |
262 | + if (f == NULL) return(glist); | |
263 | + | |
264 | + while (csvreadline(buff, sizeof(buff), ptrs, 2, f) == 0) { | |
265 | + int port = atoi(ptrs[1]); | |
266 | + if ((port < 1) || (port > 0xffff)) port = 70; | |
267 | + if (glist_addnewhost(&glist, glistlen, ptrs[0], (unsigned short)port, 0) == NULL) break; | |
268 | + } | |
269 | + fclose(f); | |
270 | + | |
271 | + return(glist); | |
272 | +} | |
273 | + | |
274 | + | |
275 | +/* pick a random node from glist, but avoid recently picked entries */ | |
276 | +static struct gopherlist *pickrandhostfromlist(struct gopherlist *glist, unsigned long glistlen) { | |
277 | + static long choice; | |
278 | + long i; | |
279 | + int try; | |
280 | + if (glistlen == 0) return(NULL); | |
281 | + for (try = 0; try < 5; try++) { | |
282 | + choice += rand(); | |
283 | + choice %= glistlen; | |
284 | + for (i = 0; i < choice; i++) { | |
285 | + if (glist == NULL) return(NULL); | |
286 | + glist = glist->next; | |
287 | + } | |
288 | + if (add_glist_to_recent_history_if_not_present(glist) == 0) { | |
289 | + return(glist); | |
290 | + } else if (glist != NULL) { | |
291 | + printf("NOTICE: picked selector %s:%u'%s' at first, but rejected as it is found in recent history already\n", glist->fqdn, glist->port, glist->selector); | |
292 | + } | |
293 | + } | |
294 | + return(NULL); | |
295 | +} | |
296 | + | |
297 | + | |
298 | +static int connect_nonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) { | |
299 | + int r; | |
300 | + struct timeval t; | |
301 | + fd_set selset; | |
302 | + for (;;) { | |
303 | + r = connect(s, addr, addrlen); | |
304 | + if (r == 0) return(0); | |
305 | + if (errno != EINPROGRESS) return(r); | |
306 | + FD_ZERO(&selset); | |
307 | + FD_SET(s, &selset); | |
308 | + t.tv_sec = timeout; | |
309 | + t.tv_usec = 0; | |
310 | + r = select(s + 1, NULL, &selset, NULL, &t); | |
311 | + if (r < 0) return(-3); | |
312 | + r = connect(s, addr, addrlen); | |
313 | + return(r); | |
314 | + } | |
315 | +} | |
316 | + | |
317 | + | |
318 | +static long gopher_fetch(char *buff, size_t buffsz, const char *host, unsigned short port, const char *selector, char *ipstr) { | |
319 | + int sock = -1; | |
320 | + size_t len; | |
321 | + int flags; | |
322 | + time_t timeout; | |
323 | + struct addrinfo *addr, *addrptr; | |
324 | + | |
325 | + /* resolve host & connect */ | |
326 | + if (getaddrinfo(host, NULL, NULL, &addr) != 0) return(-1); | |
327 | + for (addrptr = addr; addrptr != NULL; addrptr = addrptr->ai_next) { | |
328 | + struct sockaddr_in *sin; | |
329 | + sock = socket(addrptr->ai_family, addrptr->ai_socktype, addrptr->ai_protocol); | |
330 | + if (sock < 0) continue; | |
331 | + /* set port */ | |
332 | + sin = (void *)(addrptr->ai_addr); | |
333 | + sin->sin_port = htons(port); | |
334 | + /* set socket as non-blocking */ | |
335 | + flags = fcntl(sock, F_GETFL); | |
336 | + fcntl(sock, F_SETFL, flags | O_NONBLOCK); | |
337 | + /* try to connect */ | |
338 | + if (connect_nonblocking(sock, addrptr->ai_addr, addrptr->ai_addrlen, 10) == 0) { | |
339 | + /* fill ipstr and continue */ | |
340 | + inet_ntop(addrptr->ai_family, addrptr->ai_addr, ipstr, addrptr->ai_addrlen); | |
341 | + break; | |
342 | + } else { /* close sock and try next option (if any) */ | |
343 | + close(sock); | |
344 | + sock = -1; | |
345 | + } | |
346 | + } | |
347 | + freeaddrinfo(addr); | |
348 | + if (sock < 0) return(-2); | |
349 | + | |
350 | + /* send selector, terminated by a CR/LF pair */ | |
351 | + if (selector != NULL) send(sock, selector, strlen(selector), MSG_MORE); | |
352 | + send(sock, "\r\n", 2, 0); | |
353 | + | |
354 | + /* fetch answer until end of transmission or timeout */ | |
355 | + len = 0; | |
356 | + timeout = time(NULL) + 10; | |
357 | + for (;;) { | |
358 | + size_t spaceleft = buffsz - len; | |
359 | + ssize_t rlen; | |
360 | + if (time(NULL) > timeout) { | |
361 | + close(sock); | |
362 | + return(-3); | |
363 | + } | |
364 | + if (spaceleft == 0) break; /* I'm stuffed thank you */ | |
365 | + rlen = recv(sock, buff + len, spaceleft, 0); | |
366 | + if (rlen == 0) break; /* orderly shutdown */ | |
367 | + if (rlen < 0) { /* sock error */ | |
368 | + if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) continue; | |
369 | + close(sock); | |
370 | + return(-4); | |
371 | + } | |
372 | + len += (size_t)rlen; | |
373 | + } | |
374 | + | |
375 | + /* close sock and quit */ | |
376 | + close(sock); | |
377 | + return((long)len); | |
378 | +} | |
379 | + | |
380 | + | |
381 | +/* read a gopher line entry and fill host, port and selector accordingly. | |
382 | + * returns 0 on success, non-zero otherwise. */ | |
383 | +static int readgline(char *host, unsigned short hostsz, unsigned short *port, char *selector, unsigned short selectorsz, const char *menu, long menulen) { | |
384 | + char portstr[8]; | |
385 | + unsigned short i = 0, t; | |
386 | + /* skip first tabs (description) */ | |
387 | + for (;;) { | |
388 | + i++; | |
389 | + /* printf("i=%d ['%c']\n", i, menu[i]); */ | |
390 | + if (menu[i] == '\t') break; | |
391 | + if (menu[i] == '\r') return(-2); | |
392 | + if (menu[i] == '\n') return(-3); | |
393 | + if (i >= menulen) return(-4); | |
394 | + } | |
395 | + /* read selector */ | |
396 | + for (t = 0; ; t++) { | |
397 | + if (t >= selectorsz) return(-5); | |
398 | + i++; | |
399 | + if (i >= menulen) return(-6); | |
400 | + if (menu[i] == '\r') return(-7); | |
401 | + if (menu[i] == '\n') return(-8); | |
402 | + selector[t] = menu[i]; | |
403 | + if (selector[t] == '\t') { | |
404 | + selector[t] = 0; | |
405 | + break; | |
406 | + } | |
407 | + } | |
408 | + /* read hostname */ | |
409 | + for (t = 0; ; t++) { | |
410 | + if (t >= hostsz) return(-9); | |
411 | + i++; | |
412 | + if (i >= menulen) return(-10); | |
413 | + if (menu[i] == '\r') return(-11); | |
414 | + if (menu[i] == '\n') return(-12); | |
415 | + host[t] = menu[i]; | |
416 | + if (host[t] == '\t') { | |
417 | + host[t] = 0; | |
418 | + break; | |
419 | + } | |
420 | + } | |
421 | + /* read port */ | |
422 | + for (t = 0; ; t++) { | |
423 | + if (t >= sizeof(portstr)) return(-13); | |
424 | + i++; | |
425 | + if (i >= menulen) return(-14); | |
426 | + portstr[t] = menu[i]; | |
427 | + if ((portstr[t] == '\t') || (portstr[t] == '\n')) { | |
428 | + int tport; | |
429 | + portstr[t] = 0; | |
430 | + tport = atoi(portstr); | |
431 | + if ((tport < 1) || (tport > 0xffff)) return(-15); | |
432 | + *port = (unsigned short)tport; | |
433 | + break; | |
434 | + } | |
435 | + } | |
436 | + return(0); | |
437 | +} | |
438 | + | |
439 | + | |
440 | +static int ishostvalid(const char *host) { | |
441 | + unsigned short i; | |
442 | + for (i = 0; host[i] != 0; i++) { | |
443 | + if ((host[i] >= 'a') && (host[i] <= 'z')) continue; | |
444 | + if ((host[i] >= 'A') && (host[i] <= 'Z')) continue; | |
445 | + if ((host[i] >= '0') && (host[i] <= '9')) continue; | |
446 | + if (host[i] == '-') continue; | |
447 | + if (host[i] == '.') continue; | |
448 | + return(-1); | |
449 | + } | |
450 | + if (i < 3) return(-10); /* host len should be at least 3 chars long */ | |
451 | + return(0); | |
452 | +} | |
453 | + | |
454 | + | |
455 | +/* parses a gopher menu and remembers all the hosts present in its links */ | |
456 | +static struct gopherlist *menu2gopherlist(const char *menu, long menulen) { | |
457 | + long i; | |
458 | + struct gopherlist *res = NULL; | |
459 | + struct gopherlist *node; | |
460 | + | |
461 | + /* DEBUG */ | |
462 | + printf("------------------------------------------------\n"); | |
463 | + for (i = 0; i < menulen; i++) printf("%c", menu[i]); | |
464 | + printf("------------------------------------------------\n"); | |
465 | + | |
466 | + /* iterate line by line */ | |
467 | + for (i = 0; i < menulen; i++) { | |
468 | + if (menu[i] == '1') { | |
469 | + char host[64]; | |
470 | + char selector[128]; | |
471 | + unsigned short port; | |
472 | + | |
473 | + if (readgline(host, sizeof(host), &port, selector, sizeof(selector), menu + i, menulen - i) == 0) { | |
474 | + /* validate host name */ | |
475 | + if (ishostvalid(host) != 0) continue; | |
476 | + /* */ | |
477 | + node = calloc(1, sizeof(struct gopherlist) + strlen(host)); | |
478 | + if (node == NULL) { | |
479 | + glist_free(res); | |
480 | + printf("ERR: OUT OF MEMORY\n"); | |
481 | + return(NULL); | |
482 | + } | |
483 | + node->port = port; | |
484 | + strcpy(node->fqdn, host); | |
485 | + node->next = res; | |
486 | + node->selector = strdup(selector); | |
487 | + if (res != NULL) res->prev = node; | |
488 | + res = node; | |
489 | + } | |
490 | + } | |
491 | + /* skip to next line */ | |
492 | + for (;;) { | |
493 | + i++; | |
494 | + if (i >= menulen) break; | |
495 | + if (menu[i] == '\n') break; | |
496 | + } | |
497 | + } | |
498 | + | |
499 | + return(res); | |
500 | +} | |
501 | + | |
502 | + | |
503 | +static void gnode_free(struct gopherlist **node) { | |
504 | + if (*node == NULL) return; | |
505 | + free((*node)->selector); | |
506 | + free(*node); | |
507 | + *node = NULL; | |
508 | +} | |
509 | + | |
510 | + | |
511 | +static struct gopherlist *gnodedup(struct gopherlist *node) { | |
512 | + struct gopherlist *res; | |
513 | + if (node == NULL) return(NULL); | |
514 | + res = calloc(1, sizeof(struct gopherlist) + strlen(node->fqdn)); | |
515 | + if (res == NULL) return(NULL); | |
516 | + | |
517 | + memcpy(res, node, sizeof(struct gopherlist) + strlen(node->fqdn)); | |
518 | + if (node->selector != NULL) res->selector = strdup(node->selector); | |
519 | + return(res); | |
520 | +} | |
521 | + | |
522 | + | |
523 | +/**************** MAIN ****************/ | |
524 | + | |
525 | +int main(int argc, char **argv) { | |
526 | + time_t nextaction = 0; | |
527 | + time_t nextdbsave = 0; | |
528 | + struct gopherlist *glist, *mlist, *gnode; | |
529 | + struct gopherlist *curhost = NULL; | |
530 | + char curhost_ipaddr[INET6_ADDRSTRLEN]; | |
531 | + int ttl = 0; | |
532 | + char buff[0xffff]; | |
533 | + long bufflen; | |
534 | + char *dbfile, *dbfilecnt; | |
535 | + unsigned long glistlen; | |
536 | + unsigned long i; | |
537 | + | |
538 | + if (parseargs(argc, argv, &dbfile, &dbfilecnt) != 0) { | |
539 | + printf("usage: gopherjoker dbfile.csv dbcount.txt\n"); | |
540 | + return(1); | |
541 | + } | |
542 | + | |
543 | + /* load db file */ | |
544 | + glist = loaddb(dbfile, &glistlen); | |
545 | + | |
546 | + /* init random engine */ | |
547 | + srand((unsigned int)time(NULL)); | |
548 | + | |
549 | + for (;;) { | |
550 | + | |
551 | + printf("\n\n\n\n\n"); | |
552 | + | |
553 | + /* do not browse too fast */ | |
554 | + while (time(NULL) < nextaction) sleep(1); | |
555 | + nextaction = time(NULL) + WAITPERIOD; | |
556 | + | |
557 | + /* if extra manual hosts required to be added, do it now */ | |
558 | + glist = loadextrahosts(glist, NEWHOSTSLIST, &glistlen); | |
559 | + | |
560 | + /* save the db once every hour */ | |
561 | + if (time(NULL) > nextdbsave) { | |
562 | + unsigned long savedbcount; | |
563 | + printf("dumping hosts lists to %s\n", dbfile); | |
564 | + savedbcount = savedb(dbfile, dbfilecnt, glist); | |
565 | + if (savedbcount != glistlen) { | |
566 | + printf("ERR: savedbcount != glistlen @ %d (%lu != %lu)\n", __LINE__, savedbcount, glistlen); | |
567 | + } | |
568 | + nextdbsave = time(NULL) + SAVEPERIOD; | |
569 | + } | |
570 | + | |
571 | + /* if ttl expired, pick a new host to browse */ | |
572 | + if ((--ttl == 0) || (curhost == NULL)) { | |
573 | + printf("TTL expired -> picking new host\n"); | |
574 | + curhost = pickrandhostfromlist(glist, glistlen); | |
575 | + curhost = gnodedup(curhost); | |
576 | + ttl = TTLINIT; | |
577 | + } | |
578 | + | |
579 | + printf("TTL=%d | glistlen=%ld\n", ttl, glistlen); | |
580 | + | |
581 | + /* if no host, continue */ | |
582 | + if (curhost == NULL) { | |
583 | + printf("hosts list empty. add some through " NEWHOSTSLIST ".\n"); | |
584 | + continue; | |
585 | + } else { | |
586 | + printf("fetching %s:%u/1%s ...\n", curhost->fqdn, curhost->port, curhost->selector); | |
587 | + } | |
588 | + | |
589 | + /* fetch selector */ | |
590 | + bufflen = gopher_fetch(buff, sizeof(buff), curhost->fqdn, curhost->port, curhost->selector, curhost_ipaddr); | |
591 | + if (bufflen < 1) { /* fail */ | |
592 | + printf("failed\n"); | |
593 | + /* if it was about root selector, see if it's time to drop the server */ | |
594 | + if (curhost->selector == NULL) { | |
595 | + gnode = glist_findhost(glist, curhost->fqdn, curhost->port); | |
596 | + if (gnode->failedsince == 0) { | |
597 | + /* server was working at some point in the past, but not anymore */ | |
598 | + gnode->failedsince = time(NULL); | |
599 | + printf("server %s:%u went down -> flaged as failed since now (%s)\n", gnode->fqdn, gnode->port, ctime(&(gnode->failedsince))); | |
600 | + } else if (time(NULL) > curhost->failedsince + MAXFAILTIME) { | |
601 | + /* remove server from the list */ | |
602 | + printf("server removed due to long-time failure: %s:%u (failed since %s)\n", gnode->fqdn, gnode->port, ctime(&(gnode->failedsince))); | |
603 | + glist = dropnode(glist, gnode, &glistlen); | |
604 | + gnode_free(&gnode); | |
605 | + } | |
606 | + } | |
607 | + gnode_free(&curhost); | |
608 | + continue; | |
609 | + } | |
610 | + | |
611 | + printf("ok (%ld bytes)\n", bufflen); | |
612 | + | |
613 | + /* menu to glist */ | |
614 | + mlist = menu2gopherlist(buff, bufflen); | |
615 | + if (mlist == NULL) { | |
616 | + printf("ERR: no entries found in menu\n"); | |
617 | + /* if no '1' menu entries at all AND this was its main menu AND I got | |
618 | + * at least 64 bytes of data, then add server's IP address */ | |
619 | + if ((curhost->selector == NULL) && (bufflen >= 64)) { | |
620 | + printf(" (keeping server in list as %s)\n", curhost_ipaddr); | |
621 | + glist_addnewhost(&glist, &glistlen, curhost_ipaddr, curhost->port, 0); | |
622 | + } | |
623 | + /* */ | |
624 | + glist_free(mlist); | |
625 | + gnode_free(&curhost); | |
626 | + continue; | |
627 | + } | |
628 | + | |
629 | + /* try adding hosts to global glist (and count 'em) */ | |
630 | + i = 0; | |
631 | + for (gnode = mlist; gnode != NULL; gnode = gnode->next) { | |
632 | + i++; | |
633 | + glist_addnewhost(&glist, &glistlen, gnode->fqdn, gnode->port, 1); | |
634 | + } | |
635 | + | |
636 | + /* if main menu, then check that host is pointing to himself */ | |
637 | + if (curhost->selector == NULL) { | |
638 | + /* make sure the server points to itself */ | |
639 | + if (glist_findhost(mlist, curhost->fqdn, curhost->port) == NULL) { | |
640 | + printf("ERR: main menu contains no link to self, dropping hostname '%s'\n", curhost->fqdn); | |
641 | + gnode = glist_findhost(glist, curhost->fqdn, curhost->port); | |
642 | + glist = dropnode(glist, gnode, &glistlen); | |
643 | + glist_free(mlist); | |
644 | + gnode_free(&curhost); | |
645 | + continue; | |
646 | + } | |
647 | + } | |
648 | + | |
649 | + /* mark host as 'okay' */ | |
650 | + gnode = glist_findhost(glist, curhost->fqdn, curhost->port); | |
651 | + gnode->failedsince = 0; | |
652 | + | |
653 | + /* choose a random entry from mlist */ | |
654 | + gnode = pickrandhostfromlist(mlist, i); | |
655 | + if (gnode == NULL) { | |
656 | + printf("pickrandhostfromlist() could not find a node candidate in list\n"); | |
657 | + gnode_free(&curhost); | |
658 | + glist_free(mlist); | |
659 | + continue; | |
660 | + } | |
661 | + curhost = gnodedup(gnode); | |
662 | + | |
663 | + glist_free(mlist); | |
664 | + } | |
665 | + /* end of program */ | |
666 | +} |
@@ -0,0 +1,13 @@ | ||
1 | +#!/bin/sh | |
2 | + | |
3 | +CC="gcc" | |
4 | +CFLAGS="-Wall -Wextra -pedantic -O2 -std=gnu89" | |
5 | + | |
6 | +$CC $CFLAGS frontend/gophermap.c -o frontend/gophermap.cgi | |
7 | +$CC $CFLAGS frontend/down/gophermap.c -o frontend/down/gophermap.cgi | |
8 | +$CC $CFLAGS frontend/list/gophermap.c -o frontend/list/gophermap.cgi | |
9 | +$CC $CFLAGS frontend/rnd/gophermap.c -o frontend/rnd/gophermap.cgi | |
10 | + | |
11 | +cd gopherjoker | |
12 | +make | |
13 | +cd .. |
@@ -0,0 +1,10 @@ | ||
1 | +#!/bin/sh | |
2 | + | |
3 | +rm -f frontend/gophermap.cgi | |
4 | +rm -f frontend/down/gophermap.cgi | |
5 | +rm -f frontend/list/gophermap.cgi | |
6 | +rm -f frontend/rnd/gophermap.cgi | |
7 | + | |
8 | +cd gopherjoker | |
9 | +make clean | |
10 | +cd .. |