-rw-r--r-- | lib/configuration.cc | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/lib/configuration.cc b/lib/configuration.cc new file mode 100644 index 0000000..4ee1526 --- a/dev/null +++ b/lib/configuration.cc @@ -0,0 +1,474 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <unistd.h> + #include <fnmatch.h> + #include <cassert> + #include <stdexcept> + using namespace std; + #include <dotconf.h> + #include "sitecing/configuration.h" + #include "sitecing/sitecing_util.h" + #include "sitecing/scoreboard.h" +#endif + +namespace sitecing { + + configuration::configuration() + : flags(0), autobuild(false) { } + configuration::configuration(const string& cfile,bool ab) + : flags(0), autobuild(ab) { + parse(cfile); + } + + enum dc_ctx { + DCC_ROOT = 1, + DCC_PATH = 2, + DCC_SCRC = 4 + }; + struct dc_context { + dc_ctx ctx; + configuration* cf; + list<config_options*> co; + }; + + static DOTCONF_CB(dco_root_source) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->root_source = cmd->data.str; + dcc->cf->flags |= configuration::flag_root_source; + return NULL; + } + static DOTCONF_CB(dco_root_intermediate) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->root_intermediate = cmd->data.str; + dcc->cf->flags |= configuration::flag_root_intermediate; + return NULL; + } + static DOTCONF_CB(dco_root_so) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->root_so = cmd->data.str; + dcc->cf->flags |= configuration::flag_root_so; + return NULL; + } + static DOTCONF_CB(dco_listen_socket) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->listen_socket = cmd->data.str; + dcc->cf->flags |= configuration::flag_listen_socket; + return NULL; + } + static DOTCONF_CB(dco_rc_file_name) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->rc_file_name = cmd->data.str; + dcc->cf->flags |= configuration::flag_rc_file_name; + return NULL; + } + static DOTCONF_CB(dco_min_children) { dc_context *dcc = (dc_context*)ctx; + if(cmd->data.value>=MAX_SITECING_SCOREBOARD_SLOTS) + return "MinChildren is too big"; + dcc->cf->min_children = cmd->data.value; + dcc->cf->flags |= configuration::flag_min_children; + return NULL; + } + static DOTCONF_CB(dco_max_children) { dc_context *dcc = (dc_context*)ctx; + if(cmd->data.value>=MAX_SITECING_SCOREBOARD_SLOTS) + return "MaxChildren is too big"; + dcc->cf->max_children = cmd->data.value; + dcc->cf->flags |= configuration::flag_max_children; + return NULL; + } + static DOTCONF_CB(dco_min_spare_children) { dc_context *dcc = (dc_context*)ctx; + if(cmd->data.value>=MAX_SITECING_SCOREBOARD_SLOTS) + return "MinSpareChildren is too big"; + dcc->cf->min_spare_children = cmd->data.value; + dcc->cf->flags |= configuration::flag_min_spare_children; + return NULL; + } + static DOTCONF_CB(dco_max_spare_children) { dc_context *dcc = (dc_context*)ctx; + if(cmd->data.value>=MAX_SITECING_SCOREBOARD_SLOTS) + return "MaxSpareChildren is too big"; + dcc->cf->max_spare_children = cmd->data.value; + dcc->cf->flags |= configuration::flag_max_spare_children; + return NULL; + } + static DOTCONF_CB(dco_requests_per_child) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->requests_per_child = cmd->data.value; + dcc->cf->flags |= configuration::flag_requests_per_child; + return NULL; + } + static DOTCONF_CB(dco_multi_process) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->multi_process = cmd->data.value; + dcc->cf->flags |= configuration::flag_multi_process; + return NULL; + } + static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->user = cmd->data.str; + dcc->cf->flags |= configuration::flag_user; + return NULL; + } + static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->group = cmd->data.str; + dcc->cf->flags |= configuration::flag_group; + return NULL; + } + static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->chroot = cmd->data.str; + dcc->cf->flags |= configuration::flag_chroot; + return NULL; + } + static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->pid_file = cmd->data.str; + dcc->cf->flags |= configuration::flag_pid_file; + return NULL; + } + static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*) ctx; + dcc->cf->daemonize = cmd->data.value; + dcc->cf->flags |= configuration::flag_daemonize; + return NULL; + } + + static DOTCONF_CB(dco_path) { dc_context *dcc = (dc_context*)ctx; + string path = cmd->data.str; + if(path[path.length()-1]=='>') + path.erase(path.length()-1); + // TODO: normalize path + dcc->co.push_front(&(dcc->cf->specs[path])); + dcc->ctx = DCC_PATH; // TODO: stack it, instead + return NULL; + } + static DOTCONF_CB(dco__path) { dc_context *dcc = (dc_context*)ctx; + dcc->co.pop_front(); + assert(dcc->co.size()); + dcc->ctx = DCC_ROOT; // TODO: stack it, instead + return NULL; + } + + static DOTCONF_CB(dco_skeleton) { dc_context *dcc = (dc_context*)ctx; + dcc->co.front()->skeleton = cmd->data.str; + dcc->co.front()->flags |= config_options::flag_skeleton; + return NULL; + } + static DOTCONF_CB(dco_cpp_flags) { dc_context *dcc = (dc_context*)ctx; + for(char **arg=cmd->data.list;*arg;arg++) + dcc->co.front()->cpp_flags.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_cpp_flags; + return NULL; + } + static DOTCONF_CB(dco_ld_flags) { dc_context *dcc = (dc_context*)ctx; + for(char **arg=cmd->data.list;*arg;arg++) + dcc->co.front()->ld_flags.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_ld_flags; + return NULL; + } + static DOTCONF_CB(dco_intermediate_deps) { dc_context *dcc = (dc_context*) ctx; + for(char **arg=cmd->data.list;*arg;arg++) + dcc->co.front()->intermediate_deps.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_intermediate_deps; + return NULL; + } + static DOTCONF_CB(dco_so_deps) { dc_context *dcc = (dc_context*) ctx; + for(char **arg=cmd->data.list;*arg;arg++) + dcc->co.front()->so_deps.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_so_deps; + return NULL; + } + static DOTCONF_CB(dco_build) { dc_context *dcc = (dc_context*)ctx; + dcc->co.front()->build = cmd->data.value; + dcc->co.front()->flags |= config_options::flag_build; + return NULL; + } + static DOTCONF_CB(dco_cpp_deps) { dc_context *dcc = (dc_context*)ctx; + dcc->co.front()->cpp_deps = cmd->data.value; + dcc->co.front()->flags |= config_options::flag_cpp_deps; + return NULL; + } + static DOTCONF_CB(dco_exception_handler) { dc_context *dcc = (dc_context*)ctx; + dcc->co.front()->exception_handler = cmd->data.str; + dcc->co.front()->flags |= config_options::flag_exception_handler; + return NULL; + } + static DOTCONF_CB(dco_http_status_handler) { dc_context *dcc = (dc_context*)ctx; + if(cmd->arg_count!=2) + return "Invalid number of arguments"; + dcc->co.front()->http_status_handlers[cmd->data.list[0]] = cmd->data.list[1]; + dcc->co.front()->flags |= config_options::flag_http_status_handlers; + return NULL; + } + static DOTCONF_CB(dco_action) { dc_context *dcc = (dc_context*)ctx; + if(cmd->arg_count<2) + return "Invalid number of arguments"; + try { + char **arg=cmd->data.list; + dcc->co.front()->action_handlers.push_back(config_options::action_handler_t(arg[0],arg[1])); + for(arg+=2;*arg;arg++) + dcc->co.front()->action_handlers.back().args.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_action_handlers; + }catch(exception& e) { + return "Error processing Action directive"; // XXX: could be done better + } + return NULL; + } + static DOTCONF_CB(dco_auto_build_files) { dc_context *dcc = (dc_context*)ctx; + if(!( dcc->cf && dcc->cf->autobuild)) + return NULL; + for(char **arg=cmd->data.list;*arg;arg++) + dcc->co.front()->auto_build_files.push_back(*arg); + dcc->co.front()->flags |= config_options::flag_auto_build_files; + return NULL; + } + + static const configoption_t dc_options[] = { + { "RootSource", ARG_STR, dco_root_source, NULL, DCC_ROOT }, + { "RootIntermediate", ARG_STR, dco_root_intermediate, NULL, DCC_ROOT }, + { "RootSO", ARG_STR, dco_root_so, NULL, DCC_ROOT }, + { "ListenSocket", ARG_STR, dco_listen_socket, NULL, DCC_ROOT }, + { "RCFileName", ARG_STR, dco_rc_file_name, NULL, DCC_ROOT }, + { "MinChildren", ARG_INT, dco_min_children, NULL, DCC_ROOT }, + { "MaxChildren", ARG_INT, dco_max_children, NULL, DCC_ROOT }, + { "MinSpareChildren", ARG_INT, dco_min_spare_children, NULL, DCC_ROOT }, + { "MaxSpareChildren", ARG_INT, dco_max_spare_children, NULL, DCC_ROOT }, + { "RequestsPerChild", ARG_INT, dco_requests_per_child, NULL, DCC_ROOT }, + { "MultiProcess", ARG_TOGGLE, dco_multi_process, NULL, DCC_ROOT }, + { "User", ARG_STR, dco_user, NULL, DCC_ROOT }, + { "Group", ARG_STR, dco_group, NULL, DCC_ROOT }, + { "Chroot", ARG_STR, dco_chroot, NULL, DCC_ROOT }, + { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT }, + { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT }, + { "<Path", ARG_STR, dco_path, NULL, DCC_ROOT }, + { "Skeleton", ARG_STR, dco_skeleton, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "CPPFLAGS", ARG_LIST, dco_cpp_flags, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "LDFLAGS", ARG_LIST, dco_ld_flags, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "Build", ARG_TOGGLE, dco_build, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "CPPDeps", ARG_TOGGLE, dco_cpp_deps, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "ExceptionHandler", ARG_STR, dco_exception_handler, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "HTTPStatusHandler", ARG_LIST, dco_http_status_handler, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "IntermediateDeps", ARG_LIST, dco_intermediate_deps, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "SODeps", ARG_LIST, dco_so_deps, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "Action", ARG_LIST, dco_action, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "AutoBuildFiles", ARG_LIST, dco_auto_build_files, NULL, DCC_ROOT|DCC_PATH|DCC_SCRC }, + { "</Path>", ARG_NONE, dco__path, NULL, DCC_PATH }, + LAST_OPTION + }; + + static const char *dc_context_checker(command_t *cmd,unsigned long mask) { + dc_context *dcc = (dc_context*)cmd->context; + if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) ) + return NULL; + return "misplaced option"; + } + static FUNC_ERRORHANDLER(dc_error_handler) { + throw konforka::exception(CODEPOINT,string("error parsing config file: ")+msg); + } + + bool loaded_options::is_valid() { + struct stat nst; + if(stat(source_file.c_str(),&nst)) + return false; + if(st.st_mtime!=nst.st_mtime) + return false; + return true; + } + + loaded_options *configuration::lookup_loaded_options(const string& target) { + // we assume 'target' is a directory with trailing slash appended + string scrc = root_source+target; + if(flags&flag_rc_file_name) + scrc += rc_file_name; + else + scrc += ".scrc"; + // TODO: normalize me, anyway. + if(access(scrc.c_str(),R_OK)) + return 0; // TODO FIXME: this approach leaves already loaded .scrcs around in case of removal + loaded_specs_t::iterator i = loaded_specs.find(target); + if(i==loaded_specs.end() || !i->second.is_valid()) { + if(i!=loaded_specs.end()) + loaded_specs.erase(i); + pair<loaded_specs_t::iterator,bool> ii = loaded_specs.insert(loaded_specs_t::value_type(target,loaded_options())); + assert(ii.first!=loaded_specs.end()); + ii.first->second.parse(this,scrc); + i = ii.first; + } + assert(i!=loaded_specs.end()); + return &(i->second); + } + + config_options::action_handler_t *config_options::lookup_action_handler(const string& target) { + for(action_handlers_t::iterator i=action_handlers.begin();i!=action_handlers.end();++i) { + if(i->regex.search(target)) + return &*i; + } + return NULL; + } + + string config_options::lookup_http_status_handler(const string& status) { + http_status_handlers_t::const_iterator i = http_status_handlers.find(status); + string rv; + if(i!=http_status_handlers.end()) + rv = i->second; + return rv; + } + + string configuration::lookup_http_status_handler(const string& target,const string& status) { + string t = "/"; + t += normalize_path(target,strip_leading_slash); + string rv; + for(;;) { + if(t[t.length()-1]=='/') { + loaded_options* lo = lookup_loaded_options(t); + if( lo && (lo->flags&config_options::flag_http_status_handlers) ) { + rv = lo->lookup_http_status_handler(status); + if(!rv.empty()) + return rv; + } + } + specs_t::iterator i = specs.find(t); + if( i!=specs.end() && (i->second.flags&&config_options::flag_http_status_handlers) ) { + rv = i->second.lookup_http_status_handler(status); + if(!rv.empty()) + return rv; + } + if(t.empty()) + return rv; + string::size_type sl=t.rfind('/'); + if(sl==string::npos) { + t.erase(); + }else{ + if(sl==(t.length()-1)) + t.erase(sl); + else + t.erase(sl+1); + } + } + } + + config_options::action_handler_t *configuration::lookup_action_handler(const string& target) { + string t = "/"; + t += normalize_path(target,strip_leading_slash); + for(;;) { + if(t[t.length()-1]=='/') { + loaded_options* lo = lookup_loaded_options(t); + if( lo && (lo->flags&config_options::flag_action_handlers) ) { + config_options::action_handler_t *rv = lo->lookup_action_handler(target); + if(rv) + return rv; + } + } + specs_t::iterator i = specs.find(t); + if( i!=specs.end() && (i->second.flags&&config_options::flag_action_handlers) ) { + config_options::action_handler_t *rv = i->second.lookup_action_handler(target); + if(rv) + return rv; + } + if(t.empty()) + return NULL; + string::size_type sl=t.rfind('/'); + if(sl==string::npos) { + t.erase(); + }else{ + if(sl==(t.length()-1)) + t.erase(sl); + else + t.erase(sl+1); + } + } + } + + config_options* configuration::lookup_config(const string& target,int flag) { + string t = "/"; // always assume leading slash + t += normalize_path(target,strip_leading_slash); + // XXX: reconsider precedence + for(;;) { + if(t[t.length()-1]=='/') { + loaded_options* lo = lookup_loaded_options(t); + if( lo && (lo->flags&flag)==flag ) + return lo; + } + specs_t::iterator i = specs.find(t); + if( i!=specs.end() && (i->second.flags&flag)==flag ) + return &(i->second); + if(t.empty()) + return NULL; + string::size_type sl=t.rfind('/'); + if(sl==string::npos) { + t.erase(); + }else{ + if(sl==(t.length()-1)) + t.erase(sl); + else + t.erase(sl+1); + } + } + } + + bool config_options::match_autobuild_files(const char *fn,bool &rv) { + for(list<string>::reverse_iterator i=auto_build_files.rbegin();i!=auto_build_files.rend();++i) { + const char *pat = i->c_str(); + bool plus = true; + if((*pat)=='+') + pat++; + else if((*pat)=='-') { + plus = false; + pat++; + } + if(!fnmatch(pat,fn,FNM_NOESCAPE|FNM_PATHNAME|FNM_PERIOD)) { + rv = plus; + return true; + } + } + return false; + } + + bool configuration::match_autobuild_files(const string& target,const char *fn) { + string t = "/"; + t += normalize_path(target,strip_leading_slash|strip_trailing_slash); + t += "/"; + bool rv = false; + for(;;) { + if(t[t.length()-1]=='/') { + loaded_options* lo = lookup_loaded_options(t); + if(lo && (lo->flags&config_options::flag_auto_build_files) && lo->match_autobuild_files(fn,rv) ) + return rv; + } + specs_t::iterator i = specs.find(t); + if( i!=specs.end() && (i->second.flags&config_options::flag_auto_build_files) && i->second.match_autobuild_files(fn,rv) ) + return rv; + if(t.empty()) + return rv; + string::size_type sl=t.rfind('/'); + if(sl==string::npos) { + t.erase(); + }else{ + if(sl==(t.length()-1)) + t.erase(sl); + else + t.erase(sl+1); + } + } + } + + void configuration::parse(const string& cfile) { + struct dc_context dcc; + dcc.cf = this; + dcc.ctx = DCC_ROOT; + dcc.co.push_front(&root_options()); + configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE); + if(!cf) + throw konforka::exception(CODEPOINT,"failed to dotconf_create()"); + cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler; + cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker; + if(!dotconf_command_loop(cf)) + throw konforka::exception(CODEPOINT,"failed to dotconf_command_loop()"); + dotconf_cleanup(cf); + } + + void loaded_options::parse(configuration *config,const string& cfile) { + struct dc_context dcc; + dcc.cf = config; + dcc.ctx = DCC_SCRC; + dcc.co.push_front(this); + configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE); + if(!cf) + throw konforka::exception(CODEPOINT,"failed to dotconf_create()"); + cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler; + cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker; + if(!dotconf_command_loop(cf)) + throw konforka::exception(CODEPOINT,"failed to dotconf_command_loop()"); + dotconf_cleanup(cf); + source_file = cfile; + stat(cfile.c_str(),&st); // TODO: handle errors? + } +} |