]> git.sesse.net Git - cubemap/blob - config.cpp
Ignore SIGPIPE, so we do not die when a client shuts down at the wrong time.
[cubemap] / config.cpp
1 #include <assert.h>
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <map>
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "config.h"
12 #include "parse.h"
13
14 using namespace std;
15
16 #define DEFAULT_BACKLOG_SIZE 1048576
17
18 struct ConfigLine {
19         string keyword;
20         vector<string> arguments;
21         map<string, string> parameters;
22 };
23
24 bool read_config(const string &filename, vector<ConfigLine> *lines)
25 {
26         FILE *fp = fopen(filename.c_str(), "r");
27         if (fp == NULL) {
28                 perror(filename.c_str());
29                 return false;
30         }
31
32         char buf[4096];
33         while (!feof(fp)) {
34                 if (fgets(buf, sizeof(buf), fp) == NULL) {
35                         break;
36                 }
37
38                 // Chop off the string at the first #, \r or \n.
39                 buf[strcspn(buf, "#\r\n")] = 0;
40
41                 // Remove all whitespace from the end of the string.
42                 size_t len = strlen(buf);
43                 while (len > 0 && isspace(buf[len - 1])) {
44                         buf[--len] = 0;
45                 }
46
47                 // If the line is now all blank, ignore it.
48                 if (len == 0) {
49                         continue;
50                 }
51
52                 vector<string> tokens = split_tokens(buf);
53                 assert(!tokens.empty());
54                 
55                 ConfigLine line;
56                 line.keyword = tokens[0];
57
58                 for (size_t i = 1; i < tokens.size(); ++i) {
59                         // foo=bar is a parameter; anything else is an argument.
60                         size_t equals_pos = tokens[i].find_first_of('=');
61                         if (equals_pos == string::npos) {
62                                 line.arguments.push_back(tokens[i]);
63                         } else {
64                                 string key = tokens[i].substr(0, equals_pos);
65                                 string value = tokens[i].substr(equals_pos + 1, string::npos);
66                                 line.parameters.insert(make_pair(key, value));
67                         }
68                 }
69
70                 lines->push_back(line);
71         }
72
73         fclose(fp);
74         return true;
75 }
76
77 bool fetch_config_string(const vector<ConfigLine> &config, const string &keyword, string *value)
78 {
79         for (unsigned i = 0; i < config.size(); ++i) {
80                 if (config[i].keyword != keyword) {
81                         continue;
82                 }
83                 if (config[i].parameters.size() > 0 ||
84                     config[i].arguments.size() != 1) {
85                         fprintf(stderr, "ERROR: '%s' takes one argument and no parameters\n", keyword.c_str());
86                         return false;
87                 }
88                 *value = config[i].arguments[0];
89                 return true;
90         }
91         return false;
92 }
93
94 bool fetch_config_int(const vector<ConfigLine> &config, const string &keyword, int *value)
95 {
96         for (unsigned i = 0; i < config.size(); ++i) {
97                 if (config[i].keyword != keyword) {
98                         continue;
99                 }
100                 if (config[i].parameters.size() > 0 ||
101                     config[i].arguments.size() != 1) {
102                         fprintf(stderr, "ERROR: '%s' takes one argument and no parameters\n", keyword.c_str());
103                         return false;
104                 }
105                 *value = atoi(config[i].arguments[0].c_str());  // TODO: verify int validity.
106                 return true;
107         }
108         return false;
109 }
110
111 bool parse_port(const ConfigLine &line, Config *config)
112 {
113         if (line.arguments.size() != 1) {
114                 fprintf(stderr, "ERROR: 'port' takes exactly one argument\n");
115                 return false;
116         }
117
118         AcceptorConfig acceptor;
119         acceptor.port = atoi(line.arguments[0].c_str());
120         if (acceptor.port < 1 || acceptor.port >= 65536) {
121                 fprintf(stderr, "ERROR: port %d is out of range (must be [1,65536>).\n", acceptor.port);
122                 return false;
123         }
124
125         config->acceptors.push_back(acceptor);
126         return true;
127 }
128
129 int allocate_mark_pool(int from, int to, Config *config)
130 {
131         int pool_index = -1;    
132
133         // Reuse mark pools if an identical one exists.
134         // Otherwise, check if we're overlapping some other mark pool.
135         for (size_t i = 0; i < config->mark_pools.size(); ++i) {
136                 const MarkPoolConfig &pool = config->mark_pools[i];
137                 if (from == pool.from && to == pool.to) {
138                         pool_index = i;
139                 } else if ((from >= pool.from && from < pool.to) ||
140                            (to >= pool.from && to < pool.to)) {
141                         fprintf(stderr, "WARNING: Mark pool %d-%d partially overlaps with %d-%d, you may get duplicate marks.\n",
142                                         from, to, pool.from, pool.to);
143                         fprintf(stderr, "         Mark pools must either be completely disjunct, or completely overlapping.\n");
144                 }
145         }
146
147         if (pool_index != -1) {
148                 return pool_index;
149         }
150
151         // No match to existing pools.
152         MarkPoolConfig pool;
153         pool.from = from;
154         pool.to = to;
155         config->mark_pools.push_back(pool);
156
157         return config->mark_pools.size() - 1;
158 }
159
160 bool parse_mark_pool(const string &mark_str, int *from, int *to)
161 {
162         size_t split = mark_str.find_first_of('-');
163         if (split == string::npos) {
164                 fprintf(stderr, "ERROR: Invalid mark specification '%s' (expected 'X-Y').\n",
165                         mark_str.c_str());
166                 return false;
167         }
168
169         string from_str(mark_str.begin(), mark_str.begin() + split);
170         string to_str(mark_str.begin() + split + 1, mark_str.end());
171         *from = atoi(from_str.c_str());
172         *to = atoi(to_str.c_str());
173
174         if (*from <= 0 || *from >= 65536 || *to <= 0 || *to >= 65536) {
175                 fprintf(stderr, "ERROR: Mark pool range %d-%d is outside legal range [1,65536>.\n",
176                         *from, *to);
177                 return false;
178         }
179
180         return true;
181 }
182
183 bool parse_stream(const ConfigLine &line, Config *config)
184 {
185         if (line.arguments.size() != 1) {
186                 fprintf(stderr, "ERROR: 'stream' takes exactly one argument\n");
187                 return false;
188         }
189
190         StreamConfig stream;
191         stream.stream_id = line.arguments[0];
192
193         map<string, string>::const_iterator src_it = line.parameters.find("src");
194         if (src_it == line.parameters.end()) {
195                 fprintf(stderr, "WARNING: stream '%s' has no src= attribute, clients will not get any data.\n",
196                         stream.stream_id.c_str());
197         } else {
198                 stream.src = src_it->second;
199                 // TODO: Verify that the URL is parseable?
200         }
201
202         map<string, string>::const_iterator backlog_it = line.parameters.find("backlog_size");
203         if (backlog_it == line.parameters.end()) {
204                 stream.backlog_size = DEFAULT_BACKLOG_SIZE;
205         } else {
206                 stream.backlog_size = atoi(backlog_it->second.c_str());
207         }
208
209         // Parse marks, if so desired.
210         map<string, string>::const_iterator mark_parm_it = line.parameters.find("mark");
211         if (mark_parm_it == line.parameters.end()) {
212                 stream.mark_pool = -1;
213         } else {
214                 int from, to;
215                 if (!parse_mark_pool(mark_parm_it->second, &from, &to)) {
216                         return false;
217                 }
218                 stream.mark_pool = allocate_mark_pool(from, to, config);
219         }
220
221         config->streams.push_back(stream);
222         return true;
223 }
224
225 bool parse_config(const string &filename, Config *config)
226 {
227         vector<ConfigLine> lines;
228         if (!read_config(filename, &lines)) {
229                 return false;
230         }
231
232         if (!fetch_config_int(lines, "num_servers", &config->num_servers)) {
233                 fprintf(stderr, "ERROR: Missing 'num_servers' statement in config file.\n");
234                 return false;
235         }
236         if (config->num_servers < 1 || config->num_servers >= 20000) {  // Insanely high max limit.
237                 fprintf(stderr, "ERROR: 'num_servers' is %d, needs to be in [1, 20000>.\n", config->num_servers);
238                 return false;
239         }
240
241         // See if the user wants stats.
242         config->stats_interval = 60;
243         bool has_stats_file = fetch_config_string(lines, "stats_file", &config->stats_file);
244         bool has_stats_interval = fetch_config_int(lines, "stats_interval", &config->stats_interval);
245         if (has_stats_interval && !has_stats_file) {
246                 fprintf(stderr, "WARNING: 'stats_interval' given, but no 'stats_file'. No statistics will be written.\n");
247         }
248
249         for (size_t i = 0; i < lines.size(); ++i) {
250                 const ConfigLine &line = lines[i];
251                 if (line.keyword == "num_servers" ||
252                     line.keyword == "stats_file" ||
253                     line.keyword == "stats_interval") {
254                         // Already taken care of, above.
255                 } else if (line.keyword == "port") {
256                         if (!parse_port(line, config)) {
257                                 return false;
258                         }
259                 } else if (line.keyword == "stream") {
260                         if (!parse_stream(line, config)) {
261                                 return false;
262                         }
263                 } else {
264                         fprintf(stderr, "ERROR: Unknown configuration keyword '%s'.\n",
265                                 line.keyword.c_str());
266                         return false;
267                 }
268         }
269
270         return true;
271 }