]>
Commit | Line | Data |
---|---|---|
12c86877 BH |
1 | /* |
2 | PowerDNS Versatile Database Driven Nameserver | |
5b2cb3be | 3 | Copyright (C) 2002 - 2008 PowerDNS.COM BV |
12c86877 BH |
4 | |
5 | This program is free software; you can redistribute it and/or modify | |
fd4b10f7 BH |
6 | it under the terms of the GNU General Public License version 2 as published |
7 | by the Free Software Foundation | |
12c86877 | 8 | |
f782fe38 MH |
9 | Additionally, the license of this program contains a special |
10 | exception which allows to distribute the program in binary form when | |
11 | it is linked against OpenSSL. | |
12 | ||
12c86877 BH |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
06bd9ccf | 20 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
12c86877 | 21 | */ |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
12c86877 | 25 | #include "arguments.hh" |
5b2cb3be | 26 | #include <boost/algorithm/string.hpp> |
2dfec518 | 27 | #include <boost/algorithm/string/compare.hpp> |
4f6ef75f | 28 | #include <boost/algorithm/string/predicate.hpp> |
fa8fd4d2 | 29 | |
61b26744 | 30 | #include "namespaces.hh" |
4f6ef75f | 31 | #include "logger.hh" |
8a16d778 AT |
32 | #include <sys/types.h> |
33 | #include <dirent.h> | |
34 | #include <sys/stat.h> | |
35 | #include <unistd.h> | |
64f3490e | 36 | #include <limits.h> |
12c86877 | 37 | |
12c86877 BH |
38 | const ArgvMap::param_t::const_iterator ArgvMap::begin() |
39 | { | |
40 | return params.begin(); | |
41 | } | |
42 | ||
43 | const ArgvMap::param_t::const_iterator ArgvMap::end() | |
44 | { | |
45 | return params.end(); | |
46 | } | |
47 | ||
48 | string & ArgvMap::set(const string &var) | |
49 | { | |
50 | return params[var]; | |
51 | } | |
52 | ||
53 | bool ArgvMap::mustDo(const string &var) | |
bd11bd1d | 54 | { |
12c86877 BH |
55 | return ((*this)[var]!="no") && ((*this)[var]!="off"); |
56 | } | |
57 | ||
58 | vector<string>ArgvMap::list() | |
59 | { | |
60 | vector<string> ret; | |
61 | for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i) | |
62 | ret.push_back(i->first); | |
63 | return ret; | |
64 | } | |
65 | ||
66 | string ArgvMap::getHelp(const string &item) | |
67 | { | |
68 | return helpmap[item]; | |
69 | } | |
70 | ||
71 | string & ArgvMap::set(const string &var, const string &help) | |
72 | { | |
73 | helpmap[var]=help; | |
74 | d_typeMap[var]="Parameter"; | |
75 | return set(var); | |
76 | } | |
77 | ||
78 | void ArgvMap::setCmd(const string &var, const string &help) | |
79 | { | |
80 | helpmap[var]=help; | |
81 | d_typeMap[var]="Command"; | |
82 | set(var)="no"; | |
83 | } | |
84 | ||
85 | string & ArgvMap::setSwitch(const string &var, const string &help) | |
86 | { | |
87 | helpmap[var]=help; | |
88 | d_typeMap[var]="Switch"; | |
89 | return set(var); | |
90 | } | |
91 | ||
92 | ||
12c86877 BH |
93 | bool ArgvMap::contains(const string &var, const string &val) |
94 | { | |
102eb646 BH |
95 | params_t::const_iterator param = params.find(var); |
96 | if(param == params.end() || param->second.empty()) { | |
97 | return false; | |
98 | } | |
562588a3 BH |
99 | vector<string> parts; |
100 | vector<string>::const_iterator i; | |
101 | ||
102eb646 | 102 | stringtok( parts, param->second, ", \t" ); |
562588a3 BH |
103 | for( i = parts.begin(); i != parts.end(); i++ ) { |
104 | if( *i == val ) { | |
105 | return true; | |
106 | } | |
107 | } | |
108 | ||
109 | return false; | |
12c86877 BH |
110 | } |
111 | ||
12c86877 BH |
112 | string ArgvMap::helpstring(string prefix) |
113 | { | |
114 | if(prefix=="no") | |
115 | prefix=""; | |
116 | ||
117 | string help; | |
118 | ||
119 | for(map<string,string>::const_iterator i=helpmap.begin(); | |
120 | i!=helpmap.end(); | |
121 | i++) | |
122 | { | |
123 | if(!prefix.empty() && i->first.find(prefix)) // only print items with prefix | |
4957a608 | 124 | continue; |
12c86877 BH |
125 | |
126 | help+=" --"; | |
127 | help+=i->first; | |
128 | ||
129 | string type=d_typeMap[i->first]; | |
130 | ||
131 | if(type=="Parameter") | |
4957a608 | 132 | help+="=..."; |
12c86877 | 133 | else if(type=="Switch") |
4957a608 BH |
134 | { |
135 | help+=" | --"+i->first+"=yes"; | |
136 | help+=" | --"+i->first+"=no"; | |
137 | } | |
12c86877 BH |
138 | |
139 | ||
140 | help+="\n\t"; | |
141 | help+=i->second; | |
142 | help+="\n"; | |
143 | ||
144 | } | |
145 | return help; | |
146 | } | |
147 | ||
6bef3d91 | 148 | string ArgvMap::configstring(bool current) |
12c86877 BH |
149 | { |
150 | string help; | |
6bef3d91 RA |
151 | |
152 | if (current) | |
153 | help="# Autogenerated configuration file based on running instance\n"; | |
154 | else | |
155 | help="# Autogenerated configuration file template\n"; | |
12c86877 | 156 | |
c7efa8ff RA |
157 | for(map<string,string>::const_iterator i=helpmap.begin(); i!=helpmap.end(); i++) { |
158 | if(d_typeMap[i->first]=="Command") | |
159 | continue; | |
160 | ||
161 | help+="#################################\n"; | |
162 | help+="# "; | |
163 | help+=i->first; | |
164 | help+="\t"; | |
165 | help+=i->second; | |
166 | help+="\n#\n"; | |
167 | if (current) { | |
168 | help+=i->first+"="+params[i->first]+"\n\n"; | |
169 | } else { | |
170 | help+="# "+i->first+"="+params[i->first]+"\n\n"; | |
12c86877 | 171 | } |
c7efa8ff | 172 | } |
12c86877 BH |
173 | return help; |
174 | } | |
175 | ||
12c86877 BH |
176 | const string & ArgvMap::operator[](const string &arg) |
177 | { | |
178 | if(!parmIsset(arg)) | |
179 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
180 | ||
12c86877 BH |
181 | return params[arg]; |
182 | } | |
183 | ||
cc2978dc BH |
184 | mode_t ArgvMap::asMode(const string &arg) |
185 | { | |
186 | mode_t mode; | |
187 | const char *cptr_orig; | |
188 | char *cptr_ret = NULL; | |
189 | ||
190 | if(!parmIsset(arg)) | |
191 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
192 | ||
193 | cptr_orig = params[arg].c_str(); | |
194 | mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8)); | |
195 | if (mode == 0 && cptr_ret == cptr_orig) | |
196 | throw ArgException("'" + arg + string("' contains invalid octal mode")); | |
197 | return mode; | |
198 | } | |
199 | ||
200 | gid_t ArgvMap::asGid(const string &arg) | |
201 | { | |
202 | gid_t gid; | |
203 | const char *cptr_orig; | |
204 | char *cptr_ret = NULL; | |
205 | ||
206 | if(!parmIsset(arg)) | |
207 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
208 | ||
209 | cptr_orig = params[arg].c_str(); | |
210 | gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0)); | |
211 | if (gid == 0 && cptr_ret == cptr_orig) { | |
212 | // try to resolve | |
213 | struct group *group = getgrnam(params[arg].c_str()); | |
214 | if (group == NULL) | |
215 | throw ArgException("'" + arg + string("' contains invalid group")); | |
216 | gid = group->gr_gid; | |
217 | } | |
218 | return gid; | |
219 | } | |
220 | ||
221 | uid_t ArgvMap::asUid(const string &arg) | |
222 | { | |
223 | uid_t uid; | |
224 | const char *cptr_orig; | |
225 | char *cptr_ret = NULL; | |
226 | ||
227 | if(!parmIsset(arg)) | |
228 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
229 | ||
230 | cptr_orig = params[arg].c_str(); | |
231 | uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0)); | |
232 | if (uid == 0 && cptr_ret == cptr_orig) { | |
233 | // try to resolve | |
234 | struct passwd *pwent = getpwnam(params[arg].c_str()); | |
235 | if (pwent == NULL) | |
236 | throw ArgException("'" + arg + string("' contains invalid group")); | |
237 | uid = pwent->pw_uid; | |
238 | } | |
239 | return uid; | |
240 | } | |
cc2978dc | 241 | |
ff99a74b | 242 | int ArgvMap::asNum(const string &arg, int def) |
12c86877 | 243 | { |
cc2978dc BH |
244 | int retval; |
245 | const char *cptr_orig; | |
246 | char *cptr_ret = NULL; | |
247 | ||
12c86877 BH |
248 | if(!parmIsset(arg)) |
249 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
250 | ||
ff99a74b | 251 | // use default for empty values |
cc2978dc | 252 | if (params[arg].empty()) |
ff99a74b | 253 | return def; |
cc2978dc BH |
254 | |
255 | cptr_orig = params[arg].c_str(); | |
256 | retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0)); | |
257 | if (!retval && cptr_ret == cptr_orig) | |
258 | throw ArgException("'"+arg+string("' is not valid number")); | |
259 | ||
260 | return retval; | |
261 | } | |
262 | ||
263 | bool ArgvMap::isEmpty(const string &arg) | |
264 | { | |
265 | if(!parmIsset(arg)) | |
266 | return true; | |
267 | return params[arg].empty(); | |
12c86877 BH |
268 | } |
269 | ||
270 | double ArgvMap::asDouble(const string &arg) | |
271 | { | |
cc2978dc BH |
272 | double retval; |
273 | const char *cptr_orig; | |
274 | char *cptr_ret = NULL; | |
275 | ||
12c86877 BH |
276 | if(!parmIsset(arg)) |
277 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
278 | ||
cc2978dc BH |
279 | if (params[arg].empty()) |
280 | return 0.0; | |
281 | ||
282 | cptr_orig = params[arg].c_str(); | |
283 | retval = strtod(cptr_orig, &cptr_ret); | |
284 | ||
285 | if (retval == 0 && cptr_ret == cptr_orig) | |
286 | throw ArgException("'"+arg+string("' is not valid double")); | |
287 | ||
288 | return retval; | |
12c86877 BH |
289 | } |
290 | ||
291 | ArgvMap::ArgvMap() | |
292 | { | |
293 | ||
294 | } | |
295 | ||
296 | bool ArgvMap::parmIsset(const string &var) | |
297 | { | |
298 | return (params.find(var)!=params.end()); | |
299 | } | |
300 | ||
301 | void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax) | |
302 | { | |
303 | string var, val; | |
bdf40704 | 304 | string::size_type pos; |
5a177409 | 305 | bool incremental = false; |
4d752b05 KM |
306 | |
307 | if(!arg.find("--") && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case | |
308 | { | |
309 | var=arg.substr(2,pos-2); | |
310 | val=arg.substr(pos+2); | |
311 | incremental = true; | |
312 | } | |
313 | else if(!arg.find("--") && (pos=arg.find("="))!=string::npos) // this is a --port=25 case | |
314 | { | |
315 | var=arg.substr(2,pos-2); | |
316 | val=arg.substr(pos+1); | |
317 | } | |
12c86877 | 318 | else if(!arg.find("--") && (arg.find("=")==string::npos)) // this is a --daemon case |
4d752b05 KM |
319 | { |
320 | var=arg.substr(2); | |
321 | val=""; | |
322 | } | |
12c86877 | 323 | else if(arg[0]=='-') |
4d752b05 KM |
324 | { |
325 | var=arg.substr(1); | |
326 | val=""; | |
12c86877 | 327 | } |
4d752b05 KM |
328 | else // command |
329 | d_cmds.push_back(arg); | |
c63f0951 AT |
330 | |
331 | boost::trim(var); | |
12c86877 BH |
332 | |
333 | if(var!="" && (parseOnly.empty() || var==parseOnly)) { | |
bd11bd1d | 334 | pos=val.find_first_not_of(" \t"); // strip leading whitespace |
3752660e | 335 | if(pos && pos!=string::npos) |
bd11bd1d | 336 | val=val.substr(pos); |
4d752b05 KM |
337 | if(parmIsset(var)) |
338 | { | |
339 | if(incremental) | |
340 | { | |
341 | if(params[var].empty()) | |
342 | { | |
343 | if(!d_cleared.count(var)) | |
344 | throw ArgException("Incremental parameter '"+var+"' without a parent"); | |
345 | params[var]=val; | |
346 | } | |
347 | else | |
348 | params[var]+=", " + val; | |
349 | } | |
350 | else | |
351 | { | |
352 | params[var]=val; | |
353 | d_cleared.insert(var); | |
5a177409 | 354 | } |
3702b428 | 355 | } |
4d752b05 | 356 | else if(!lax) |
a67dd0cf | 357 | throw ArgException("Trying to set unknown parameter '"+var+"'"); |
12c86877 BH |
358 | } |
359 | } | |
360 | ||
361 | const vector<string>&ArgvMap::getCommands() | |
362 | { | |
363 | return d_cmds; | |
364 | } | |
365 | ||
366 | void ArgvMap::parse(int &argc, char **argv, bool lax) | |
367 | { | |
116cda91 | 368 | d_cmds.clear(); |
58a57699 | 369 | d_cleared.clear(); |
12c86877 BH |
370 | for(int n=1;n<argc;n++) { |
371 | parseOne(argv[n],"",lax); | |
372 | } | |
373 | } | |
374 | ||
375 | void ArgvMap::preParse(int &argc, char **argv, const string &arg) | |
376 | { | |
377 | for(int n=1;n<argc;n++) { | |
378 | string varval=argv[n]; | |
379 | if(!varval.find("--"+arg)) | |
380 | parseOne(argv[n]); | |
381 | } | |
382 | } | |
383 | ||
d3002a04 AT |
384 | bool ArgvMap::parseFile(const char *fname, const string& arg, bool lax) { |
385 | string line; | |
07b7e0b5 AT |
386 | string pline; |
387 | string::size_type pos; | |
388 | ||
d3002a04 AT |
389 | ifstream f(fname); |
390 | if(!f) | |
391 | return false; | |
07b7e0b5 | 392 | |
d3002a04 AT |
393 | while(getline(f,pline)) { |
394 | trim_right(pline); | |
395 | ||
83281a74 | 396 | if(!pline.empty() && pline[pline.size()-1]=='\\') { |
d3002a04 AT |
397 | line+=pline.substr(0,pline.length()-1); |
398 | continue; | |
399 | } | |
400 | else | |
401 | line+=pline; | |
402 | ||
403 | // strip everything after a # | |
404 | if((pos=line.find("#"))!=string::npos) { | |
405 | // make sure it's either first char or has whitespace before | |
406 | // fixes issue #354 | |
407 | if (pos == 0 || std::isspace(line[pos-1])) | |
408 | line=line.substr(0,pos); | |
409 | } | |
410 | ||
411 | // strip trailing spaces | |
412 | trim_right(line); | |
07b7e0b5 | 413 | |
d3002a04 AT |
414 | // strip leading spaces |
415 | if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos) | |
416 | line=line.substr(pos); | |
07b7e0b5 | 417 | |
d3002a04 AT |
418 | // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd |
419 | ||
420 | parseOne( string("--") + line, arg, lax ); | |
421 | line=""; | |
422 | } | |
07b7e0b5 | 423 | |
07b7e0b5 AT |
424 | return true; |
425 | } | |
426 | ||
427 | ||
f64de501 | 428 | bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault) |
12c86877 | 429 | { |
f64de501 | 430 | params[arg]=theDefault; |
b56ac6fd | 431 | |
d3002a04 | 432 | return parseFile(fname, arg, false); |
12c86877 BH |
433 | } |
434 | ||
12c86877 | 435 | bool ArgvMap::file(const char *fname, bool lax) |
4f6ef75f AT |
436 | { |
437 | return file(fname,lax,false); | |
438 | } | |
439 | ||
440 | bool ArgvMap::file(const char *fname, bool lax, bool included) | |
12c86877 | 441 | { |
8a16d778 | 442 | if (!parmIsset("include-dir")) // inject include-dir |
4f6ef75f AT |
443 | set("include-dir","Directory to include configuration files from"); |
444 | ||
d3002a04 AT |
445 | if(!parseFile(fname, "", lax)) { |
446 | L << Logger::Warning << "Unable to open " << fname << std::endl; | |
447 | return false; | |
448 | } | |
eefd15f9 | 449 | |
4f6ef75f | 450 | // handle include here (avoid re-include) |
8a16d778 | 451 | if (!included && !params["include-dir"].empty()) { |
93d7e233 AT |
452 | std::vector<std::string> extraConfigs; |
453 | gatherIncludes(extraConfigs); | |
ef7cd021 | 454 | for(const std::string& fn : extraConfigs) { |
93d7e233 AT |
455 | if (!file(fn.c_str(), lax, true)) { |
456 | L << Logger::Error << fn << " could not be parsed" << std::endl; | |
457 | throw ArgException(fn + " could not be parsed"); | |
4f6ef75f | 458 | } |
93d7e233 | 459 | } |
93d7e233 | 460 | } |
8a16d778 | 461 | |
93d7e233 AT |
462 | return true; |
463 | } | |
8a16d778 | 464 | |
93d7e233 AT |
465 | void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) { |
466 | extraConfigs.clear(); | |
467 | if (params["include-dir"].empty()) return; // nothing to do | |
468 | struct stat st; | |
469 | DIR *dir; | |
470 | struct dirent *ent; | |
471 | ||
472 | // stat | |
473 | if (stat(params["include-dir"].c_str(), &st)) { | |
474 | L << Logger::Error << params["include-dir"] << " does not exist!" << std::endl; | |
475 | throw ArgException(params["include-dir"] + " does not exist!"); | |
476 | } | |
da748130 | 477 | |
93d7e233 AT |
478 | // wonder if it's accessible directory |
479 | if (!S_ISDIR(st.st_mode)) { | |
480 | L << Logger::Error << params["include-dir"] << " is not a directory" << std::endl; | |
481 | throw ArgException(params["include-dir"] + " is not a directory"); | |
482 | } | |
4f6ef75f | 483 | |
93d7e233 AT |
484 | if (!(dir = opendir(params["include-dir"].c_str()))) { |
485 | L << Logger::Error << params["include-dir"] << " is not accessible" << std::endl; | |
486 | throw ArgException(params["include-dir"] + " is not accessible"); | |
487 | } | |
488 | ||
489 | while((ent = readdir(dir)) != NULL) { | |
490 | if (ent->d_name[0] == '.') continue; // skip any dots | |
491 | if (boost::ends_with(ent->d_name, ".conf")) { | |
492 | // build name | |
493 | std::ostringstream namebuf; | |
494 | namebuf << params["include-dir"].c_str() << "/" << ent->d_name; // FIXME: Use some path separator | |
495 | // ensure it's readable file | |
496 | if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) { | |
497 | L << Logger::Error << namebuf.str() << " is not a file" << std::endl; | |
498 | throw ArgException(namebuf.str() + " does not exist!"); | |
499 | } | |
500 | extraConfigs.push_back(namebuf.str()); | |
501 | } | |
502 | } | |
503 | std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX()); | |
829849d6 | 504 | closedir(dir); |
12c86877 | 505 | } |