]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/arguments.cc
Merge pull request #2147 from cyclops1982/autoserial
[thirdparty/pdns.git] / pdns / arguments.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2008 PowerDNS.COM BV
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as published
7 by the Free Software Foundation
8
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
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
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "arguments.hh"
26 #include <boost/algorithm/string.hpp>
27 #include <boost/algorithm/string/compare.hpp>
28 #include <boost/algorithm/string/predicate.hpp>
29 #include <boost/foreach.hpp>
30 #include "namespaces.hh"
31 #include "logger.hh"
32 #include <sys/types.h>
33 #include <dirent.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <limits.h>
37
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)
54 {
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
93 bool ArgvMap::contains(const string &var, const string &val)
94 {
95 params_t::const_iterator param = params.find(var);
96 if(param == params.end() || param->second.empty()) {
97 return false;
98 }
99 vector<string> parts;
100 vector<string>::const_iterator i;
101
102 stringtok( parts, param->second, ", \t" );
103 for( i = parts.begin(); i != parts.end(); i++ ) {
104 if( *i == val ) {
105 return true;
106 }
107 }
108
109 return false;
110 }
111
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
124 continue;
125
126 help+=" --";
127 help+=i->first;
128
129 string type=d_typeMap[i->first];
130
131 if(type=="Parameter")
132 help+="=...";
133 else if(type=="Switch")
134 {
135 help+=" | --"+i->first+"=yes";
136 help+=" | --"+i->first+"=no";
137 }
138
139
140 help+="\n\t";
141 help+=i->second;
142 help+="\n";
143
144 }
145 return help;
146 }
147
148 string ArgvMap::configstring(bool current)
149 {
150 string help;
151
152 if (current)
153 help="# Autogenerated configuration file based on running instance\n";
154 else
155 help="# Autogenerated configuration file template\n";
156
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";
171 }
172 }
173 return help;
174 }
175
176 const string & ArgvMap::operator[](const string &arg)
177 {
178 if(!parmIsset(arg))
179 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
180
181 return params[arg];
182 }
183
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 }
241
242 int ArgvMap::asNum(const string &arg, int def)
243 {
244 int retval;
245 const char *cptr_orig;
246 char *cptr_ret = NULL;
247
248 if(!parmIsset(arg))
249 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
250
251 // use default for empty values
252 if (params[arg].empty())
253 return def;
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();
268 }
269
270 double ArgvMap::asDouble(const string &arg)
271 {
272 double retval;
273 const char *cptr_orig;
274 char *cptr_ret = NULL;
275
276 if(!parmIsset(arg))
277 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
278
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;
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;
304 string::size_type pos;
305 bool incremental = false;
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 }
318 else if(!arg.find("--") && (arg.find("=")==string::npos)) // this is a --daemon case
319 {
320 var=arg.substr(2);
321 val="";
322 }
323 else if(arg[0]=='-')
324 {
325 var=arg.substr(1);
326 val="";
327 }
328 else // command
329 d_cmds.push_back(arg);
330
331 boost::trim(var);
332
333 if(var!="" && (parseOnly.empty() || var==parseOnly)) {
334 pos=val.find_first_not_of(" \t"); // strip leading whitespace
335 if(pos && pos!=string::npos)
336 val=val.substr(pos);
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);
354 }
355 }
356 else if(!lax)
357 throw ArgException("Trying to set unknown parameter '"+var+"'");
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 {
368 d_cmds.clear();
369 d_cleared.clear();
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
384 bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
385 {
386 params[arg]=theDefault;
387
388 ifstream f(fname);
389 if(!f)
390 return false;
391
392 string line;
393 string pline;
394 string::size_type pos;
395
396 while(getline(f,pline)) {
397 trim_right(pline);
398
399 if(pline[pline.size()-1]=='\\') {
400 line+=pline.substr(0,pline.length()-1);
401 continue;
402 }
403 else
404 line+=pline;
405
406 // strip everything after a #
407 if((pos=line.find("#"))!=string::npos)
408 line=line.substr(0,pos);
409
410 // strip trailing spaces
411 trim_right(line);
412
413 // strip leading spaces
414 if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos)
415 line=line.substr(pos);
416
417 // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
418
419 parseOne( string("--") + line, arg );
420 line="";
421 }
422
423 return true;
424 }
425
426 bool ArgvMap::file(const char *fname, bool lax)
427 {
428 return file(fname,lax,false);
429 }
430
431 bool ArgvMap::file(const char *fname, bool lax, bool included)
432 {
433 ifstream f(fname);
434 if(!f) {
435 L << Logger::Warning << "Unable to open " << fname << std::endl;
436 return false;
437 }
438
439 if (!parmIsset("include-dir")) // inject include-dir
440 set("include-dir","Directory to include configuration files from");
441
442 string line;
443 string pline;
444 string::size_type pos;
445
446 while(getline(f,pline)) {
447 trim_right(pline);
448 if(pline.empty())
449 continue;
450
451 if(pline[pline.size()-1]=='\\') {
452 line+=pline.substr(0,pline.length()-1);
453
454 continue;
455 }
456 else
457 line+=pline;
458
459 // strip everything after a #
460 if((pos=line.find("#"))!=string::npos)
461 line=line.substr(0,pos);
462
463 // strip trailing spaces
464 trim(line);
465
466 parseOne(string("--")+line,"",lax);
467 line="";
468 }
469
470 // handle include here (avoid re-include)
471 if (!included && !params["include-dir"].empty()) {
472 std::vector<std::string> extraConfigs;
473 gatherIncludes(extraConfigs);
474 BOOST_FOREACH(const std::string& fn, extraConfigs) {
475 if (!file(fn.c_str(), lax, true)) {
476 L << Logger::Error << fn << " could not be parsed" << std::endl;
477 throw ArgException(fn + " could not be parsed");
478 }
479 }
480 }
481
482 return true;
483 }
484
485 void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
486 extraConfigs.clear();
487 if (params["include-dir"].empty()) return; // nothing to do
488 struct stat st;
489 DIR *dir;
490 struct dirent *ent;
491
492 // stat
493 if (stat(params["include-dir"].c_str(), &st)) {
494 L << Logger::Error << params["include-dir"] << " does not exist!" << std::endl;
495 throw ArgException(params["include-dir"] + " does not exist!");
496 }
497
498 // wonder if it's accessible directory
499 if (!S_ISDIR(st.st_mode)) {
500 L << Logger::Error << params["include-dir"] << " is not a directory" << std::endl;
501 throw ArgException(params["include-dir"] + " is not a directory");
502 }
503
504 if (!(dir = opendir(params["include-dir"].c_str()))) {
505 L << Logger::Error << params["include-dir"] << " is not accessible" << std::endl;
506 throw ArgException(params["include-dir"] + " is not accessible");
507 }
508
509 while((ent = readdir(dir)) != NULL) {
510 if (ent->d_name[0] == '.') continue; // skip any dots
511 if (boost::ends_with(ent->d_name, ".conf")) {
512 // build name
513 std::ostringstream namebuf;
514 namebuf << params["include-dir"].c_str() << "/" << ent->d_name; // FIXME: Use some path separator
515 // ensure it's readable file
516 if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) {
517 L << Logger::Error << namebuf.str() << " is not a file" << std::endl;
518 throw ArgException(namebuf.str() + " does not exist!");
519 }
520 extraConfigs.push_back(namebuf.str());
521 }
522 }
523 std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX());
524 closedir(dir);
525 }