-rw-r--r-- | lib/.gitignore | 9 | ||||
-rw-r--r-- | lib/Makefile.am | 22 | ||||
-rw-r--r-- | lib/acomponent.cc | 47 | ||||
-rw-r--r-- | lib/cgi_component.cc | 30 | ||||
-rw-r--r-- | lib/component_factory.cc | 279 | ||||
-rw-r--r-- | lib/component_so.cc | 112 | ||||
-rw-r--r-- | lib/configuration.cc | 474 | ||||
-rw-r--r-- | lib/file_factory.cc | 55 | ||||
-rw-r--r-- | lib/pch.h | 45 | ||||
-rw-r--r-- | lib/process_manager.cc | 152 | ||||
-rw-r--r-- | lib/scoreboard.cc | 71 | ||||
-rw-r--r-- | lib/sitecing_enflesher.ll | 202 | ||||
-rw-r--r-- | lib/sitecing_interface_cgi.cc | 25 | ||||
-rw-r--r-- | lib/sitecing_parser.ll | 594 | ||||
-rw-r--r-- | lib/sitecing_util.cc | 278 | ||||
-rw-r--r-- | lib/sitespace.cc | 52 | ||||
-rw-r--r-- | lib/util.cc | 83 |
17 files changed, 2530 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..8137060 --- a/dev/null +++ b/lib/.gitignore @@ -0,0 +1,9 @@ +Makefile.in +sitecing_enflesher.cc +sitecing_parser.cc +.libs +.deps +Makefile +*.o +*.lo +*.la diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..53d8182 --- a/dev/null +++ b/lib/Makefile.am @@ -0,0 +1,22 @@ +lib_LTLIBRARIES = libsitecing.la +noinst_HEADERS = pch.h + +INCLUDES = -I${top_srcdir}/include -I${top_builddir} ${KINGATE_CFLAGS} ${DOTCONF_CFLAGS} \ + ${PCREPP_CFLAGS} +AM_CPPFLAGS = -D__SC_DEFAULT_SKELETON=\"${pkgdatadir}/component.skel\" +AM_LFLAGS = -olex.yy.c + +libsitecing_la_SOURCES = \ + sitecing_parser.ll sitecing_enflesher.ll \ + sitecing_interface_cgi.cc \ + acomponent.cc \ + cgi_component.cc \ + component_so.cc \ + file_factory.cc component_factory.cc \ + sitespace.cc \ + configuration.cc \ + util.cc sitecing_util.cc \ + scoreboard.cc \ + process_manager.cc +libsitecing_la_LDFLAGS = \ + -version-info 1:0:0 diff --git a/lib/acomponent.cc b/lib/acomponent.cc new file mode 100644 index 0000000..8dfeee4 --- a/dev/null +++ b/lib/acomponent.cc @@ -0,0 +1,47 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <cstdarg> + #include <fstream> + #include <konforka/exception.h> + using namespace std; + #include "sitecing/acomponent.h" +#endif + +namespace sitecing { + + acomponent::acomponent() + : __SCIF(NULL) { + } + acomponent::~acomponent() { + } + + void acomponent::__set_interface(sitecing_interface* scif) { + sitecing_interface *o = __SCIF; + __SCIF = scif; + if(o!=scif) { + __on_change_interface(o); + __do_imports(); + __on_imports(); + } + } + + void acomponent::__on_change_interface(sitecing_interface *oscif) { } + void acomponent::__do_imports() { } + void acomponent::__on_imports() { } + + void acomponent::run(int _magic,...) { + va_list va; + va_start(va,_magic); + main(_magic,va); + va_end(va); + } + + + void acomponent::pass_file_through(const char *fn) { + ifstream ifs(fn,ios::in|ios::binary); + if(!ifs) + throw konforka::exception(CODEPOINT,"failed to open file"); + (*(__SCIF->out)) << ifs.rdbuf(); + } +} diff --git a/lib/cgi_component.cc b/lib/cgi_component.cc new file mode 100644 index 0000000..b5c4bee --- a/dev/null +++ b/lib/cgi_component.cc @@ -0,0 +1,30 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include "sitecing/cgi_component.h" +#endif + +namespace sitecing { + + cgi_component::cgi_component() + : __CGI(NULL) { + } + cgi_component::~cgi_component() { + } + + void cgi_component::__set_interface(sitecing_interface* scif) { + acomponent::__set_interface(scif); + kingate::cgi_gateway *oc = __CGI; + __CGI = __SCIF?__SCIF->cgigw:NULL; + if(__CGI!=oc) + __on_change_CGI(oc); + } + void cgi_component::__on_change_interface(sitecing_interface *o) { + acomponent::__on_change_interface(o); // But it's a no-op + // TODO: do something about runtime type check, maybe? + __SCIF = (sitecing_interface_cgi*)acomponent::__SCIF; + } + void cgi_component::__on_change_CGI(kingate::cgi_gateway *o) { } + void cgi_component::__on_imports() { } + +} diff --git a/lib/component_factory.cc b/lib/component_factory.cc new file mode 100644 index 0000000..bcf19f2 --- a/dev/null +++ b/lib/component_factory.cc @@ -0,0 +1,279 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <sys/types.h> + #include <sys/stat.h> + #include <unistd.h> + #include <sys/wait.h> + #include <fcntl.h> + #include <iostream> + #include <fstream> + #include <stdexcept> + #include <vector> + using namespace std; + #include "sitecing/component_factory.h" + #include "sitecing/sitecing_util.h" + #include "sitecing/sitecing_parser.h" + #include "sitecing/sitecing_exception.h" +#endif + +namespace sitecing { + + static const char *pp_targets[] = { ".cc", ".h", ".imports", ".classname", ".baseclassname", ".ancestors" }; + + component_factory::component_factory(configuration& c) + : config(c), + root_source(normalize_path(c.root_source,strip_trailing_slash)+'/'), + root_intermediate(normalize_path(c.root_intermediate,strip_trailing_slash)+'/'), + root_so(normalize_path(c.root_so,strip_trailing_slash)+'/') { + } + + void component_factory::get_dependencies(const string& dst,file_list_t& deps) { + deps.clear(); + string dp = normalize_path(dst,strip_trailing_slash); + // source documents + try { // XXX: or just compare it off? + string noro = strip_prefix(dp,root_source); + return; + }catch(utility_no_affix& una) { + } + // .so binaries + try { + string noso = strip_suffix(dp,".so"); + string noro = strip_prefix(noso,root_so); + deps.push_back(root_intermediate+noro+".cc"); + config_options *co_cpp_deps = config.lookup_config(noro,config_options::flag_cpp_deps); + if( (!co_cpp_deps) || co_cpp_deps->cpp_deps) { + ifstream df((root_intermediate+noro+".d").c_str(),ios::in); + if(df.good()) { + string str; + while(!df.eof()) { + df >> str; + if(str.find_first_of("\\:")==string::npos) + deps.push_back(combine_path(config.root_source+noro,str)); + } + } + } + config_options *co_so_deps = config.lookup_config(noro,config_options::flag_so_deps); + if(co_so_deps) { + for(list<string>::const_iterator i=co_so_deps->so_deps.begin();i!=co_so_deps->so_deps.end();++i) + deps.push_back(*i); + } + return; + }catch(utility_no_prefix& unp) { + throw konforka::exception(CODEPOINT,"component is outside of component root"); + }catch(utility_no_suffix& uns) { + } + // preprocessor targets + for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) { + try { + string nos = strip_suffix(dp,pp_targets[ppt]); + string noro = strip_prefix(nos,root_intermediate); + deps.push_back(root_source+noro); + ifstream imports((root_intermediate+noro+".imports").c_str(),ios::in); + if(imports.good()) { + string str; + while(!imports.eof()) { + imports >> str; + if(!str.empty()) + deps.push_back(root_intermediate+str+".classname"); + } + } + ifstream ancestors((root_intermediate+noro+".ancestors").c_str(),ios::in); + if(ancestors.good()) { + string str; + while(!ancestors.eof()) { + ancestors >> str; + if(!str.empty()) + deps.push_back(root_intermediate+str+".classname"); + } + } + config_options *co_intermediate_deps = config.lookup_config(noro,config_options::flag_intermediate_deps); + if(co_intermediate_deps) { + for(list<string>::const_iterator i=co_intermediate_deps->intermediate_deps.begin();i!=co_intermediate_deps->intermediate_deps.end();++i) + deps.push_back(*i); + } + return; + }catch(utility_no_affix& una) { + // do nothing. must be a cpp dependency. + } + } + } + + bool component_factory::is_uptodate(const string& dst,file_list_t *deps) { + string dp = normalize_path(dst,strip_trailing_slash); + // XXX: or just compare it off, instead of throwing things around. + try { + strip_prefix(dp,root_intermediate); + return file_factory::is_uptodate(dst,deps); + }catch(utility_no_prefix& unp) { + } + try { + strip_prefix(dp,root_so); + return file_factory::is_uptodate(dst,deps); + }catch(utility_no_prefix& unp) { + } + return true; + } + + void component_factory::build(const string& dst) { + string dp = normalize_path(dst,strip_trailing_slash); + // sources + try { + string noro = strip_prefix(dp,root_source); + // building the sources is left up to developer + return; + }catch(utility_no_prefix& unp) { + } + // .so files + try { + string noso = strip_suffix(dp,".so"); + string noro = strip_prefix(noso,root_so); + string cc = root_intermediate+noro+".cc"; + if(access(cc.c_str(),R_OK)) + throw konforka::exception(CODEPOINT,string("can't access preprocessed component code (")+cc+")"); + make_path(dir_name(root_so+noro),0755); + string pwd = dir_name(root_source+noro); + auto_chdir dir_changer(pwd); + file_lock lock_source(root_intermediate+noro+".lock"); + file_lock lock_so(root_so+noro+".so.lock"); + int stdO = open((root_intermediate+noro+".stdout").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664); + if(stdO<0) + throw konforka::exception(CODEPOINT,"failed to open/create compiler stdout"); + int stdE = open((root_intermediate+noro+".stderr").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664); + if(stdE<0) { + close(stdO); + throw konforka::exception(CODEPOINT,"failed to open/create compiler's stderr"); + } + list<string> args; + config_options *co_cpp_flags = config.lookup_config(noro,config_options::flag_cpp_flags); + if(co_cpp_flags) { + args.insert(args.end(),co_cpp_flags->cpp_flags.begin(),co_cpp_flags->cpp_flags.end()); + } + config_options *co_ld_flags = config.lookup_config(noro,config_options::flag_ld_flags); + if(co_ld_flags) { + args.insert(args.end(),co_ld_flags->ld_flags.begin(),co_ld_flags->ld_flags.end()); + } + // TODO: maybe move it to separare config option like CoreCPPFLags? + args.push_back("-I"+root_intermediate); + args.push_back("-I"+root_source); + args.push_back("-MD"); args.push_back("-MF"); args.push_back(root_intermediate+noro+".d"); + args.push_back("-shared"); + args.push_back("-o"); args.push_back(dp); + args.push_back(cc); + file_list_t ancestors; + get_ancestors(noro,ancestors); + for(file_list_t::const_iterator i=ancestors.begin();i!=ancestors.end();++i) { + string aso=root_so+*i+".so"; + make(aso); + args.push_back(aso); + } + // TODO: "g++" configurable + int rv = execute("g++",args,stdO,stdE); + if(! (WIFEXITED(rv) && !WEXITSTATUS(rv)) ) + throw compile_error(CODEPOINT,"failed to compile component",noro); + return; + }catch(utility_no_prefix& unp) { + throw konforka::exception(CODEPOINT,"component is outside of component root"); + }catch(utility_no_suffix& uns) { + } + // preprocessor targets + for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) { + try { + string nos = strip_suffix(dp,pp_targets[ppt]); + string noro = strip_prefix(nos,root_intermediate); + string src = root_source+noro; + if(access(src.c_str(),R_OK)) + throw konforka::exception(CODEPOINT,string("can't access component source (")+src+")"); + make_path(dir_name(root_intermediate+noro),0755); + file_lock lock(root_intermediate+noro+".lock"); + sitecing_parser parser(*this); + config_options *co_skeleton = config.lookup_config(noro,config_options::flag_skeleton); + if(co_skeleton) + parser.skeleton = co_skeleton->skeleton; + static const char *id_chars = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + parser.class_name = normalize_path(noro,strip_leading_slash|strip_trailing_slash); + for(string::size_type illc = parser.class_name.find_first_not_of(id_chars);illc!=string::npos;illc=parser.class_name.find_first_not_of(id_chars,illc)) { + string::size_type lc = parser.class_name.find_first_of(id_chars,illc); + int n = ((lc==string::npos)?parser.class_name.length():lc)-illc; + parser.class_name.replace(illc,n,n,'_'); + } + parser.class_name = "_SCC_"+parser.class_name; + parser.output_basename = nos; + parser.component_basename = noro; + try { + parser.preprocess(src); + }catch(preprocessor_error& pe) { + pe.component_name = noro; + pe.see(CODEPOINT); + throw; + } + return; + }catch(utility_no_affix& una) { + // must be a crap from .d file + } + } + cerr << "ignoring build request for " << dp << endl; + } + + int component_factory::execute(const string& cmd, const list<string>& args,int stdo,int stde) { + // XXX: is it right that we do stdio/stderr tricks outside of the function? + cerr << "executing: " << cmd; + vector<const char*> argv(args.size()+2); + argv[0]=cmd.c_str(); + int an = 1; + for(list<string>::const_iterator i=args.begin();i!=args.end();i++) { + cerr << " " << *i ; + argv[an++] = i->c_str(); + } + cerr << endl; + argv[an++]=NULL; + pid_t pid = vfork(); + if(pid==-1) { + close(stdo); close(stde); + throw konforka::exception(CODEPOINT,"failed to vfork()"); + } + if(!pid) { + // child + if(dup2(stdo,1)!=1) + _exit(-1); + if(dup2(stde,2)!=2) + _exit(-1); + close(0); + execvp(cmd.c_str(),(char**)&argv.front()); + _exit(-1); + } + // parent + close(stdo); close(stde); + int rv; + if(waitpid(pid,&rv,0)<0) + throw konforka::exception(CODEPOINT,"failed to waitpid()"); + return rv; + } + + string component_factory::get_classname(const string& component) { + string cn = root_intermediate+normalize_path(component,strip_trailing_slash|strip_leading_slash)+".classname"; + make(cn); + ifstream ifs(cn.c_str()); + if(!ifs.good()) + throw konforka::exception(CODEPOINT,"failed to access component .classname"); + ifs >> cn; + return cn; + } + + void component_factory::get_ancestors(const string& component,file_list_t& rv) { + string cn = root_intermediate+normalize_path(component,strip_trailing_slash|strip_leading_slash)+".ancestors"; + make(cn); + ifstream ifs(cn.c_str()); + if(!ifs.good()) + throw konforka::exception(CODEPOINT,"filed to access component .ancestors"); + rv.clear(); + while(!ifs.eof()) { + string a; + ifs >> a; + if(!a.empty()) + rv.push_back(a); + } + } + +} diff --git a/lib/component_so.cc b/lib/component_so.cc new file mode 100644 index 0000000..57cce01 --- a/dev/null +++ b/lib/component_so.cc @@ -0,0 +1,112 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <unistd.h> + #include <dlfcn.h> + #include <iostream> + #include <cassert> + #include <stdexcept> + using namespace std; + #include "sitecing/component_so.h" + #include "sitecing/sitecing_util.h" +#endif + +namespace sitecing { + + /* + * component_so + */ + + component_so::component_so(const string& soname) + : dl(NULL), sofile(soname) { + if(stat(sofile.c_str(),&stso)) + throw konforka::exception(CODEPOINT,"failed to stat() shared object"); + file_lock lock(sofile+".lock"); + dl = dlopen(sofile.c_str(),RTLD_LAZY); + lock.unlock(); + if(!dl) + throw konforka::exception(CODEPOINT,"failed to dlopen: "+string(dlerror())); + egg = (egg_t)dlsym(dl,"_egg"); + if(!egg) + throw konforka::exception(CODEPOINT,"failed to dlsym: "+string(dlerror())); + } + component_so::~component_so() { + for(free_chickens_t::iterator i=chickens_free.begin();i!=chickens_free.end();i++) + delete *i; + chickens_free.clear(); + if(!chickens_used.empty()) + throw konforka::exception(CODEPOINT,"attempt to destroy the component in use"); + dlclose(dl); + } + + bool component_so::is_uptodate() const { + struct stat st; + if(stat(sofile.c_str(),&st)) + throw konforka::exception(CODEPOINT,"failed to stat() shared object"); + return stso.st_mtime==st.st_mtime; + } + + acomponent* component_so::allocate_chicken() { + acomponent *rv; + if(!chickens_free.empty()) { + rv = chickens_free.front(); + chickens_free.pop_front(); + }else{ + rv = (*egg)(); + } + assert(chickens_used.find(rv)==chickens_used.end()); + chickens_used[rv]=1; + return rv; + } + + void component_so::allocate_chicken(acomponent* ac) { + used_chickens_t::iterator i = chickens_used.find(ac); + if(i!=chickens_used.end()) { + i->second++; + }else{ + free_chickens_t::iterator i; + for(i=chickens_free.begin();*i!=ac && i!=chickens_free.end();i++); + if(i==chickens_free.end()) + throw konforka::exception(CODEPOINT,"hens rarely adopt chickens"); + chickens_free.erase(i); + chickens_used[ac]=1; + } + } + + void component_so::deallocate_chicken(acomponent* ac) { + used_chickens_t::iterator i = chickens_used.find(ac); + if(i==chickens_used.end()) + throw konforka::exception(CODEPOINT,"you can't deallocate what is not allocated"); + i->second--; + if(i->second>0) + return; + chickens_used.erase(i); + chickens_free.push_front(ac); + } + + /* + * so_component + */ + + so_component::so_component(component_so *h,sitecing_interface *scif) + : hen(h), ac(NULL) { + if(!hen) + throw konforka::exception(CODEPOINT,"can't get an egg from the null-hen"); + ac = hen->allocate_chicken(); + ac->__set_interface(scif); + } + + void so_component::attach(component_so *h,acomponent *a) { + detach(); hen = h; ac = a; + if(!ac) + throw konforka::exception(CODEPOINT,"trying to clone null-chicken"); + if(!hen) + throw konforka::exception(CODEPOINT,"trying to clone orphan chicken"); + hen->allocate_chicken(ac); + } + void so_component::detach() { + if(hen && ac) + hen->deallocate_chicken(ac); + } + +} 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? + } +} diff --git a/lib/file_factory.cc b/lib/file_factory.cc new file mode 100644 index 0000000..c6b5748 --- a/dev/null +++ b/lib/file_factory.cc @@ -0,0 +1,55 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <sys/types.h> + #include <sys/stat.h> + #include <unistd.h> + #include <konforka/exception.h> + using namespace std; + #include "sitecing/file_factory.h" +#endif + +namespace sitecing { + + bool file_factory::is_uptodate(const string& dst,file_list_t* deps) { + file_list_t deplist; + file_list_t *fl = deps?deps:&deplist; + get_dependencies(dst,*fl); + struct stat stdst; + if(stat(dst.c_str(),&stdst)) + return false; + for(file_list_t::const_iterator i=fl->begin();i!=fl->end();i++) { + struct stat stdep; + if(stat(i->c_str(),&stdep)) + return false; + if(stdst.st_mtime<stdep.st_mtime) + return false; + if(!is_uptodate(*i)) + return false; + } + return true; + } + + void file_factory::make(const string& dst) { + try { + depth++; + if(depth>25) + throw konforka::exception(CODEPOINT,"recursed too deeply."); + file_list_t deps; + if(!is_uptodate(dst,&deps)) { + for(file_list_t::const_iterator i=deps.begin();i!=deps.end();i++) + make(*i); + build(dst); + } + depth--; + }catch(konforka::exception& ke) { + depth--; + ke.see(CODEPOINT); + throw; + }catch(...) { + depth--; + throw; + } + } + +} diff --git a/lib/pch.h b/lib/pch.h new file mode 100644 index 0000000..67f9d6d --- a/dev/null +++ b/lib/pch.h @@ -0,0 +1,45 @@ +#ifndef __PCH_H +#define __PCH_H + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/sem.h> +#include <errno.h> +#include <dlfcn.h> +#include <fnmatch.h> + +#include <dotconf.h> + +#include <cstdarg> +#include <cassert> +#include <iostream> +#include <fstream> + +#include <vector> +using namespace std; + +#include <konforka/exception.h> + +#include "sitecing/acomponent.h" +#include "sitecing/cgi_component.h" +#include "sitecing/component_factory.h" +#include "sitecing/sitecing_util.h" +#include "sitecing/sitecing_exception.h" +#include "sitecing/component_so.h" +#include "sitecing/configuration.h" +#include "sitecing/file_factory.h" +#include "sitecing/sitecing_interface_cgi.h" +#include "sitecing/sitespace.h" +#include "sitecing/util.h" +#include "sitecing/scoreboard.h" +#include "sitecing/process_manager.h" + +#include "sitecing/sitecing_parser.h" +#include "sitecing/sitecing_enflesher.h" + +#endif /* __PCH_H */ diff --git a/lib/process_manager.cc b/lib/process_manager.cc new file mode 100644 index 0000000..48bcb03 --- a/dev/null +++ b/lib/process_manager.cc @@ -0,0 +1,152 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <sys/types.h> + #include <unistd.h> + #include <sys/wait.h> + #include <signal.h> + #include <errno.h> + #include <cassert> + #include <string> + #include <konforka/exception.h> + using namespace std; + #include "sitecing/process_manager.h" +#endif + +namespace sitecing { + + process_manager::process_manager() + : min_children(1), max_children(MAX_SITECING_SCOREBOARD_SLOTS), + min_spare_children(0), max_spare_children(-1), finishing(false), + die_humbly(false) { + } + process_manager::~process_manager() { + if(die_humbly) + return; + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + scoreboard_slot *sslot = sboard.get_slot(tmp); + if((sslot->state!=scoreboard_slot::state_free) && (sslot->pid>0)) + kill(sslot->pid,SIGTERM); + } + collect_dead_souls(true); + } + + void process_manager::manage() { + while(!finishing) { + manage_children(); + // XXX: is it the way it should be? + sleep(10); + wait_for_children(); + } + collect_dead_souls(true); + } + + void process_manager::collect_dead_souls(bool actively) { + for(int tries=5;(tries>0) && (sboard.count_slots(scoreboard_slot::state_free)!=MAX_SITECING_SCOREBOARD_SLOTS);tries--) { + if(actively) { + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + scoreboard_slot *sslot = sboard.get_slot(tmp); + if((sslot->state!=scoreboard_slot::state_free) && (sslot->pid>0)) + kill(sslot->pid,SIGTERM); + } + } + wait_for_children(false); + // XXX: again.. is it the right way? + sleep(1); + } + } + + void process_manager::wait_for_children(bool hang) { + int status; + int o = WUNTRACED; + if(!hang) + o|=WNOHANG; + while(sboard.count_slots(scoreboard_slot::state_free)<MAX_SITECING_SCOREBOARD_SLOTS) { + pid_t pid = waitpid(-1,&status,o); + if(!pid) + return; + if(pid<0) { + if(errno==EINTR) + return; + throw konforka::exception(CODEPOINT,"failed to waitpid()"); + } + assert(pid); + int slot = sboard.get_slot_by_pid(pid); + sboard.free_slot(slot); + if(hang) + return; + } + } + + void process_manager::manage_children() { + if(!spawn_children()) + kill_children(); + else + sleep(1); // just to get some rest. + } + + bool process_manager::spawn_children() { + int total_children = 0; + int idle_children = 0; + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + switch(sboard.get_slot(tmp)->state) { + case scoreboard_slot::state_free: + break; + case scoreboard_slot::state_idle: + idle_children++; + default: + total_children++; + break; + } + } + int total_lack = 0; + if(total_children<min_children) + total_lack = min_children-total_children; + int idle_lack = 0; + if(idle_children<min_spare_children) + idle_lack = min_spare_children-idle_children; + bool rv = false; + for(;(idle_lack>0 || total_lack>0) && (total_children<max_children);idle_lack--,total_lack--,total_children++) { + spawn_child(); + rv = true; + } + return rv; + } + + bool process_manager::kill_children() { + int idle_children = 0; + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + if(sboard.get_slot(tmp)->state==scoreboard_slot::state_idle) + idle_children++; + } + int idle_excess = idle_children-max_spare_children; + bool rv = false; + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS && idle_excess>0;tmp++) { + scoreboard_slot *sslot = sboard.get_slot(tmp); + if((sslot->state==scoreboard_slot::state_idle) && (sslot->pid>0)) { + kill(sslot->pid,SIGTERM); + idle_excess--; + rv = true; + } + } + return rv; + } + + void process_manager::spawn_child() { + int slot = sboard.allocate_slot(); + pid_t pid = fork(); + if(pid<0) { + sboard.free_slot(slot); + throw konforka::exception(CODEPOINT,"failed to fork()"); + } + if(!pid) { + // child + sboard.get_slot(slot)->pid = getpid(); + process(slot); + _exit(0); + } + // parent + sboard.get_slot(slot)->pid = pid; + } + +} diff --git a/lib/scoreboard.cc b/lib/scoreboard.cc new file mode 100644 index 0000000..370cd93 --- a/dev/null +++ b/lib/scoreboard.cc @@ -0,0 +1,71 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <sys/types.h> + #include <unistd.h> + #include <sys/ipc.h> + #include <sys/shm.h> + #include <cassert> + #include <string> + #include <konforka/exception.h> + using namespace std; + #include "sitecing/scoreboard.h" +#endif + +namespace sitecing { + + scoreboard::scoreboard() + : shmid(-1), slots(NULL) { + shmid = shmget(IPC_PRIVATE,MAX_SITECING_SCOREBOARD_SLOTS*sizeof(scoreboard_slot),IPC_CREAT|0600); + if(shmid<0) + throw konforka::exception(CODEPOINT,"failed to shmget()"); + slots = (scoreboard_slot*)shmat(shmid,NULL,0); + if(shmctl(shmid,IPC_RMID,NULL)) + throw konforka::exception(CODEPOINT,"failed to shmctl()"); + if(!slots) + throw konforka::exception(CODEPOINT,"failed to shmat()"); + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) + slots[tmp].state=scoreboard_slot::state_free; + } + scoreboard::~scoreboard() { + shmdt(slots); + } + + int scoreboard::allocate_slot() { + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + if(slots[tmp].state==scoreboard_slot::state_free) { + slots[tmp].state=scoreboard_slot::state_allocated; + slots[tmp].pid=0; + return tmp; + } + } + throw konforka::exception(CODEPOINT,"out of scoreboard slots"); + } + void scoreboard::free_slot(int slot) { + assert(slot>=0 && slot<MAX_SITECING_SCOREBOARD_SLOTS); + if(slots[slot].state==scoreboard_slot::state_free) + throw konforka::exception(CODEPOINT,"freeing unallocated slot"); + slots[slot].state=scoreboard_slot::state_free; + } + + scoreboard_slot *scoreboard::get_slot(int slot) { + assert(slot>=0 && slot<MAX_SITECING_SCOREBOARD_SLOTS); + return &slots[slot]; + } + int scoreboard::get_slot_by_pid(pid_t pid) { + for(int rv=0;rv<MAX_SITECING_SCOREBOARD_SLOTS;rv++) + if( (slots[rv].state!=scoreboard_slot::state_free) && (slots[rv].pid == pid) ) + return rv; + throw konforka::exception(CODEPOINT,"no such process"); + } + + int scoreboard::count_slots(enum scoreboard_slot::_state state) { + int rv = 0; + for(int tmp=0;tmp<MAX_SITECING_SCOREBOARD_SLOTS;tmp++) { + if(slots[tmp].state==state) + rv++; + } + return rv; + } + +} diff --git a/lib/sitecing_enflesher.ll b/lib/sitecing_enflesher.ll new file mode 100644 index 0000000..5f631d7 --- a/dev/null +++ b/lib/sitecing_enflesher.ll @@ -0,0 +1,202 @@ +%{ +#include <iostream> +#include <fstream> +#include <cassert> +#include <stdexcept> +using namespace std; +#include "sitecing/sitecing_exception.h" +using namespace sitecing; +#define sitecing_enflesher_flexlexer_once +#include "sitecing/sitecing_enflesher.h" +#include "sitecing/sitecing_parser.h" +#undef yyFlexLexer +#define yyFlexLexer sitecing_enflesherFlexLexer +%} +%option 8bit c++ verbose noyywrap yyclass="sitecing_enflesher" yylineno prefix="sitecing_enflesher" stack debug + +ID [A-Za-z_][A-Za-z0-9_]* + +%% + +^\%\%\#[^\n]+\n { + string line = yytext; + line.erase(0,3); + line.erase(line.length()-1); + outs.flush(); + outs.close(); + outs.clear(); + outs.open((parser.output_basename+line).c_str(),ios::trunc); + if(!outs.good()) + throw preprocessor_error(CODEPOINT,"failed to write preprocessor output"); + anchor(); + anchoraged = true; +} +^\%\%[^\n]+\n { + string line = yytext; + line.erase(0,2); + line.erase(line.length()-1); + outs.flush(); + outs.close(); + outs.clear(); + outs.open((parser.output_basename+line).c_str(),ios::trunc); + if(!outs.good()) + throw preprocessor_error(CODEPOINT,"failed to write preprocessor output"); + anchoraged = false; +} + +\<\%component_basename\%\> outs << parser.component_basename; anchor_time = true; +\<\%impl\%\> outs << parser.impl; anchor_time = true; +\<\%member_functions:impl\%\> { + for(sitecing_parser::member_functions_t::const_iterator i=parser.member_functions.begin();i!=parser.member_functions.end();i++) { + outs << i->type << " " << parser.class_name << "::"; + if(i->name.empty()) { + outs << parser.class_name << "()"; + bool first = true; + for(sitecing_parser::member_variables_t::iterator i=parser.member_variables.begin();i!=parser.member_variables.end();i++) { + if(i->initializer.empty()) + continue; + if(first) { + outs << ":"; + first=false; + }else{ + outs << ","; + } + if(i->bComponent) { + outs << i->name << "(NULL)"; + }else { + outs << i->name << "(" << i->initializer << ")"; + } + } + }else if(i->name == "~") + outs << "~" << parser.class_name << "()"; + else + outs << i->name << i->args; + outs << "{\n" << i->body << "\n}\n"; + } + anchor_time = true; +} +\<\%class_name\%\> outs << parser.class_name; anchor_time = true; +\<\%baseclass_header\%\> outs << parser.base_header; anchor_time = true; +\<\%decl\%\> outs << parser.decl; anchor_time = true; +\<\%baseclass_name\%\> outs << parser.base_class; anchor_time = true; +\<\%member_variables:decl\%\> { + for(sitecing_parser::member_variables_t::iterator i=parser.member_variables.begin();i!=parser.member_variables.end();i++) { + if(i->bComponent) { + if(i->type.empty()) { + i->type = parser.factory.get_classname(i->initializer); + } + if(i->bTypeOnly) { + outs << "typedef " << i->type << " " << i->name << ";\n"; + }else{ + outs << "typedef " << i->type << " __type_" << i->name << ";\nsitecing::so_component __soc_" << i->name << ";\n__type_" << i->name << " *" << i->name << ";\n"; + } + }else{ + outs << i->type << " " << i->name << ";\n"; + } + } + anchor_time = true; +} +\<\%member_functions:decl\%\> { + for(sitecing_parser::member_functions_t::const_iterator i=parser.member_functions.begin();i!=parser.member_functions.end();i++) { + (i->name.empty()?outs:outs << "virtual ") + << i->type << " "; + if(i->name.empty()) { + outs << parser.class_name << "()"; + }else if(i->name == "~") + outs << "~" << parser.class_name << "()"; + else + outs << i->name << i->args; + outs << ";\n"; + } + anchor_time = true; +} +\<\%imports:list\%\> { + for(sitecing_parser::member_variables_t::const_iterator i=parser.member_variables.begin();i!=parser.member_variables.end();i++) { + if(i->bComponent) + outs << i->initializer << endl; + } + anchor_time = true; +} +\<\%imports:includes\%\> { + for(sitecing_parser::member_variables_t::iterator i=parser.member_variables.begin();i!=parser.member_variables.end();i++) { + if(i->bComponent) + outs << "\n#include \"" << i->initializer << ".h\"\n"; + } + anchor_time = true; +} +\<\%imports:import\%\> { + for(sitecing_parser::member_variables_t::iterator i=parser.member_variables.begin();i!=parser.member_variables.end();i++) { + if(!i->bComponent) + continue; + if(i->bTypeOnly) + continue; + outs << "__soc_" << i->name << "=__SCIF->ss->fetch(\"" << i->initializer << "\",__SCIF); " << i->name << "=static_cast<__type_" << i->name << "*>(__soc_" << i->name << ".ac->__the_most_derived_this());\n"; + } + anchor_time = true; +} + +\<\%base_component\%\> { + // TODO: + anchor_time = true; +} + +\<\%ancestors:includes\%\> { + for(sitecing_parser::ancestor_classes_t::const_iterator i=parser.ancestor_classes.begin();i!=parser.ancestor_classes.end();++i) { + outs << "#include \"" << i->path << ".h\"\n"; + } + anchor_time = true; +} +\<\%ancestors:component_list\%\> { + for(sitecing_parser::ancestor_classes_t::const_iterator i=parser.ancestor_classes.begin();i!=parser.ancestor_classes.end();++i) { + outs << i->path << "\n"; + } + anchor_time = true; +} +\<\%ancestors:base_clause_part\%\> { + for(sitecing_parser::ancestor_classes_t::const_iterator i=parser.ancestor_classes.begin();i!=parser.ancestor_classes.end();++i) { + outs << ", virtual public " << parser.factory.get_classname(i->path); + } +} +\<\%ancestors:typedefs\%\> { + for(sitecing_parser::ancestor_classes_t::const_iterator i=parser.ancestor_classes.begin();i!=parser.ancestor_classes.end();++i) { + outs << "typedef class " << parser.factory.get_classname(i->path) << " " << i->name << ";\n"; + } + anchor_time = true; +} +\<\%ancestors:import\%\> { + for(sitecing_parser::ancestor_classes_t::const_iterator i=parser.ancestor_classes.begin();i!=parser.ancestor_classes.end();++i) { + outs << i->name << "::__do_imports();\n"; + } + anchor_time = true; +} + +\n { + if(anchor_time) + anchor(); + ECHO; +} +. ECHO; + +%% + +void sitecing_enflesher::LexerOutput(const char *buf,int size) { + outs.write(buf,size); +} + +void sitecing_enflesher::enflesh() { + ifstream ifs(parser.skeleton.c_str()); + if(!ifs.good()) + throw preprocessor_error(CODEPOINT,"failed to open skeleton file"); + switch_streams(&ifs,NULL); + yylex(); +} + +void sitecing_enflesher::anchor() { + if(!anchoraged) + return; + outs << "\n#line " << lineno() << " \"" << parser.skeleton << "\"\n"; + anchor_time = false; +} +/* + * vim:set ft=lex: + */ diff --git a/lib/sitecing_interface_cgi.cc b/lib/sitecing_interface_cgi.cc new file mode 100644 index 0000000..5c3d295 --- a/dev/null +++ b/lib/sitecing_interface_cgi.cc @@ -0,0 +1,25 @@ +#include <cassert> +#include "sitecing/sitecing_interface_cgi.h" + +namespace sitecing { + + sitecing_interface_cgi::sitecing_interface_cgi(sitespace *s) + : sitecing_interface(&prebuffer), ss(s), cgigw(NULL) { + } + + void sitecing_interface_cgi::prepare(kingate::cgi_gateway *cg) { + cgigw = cg; + headers.clear(); + headers["Content-Type"] = "text/html"; + prebuffer.str(""); + } + + void sitecing_interface_cgi::flush() { + assert(cgigw); + for(headers_t::const_iterator i=headers.begin();i!=headers.end();i++) + cgigw->out() << i->first << ": " << i->second << "\n"; + (cgigw->out() << "\n").write(prebuffer.str().c_str(),prebuffer.tellp()); + cgigw->out().flush(); + } + +} diff --git a/lib/sitecing_parser.ll b/lib/sitecing_parser.ll new file mode 100644 index 0000000..6cb78f3 --- a/dev/null +++ b/lib/sitecing_parser.ll @@ -0,0 +1,594 @@ +%{ + /* + * XXX: I have a strong feeling that this parser should be completely rewritten. + */ +#include <iostream> +#include <fstream> +#include <cassert> +#include <stdexcept> +using namespace std; +#include "sitecing/sitecing_util.h" +#include "sitecing/sitecing_exception.h" +using namespace sitecing; +#define sitecing_parser_flexlexer_once +#include "sitecing/sitecing_parser.h" +#include "sitecing/sitecing_enflesher.h" +#undef yyFlexLexer +#define yyFlexLexer sitecing_parserFlexLexer +%} +%x SLASHSTAR_COMMENT SLASHSLASH_COMMENT STRING +%x CODELINE CLASSLINE DECLLINE IMPLLINE DECLBLOCK IMPLBLOCK VARLINE VARINIT +%x IMPORTLINE IMPORTCOMPONENT +%x IMPORTTYPELINE IMPORTTYPECOMPONENT +%x DERIVELINE DERIVECOMPONENT +%x CONSTRUCTOR DESTRUCTOR CODEMETHODLINE CODEMETHODARGS +%x CODEMETHODBLOCK INLINE METHODLINE METHODARGS METHODBLOCK CODEBLOCK OUTPUTBLOCK +%option 8bit c++ verbose noyywrap yyclass="sitecing_parser" prefix="sitecing_parser" stack yylineno + +WHITESPACE [ \t] +ID [A-Za-z_][A-Za-z0-9_]* +NOIDCHAR [^A-Za-z0-9_] + +%% + +<INITIAL>{ + ^\%\%class{WHITESPACE}+ { + // TODO: signal error if we already have class name acquired from source. + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments|modus_operandi::flag_devour_whitespace)); + BEGIN(CLASSLINE); + } + ^\%\%decl{WHITESPACE}+ { + modi.push_front(modus_operandi(0)); + anchor(); + BEGIN(DECLLINE); + } + ^\%\%impl{WHITESPACE}+ { + modi.push_front(modus_operandi(0)); + anchor(); + BEGIN(IMPLLINE); + } + \<\%decl\> { + modi.push_front(modus_operandi(0)); + anchor(); + BEGIN(DECLBLOCK); + } + \<\%impl\> { + modi.push_front(modus_operandi(0)); + anchor(); + BEGIN(IMPLBLOCK); + } + ^\%\%var{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + anchor(); + BEGIN(VARLINE); + } + ^\%\%import{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + BEGIN(IMPORTLINE); + } + ^\%\%import_type{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + BEGIN(IMPORTTYPELINE); + } + ^\%\%derive{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + BEGIN(DERIVELINE); + } + \<\%constructor\> { + modi.push_front(modus_operandi()); + anchor(); + BEGIN(CONSTRUCTOR); + } + \<\%destructor\> { + modi.push_front(modus_operandi()); + anchor(); + BEGIN(DESTRUCTOR); + } + \<\%codemethod{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + anchor(); + BEGIN(CODEMETHODLINE); + } + \<\%method{WHITESPACE}+ { + modi.push_front(modus_operandi(modus_operandi::flag_devour_comments)); + anchor(); + BEGIN(METHODLINE); + } + <<EOF>> { + assert(modi.size()==1); + M().modify(modus_operandi::modus_preop); + LexerOutput(";",1); + return 0; + } +} +<<EOF>> throw preprocessor_error(CODEPOINT,"unexpected end of file",lineno()); + +<CODEBLOCK,CODEMETHODBLOCK>{ + "<%output>" { + anchor(); + yy_push_state(OUTPUTBLOCK); + } +} + +<METHODLINE>{ + {WHITESPACE}+ { + modus_operandi& m = modi.front(); + if(!m.output.empty()) { + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + } + \* { + modus_operandi& m = modi.front(); + ECHO; + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + \( { + modus_operandi& m = modi.front(); + if(m.output.empty()) { + m._name=m._lastid; + }else{ + if(!m._lastid.empty()) { // XXX: lastid, I believe should never be emtpy... + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._name = m.output; + m.output.clear(); + } + ECHO; + BEGIN(METHODARGS); + } +} +<METHODARGS>{ + \%\> { + modus_operandi& m = modi.front(); + m._args = m.output; + m.output.clear(); + anchor(); + BEGIN(METHODBLOCK); + } +} + +<INITIAL,METHODBLOCK,OUTPUTBLOCK>{ + \<\%{WHITESPACE}+ { + M().modify(modus_operandi::modus_postop); + anchor(); + LexerOutput("(",1); + yy_push_state(INLINE); + } + ^\%{WHITESPACE} { + M().modify(modus_operandi::modus_code); + anchor(); + yy_push_state(CODELINE); + } + \<\%code\> { + M().modify(modus_operandi::modus_code); + anchor(); + yy_push_state(CODEBLOCK); + } + "</%output>" { + if(YY_START!=OUTPUTBLOCK) throw preprocessor_error(CODEPOINT,"unexpected tag",lineno()); + M().modify(modus_operandi::modus_code); + anchor(); + yy_pop_state(); + } +} + +<INLINE>\%\> LexerOutput(")",1); M().modus=modus_operandi::modus_preop; yy_pop_state(); +<CODELINE>\n yy_pop_state(); + +<CODEMETHODLINE>{ + {WHITESPACE}+ { + modus_operandi& m = modi.front(); + if(!m.output.empty()) { + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + } + \* { + modus_operandi& m = modi.front(); + ECHO; + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + \( { + modus_operandi& m = modi.front(); + if(m.output.empty()) { + m._name=m._lastid; + }else{ + if(!m._lastid.empty()) { // XXX: lastid, I believe should never be emtpy... + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._name = m.output; + m.output.clear(); + } + ECHO; + BEGIN(CODEMETHODARGS); + } +} +<CODEMETHODARGS>{ + \%\> { + modus_operandi& m = modi.front(); + m._args = m.output; + m.output.clear(); + m.flags=0; + anchor(); + BEGIN(CODEMETHODBLOCK); + } +} + +<IMPORTLINE>{ + {WHITESPACE}+ { } + {ID} { + if(!modi.front()._name.empty()) + throw preprocessor_error(CODEPOINT,"syntax error",lineno()); + modi.front()._name = yytext; + } + \= { + modi.front().output.clear(); + BEGIN(IMPORTCOMPONENT); + } +} +<IMPORTCOMPONENT>{ + {WHITESPACE}+ { } + \n { + modus_operandi& m = M(); + string::size_type t = m.output.find_first_not_of(" \t"); + if(t!=string::npos) + m.output.erase(0,t); + t = m.output.find_last_not_of(" \t;"); + if(t!=string::npos) + m.output.erase(t+1); + if(m.output[0]=='"' && m.output[m.output.length()-1]=='"') { + m.output.erase(0,1); + m.output.erase(m.output.length()-1); + } + string c = combine_path(component_basename,m.output); + member_variables.push_back(member_variable(m._type,m._name,normalize_path(c,strip_leading_slash),true)); + modi.pop_front(); + BEGIN(INITIAL); + } +} + +<IMPORTTYPELINE>{ + {WHITESPACE}+ { } + {ID} { + if(!modi.front()._name.empty()) + throw preprocessor_error(CODEPOINT,"syntax error",lineno()); + modi.front()._name = yytext; + } + \= { + modi.front().output.clear(); + BEGIN(IMPORTTYPECOMPONENT); + } +} +<IMPORTTYPECOMPONENT>{ + {WHITESPACE}+ { } + \n { + modus_operandi& m = M(); + string::size_type t = m.output.find_first_not_of(" \t"); + if(t!=string::npos) + m.output.erase(0,t); + t = m.output.find_last_not_of(" \t;"); + if(t!=string::npos) + m.output.erase(t+1); + if(m.output[0]=='"' && m.output[m.output.length()-1]=='"') { + m.output.erase(0,1); + m.output.erase(m.output.length()-1); + } + string c = combine_path(component_basename,m.output); + member_variables.push_back(member_variable(m._type,m._name,normalize_path(c,strip_leading_slash),true,true)); + modi.pop_front(); + BEGIN(INITIAL); + } +} + +<DERIVELINE>{ + {WHITESPACE}+ { } + {ID} { + if(!modi.front()._name.empty()) + throw preprocessor_error(CODEPOINT,"syntax_error",lineno()); + modi.front()._name = yytext; + } + \= { + modi.front().output.clear(); + BEGIN(DERIVECOMPONENT); + } +} +<DERIVECOMPONENT>{ + {WHITESPACE}+ { } + \n { + modus_operandi& m = M(); + string::size_type t = m.output.find_first_not_of(" \t"); + if(t!=string::npos) + m.output.erase(0,t); + t = m.output.find_last_not_of(" \t;"); + if(t!=string::npos) + m.output.erase(t+1); + if(m.output[0]=='"' && m.output[m.output.length()-1]=='"') { + m.output.erase(0,1); + m.output.erase(m.output.length()-1); + } + string c = combine_path(component_basename,m.output); + ancestor_classes.push_back(ancestor_class(m._name,normalize_path(c,strip_leading_slash))); + modi.pop_front(); + BEGIN(INITIAL); + } +} + +<VARLINE>{ + {WHITESPACE}+ { + modus_operandi& m = modi.front(); + if(!m.output.empty()) { + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + } + \* { + modus_operandi& m = modi.front(); + ECHO; + if(!m._lastid.empty()) { + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._lastid = m.output; + m.output.clear(); + } + \;|\n|\= { + modus_operandi& m = modi.front(); + if(m.output.empty()) { + m._name=m._lastid; + }else{ + if(!m._lastid.empty()) { // XXX: lastid should never be emtpy, I believe? + if(!m._type.empty()) m._type += ' '; + m._type += m._lastid; + } + m._name=m.output; + m.output.clear(); + } + BEGIN(VARINIT); + if(*yytext!='=') + unput('\n'); + } +} +<VARINIT>{ + \n { + modus_operandi& m = modi.front(); + string::size_type t = m.output.find_first_not_of(" \t"); + if(t!=string::npos) + m.output.erase(0,t); + t = m.output.find_last_not_of(" \t;"); + if(t!=string::npos) + m.output.erase(t+1); + member_variables.push_back(member_variable(m._type,m._name,m.output)); + if(!m.output.empty()) + have_initializers=true; + modi.pop_front(); + BEGIN(INITIAL); + } +} +<DECLLINE>\n { + ECHO; + decl += modi.front().output; + modi.pop_front(); + BEGIN(INITIAL); +} +<IMPLLINE>\n { + ECHO; + impl += modi.front().output; + modi.pop_front(); + BEGIN(INITIAL); +} +<CLASSLINE>\n { + class_name = modi.front().output; + modi.pop_front(); + BEGIN(INITIAL); +} +<CLASSLINE,DECLLINE,IMPLLINE,VARLINE,VARINIT,IMPORTLINE,IMPORTCOMPONENT,CODEMETHODLINE,CODEMETHODARGS,INLINE,METHODLINE,METHODARGS,DECLBLOCK,IMPLBLOCK,CONSTRUCTOR,DESTRUCTOR,CODEMETHODBLOCK,CODELINE,CODEBLOCK>{ + "/*" { + yy_push_state(SLASHSTAR_COMMENT); + if(!M().devour_comments()) { + ECHO; + } + } + "//" { + yy_push_state(SLASHSLASH_COMMENT); + if(!M().devour_comments()) { + ECHO; + } + } + \" { + yy_push_state(STRING); + ECHO; + } + \'\\.\' { + ECHO; + } +} + +<INITIAL,METHODBLOCK,OUTPUTBLOCK>{ + \" soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\\"",2); + \n soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\n",2); + \r soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\r",2); + \t soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\t",2); + \b soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\b",2); + \a soft_anchor(); M().modify(modus_operandi::modus_text); LexerOutput("\\a",2); + . soft_anchor(); M().modify(modus_operandi::modus_text); ECHO; + {WHITESPACE}+ soft_anchor(); M().modify(modus_operandi::modus_text); ECHO; +} + +<DECLBLOCK,IMPLBLOCK,CONSTRUCTOR,DESTRUCTOR,CODEMETHODBLOCK,METHODBLOCK,CODEBLOCK>{ + \<\/\%decl\> { + if(YY_START!=DECLBLOCK) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + decl += modi.front().output; + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/\%impl\> { + if(YY_START!=IMPLBLOCK) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + impl += modi.front().output; + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/\%constructor\> { + if(YY_START!=CONSTRUCTOR) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + member_functions.push_back(member_function("","","",modi.front().output)); + have_constructor = true; + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/\%destructor\> { + if(YY_START!=DESTRUCTOR) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + member_functions.push_back(member_function("","~","",modi.front().output)); + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/\%codemethod\> { + if(YY_START!=CODEMETHODBLOCK) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + modus_operandi& m = modi.front(); + member_functions.push_back(member_function(m._type,m._name,m._args,m.output)); + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/%method\> { + if(YY_START!=METHODBLOCK) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + modus_operandi& m = modi.front(); + m.modify(modus_operandi::modus_code); + member_functions.push_back(member_function(m._type,m._name,m._args,m.output)); + modi.pop_front(); + BEGIN(INITIAL); + } + \<\/%code\> { + if(YY_START!=CODEBLOCK) throw preprocessor_error(CODEPOINT,"tags mismatch",lineno()); + yy_pop_state(); + } + \n ECHO; +} + +<SLASHSTAR_COMMENT>{ + "*/" { + if(!M().devour_comments()) { + ECHO; + } + yy_pop_state(); + unput(' '); + } + \n { + if(!M().devour_comments()) { + ECHO; + } + } +} +<SLASHSLASH_COMMENT>{ + \n { + if(!M().devour_comments()) { + ECHO; + } + yy_pop_state(); + if(YY_START!=CODEBLOCK && YY_START!=CODEMETHODBLOCK && YY_START!=IMPLBLOCK && YY_START!=DECLBLOCK) + unput('\n'); + } +} +<SLASHSTAR_COMMENT,SLASHSLASH_COMMENT>. { + if(!M().devour_comments()) { + ECHO; + } +} +<STRING>{ + \\. ECHO; + \" ECHO; yy_pop_state(); + . ECHO; +} + +{WHITESPACE}+ { + if(!(M().flags&modus_operandi::flag_devour_whitespace)) { + ECHO; + } +} + +%% + +sitecing_parser::sitecing_parser(component_factory& f) + : factory(f), have_initializers(false), have_constructor(false), + base_class("sitecing::cgi_component"), + base_header("sitecing/cgi_component.h"), + skeleton(__SC_DEFAULT_SKELETON) { + } + +void sitecing_parser::preprocess(const string& in) { + ifstream ifs(in.c_str(),ios::in); + if(!ifs.good()) + throw preprocessor_error(CODEPOINT,"failed to open input file"); + input_file = in; + modi.push_front(modus_operandi(0)); + switch_streams(&ifs,NULL); + if(yylex()) + throw preprocessor_error(CODEPOINT,"unknown error"); + member_functions.push_back(member_function("void","main","(int _magic,va_list _args)",M().output)); + if(have_initializers && !have_constructor) + member_functions.push_back(member_function("","","","")); + sitecing_enflesher enflesher(*this); + enflesher.enflesh(); +} + +void sitecing_parser::LexerOutput(const char* buf,int size) { + assert(modi.size()); + M().output.append(buf,size); +} + +static const char *modus_transitions + [sitecing_parser::modus_operandi::modi] + [sitecing_parser::modus_operandi::modi] = { +// To: +// code preop postop text From: + { "", "(*(__SCIF->out))", "(*(__SCIF->out))<<", "(*(__SCIF->out))<<\"" }, // code + { ";", "", "<<", "<<\"" }, // preop + { NULL, NULL, "", "\"" }, // postop + { "\";", "\"", "\"<<", "" } // text +}; + +void sitecing_parser::modus_operandi::modify(modus_t m) { + const char * x = modus_transitions[modus][m]; + assert(x); + output += x; + modus = m; +} + +void sitecing_parser::soft_anchor() { + if(M().modus!=modus_operandi::modus_text) + anchor(); +} +void sitecing_parser::anchor() { + if(M().modus==modus_operandi::modus_text) + M().modify(modus_operandi::modus_preop); + M().output += "\n#line "; + char tmp[7]; + snprintf(tmp,sizeof(tmp),"%d",lineno()); + M().output += tmp; + M().output += " \""; + M().output += input_file; + M().output += "\"\n"; +} +/* vim:set ft=lex: */ diff --git a/lib/sitecing_util.cc b/lib/sitecing_util.cc new file mode 100644 index 0000000..9b6c54e --- a/dev/null +++ b/lib/sitecing_util.cc @@ -0,0 +1,278 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <sys/stat.h> + #include <sys/types.h> + #include <unistd.h> + #include <fcntl.h> + #include <sys/ipc.h> + #include <sys/sem.h> + #include <errno.h> + #include <iostream> + #include <fstream> + #include <cassert> + #include "sitecing/sitecing_util.h" +#endif + +namespace sitecing { + + /* + * XXX: all of these utilities could be sheerly optimized. + */ + + string normalize_path(const string& path,int opts) { + const char *s = path.c_str(); + string rv; + string::size_type notslash = 0; + if( (*s)=='.' && s[1]=='/' ) + s+=2; + if(opts&strip_leading_slash) + for(;(*s) && (*s)=='/';s++); + for(;*s;s++) { + if( (*s)=='/' ) { + if(s[1]=='/') + continue; + if(s[1]=='.' && s[2]=='/') { + s+=2; + continue; + } + } + if(opts&restrict_dotdot) { + if( + ( rv.empty() && s[0]=='.' && s[1]=='.' && s[2]=='/' ) // "^../" + || ( s[0]=='/' && s[1]=='.' && s[2]=='.' && (s[3]==0 || s[3]=='/') ) // "/..(/|$)" + ) + throw utility_restricted_sequence(CODEPOINT,"restricted updir sequence encountered"); + } + rv += *s; + if( (*s) != '/' ) + notslash=rv.length(); + } + if(!(opts&strip_trailing_slash)) + notslash++; + if(notslash<rv.length()) + rv.erase(notslash); // XXX: check the logic of stripping/not strippling trailing slash + return rv; + } + + string strip_prefix(const string& str,const string& prefix) { + if(str.compare(0,prefix.length(),prefix)) + throw utility_no_prefix(CODEPOINT,"no such prefix"); + return str.substr(prefix.length()); + } + + string strip_suffix(const string& str,const string& suffix) { + if(str.compare(str.length()-suffix.length(),suffix.length(),suffix)) + throw utility_no_suffix(CODEPOINT,"no such suffix"); + return str.substr(0,str.length()-suffix.length()); + } + + string dir_name(const string& filename) { + string::size_type sl = filename.find_last_of('/'); + if(sl==string::npos) + return ""; // no slashes -- no dir. + string::size_type nosl = filename.find_last_not_of('/',sl); + if(nosl==string::npos) + return ""; // only slashes -- no dir. XXX: only slashes after the last slash... does it mean no dir? + return filename.substr(0,nosl+1); + } + + void make_path(const string& path,mode_t mode) { + struct stat st; + for(string::size_type sl=0;sl!=string::npos;sl=path.find('/',sl+1)) { + if(!sl) + continue; + string p = path.substr(0,sl); + if(stat(p.c_str(),&st) || !S_ISDIR(st.st_mode)) { + if(mkdir(p.c_str(),mode)) + throw konforka::exception(CODEPOINT,"failed to mkdir()"); + } + } + if(stat(path.c_str(),&st) || !S_ISDIR(st.st_mode)) { + if(mkdir(path.c_str(),mode)) + throw konforka::exception(CODEPOINT,"failed to mkdir()"); + } + } + + void file_lock::lock(const string& f) { + unlock(); + fd = open(f.c_str(),O_CREAT|O_RDWR,S_IRUSR|S_IWUSR); + if(fd<0) + throw konforka::exception(CODEPOINT,"failed to open/create lockfile"); + try { + lock(); + }catch(konforka::exception& ke) { + ke.see(CODEPOINT); + close(fd); fd=-1; + throw; + }catch(...) { + close(fd); fd=-1; + throw; + } + } + void file_lock::lock() { + assert(fd>=0); + struct flock fl; + fl.l_type = F_WRLCK; + fl.l_whence=SEEK_SET; + fl.l_start=fl.l_len=0; + for(int tries=3;tries;tries--) { + if(!fcntl(fd,F_SETLK,&fl)) + return; + sleep(8); + } + throw konforka::exception(CODEPOINT,"failed to obtain file lock"); + } + void file_lock::unlock() { + if(fd<0) + return; + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence=SEEK_SET; + fl.l_start=fl.l_len=0; + int rv = fcntl(fd,F_SETLK,&fl); + close(fd); + fd=-1; + if(rv) + throw konforka::exception(CODEPOINT,"failed to release file lock"); + } + + void pid_file::set(const string& f,bool u) { + ofstream of(f.c_str(),ios::trunc); + if(!of) + throw konforka::exception(CODEPOINT,"failed to open file for writing pid"); + of << getpid() << endl; + of.close(); + file_name = f; + unlink_pid = u; + } + void pid_file::unlink() { + if(!unlink_pid) + return; + ::unlink(file_name.c_str()); + } + + void semaphore::init() { + deinit(); + semid = semget(IPC_PRIVATE,1,IPC_CREAT|0600); + if(semid<0) + throw konforka::exception(CODEPOINT,"failed to semget()"); + if(semctl(semid,0,SETVAL,1)) + throw konforka::exception(CODEPOINT,"failed to semctl()"); + } + void semaphore::deinit() { + if(semid<0) + return; + semctl(semid,0,IPC_RMID,0); + } + void semaphore::on() { + assert(semid>=0); + struct sembuf sb; + sb.sem_num=0; + sb.sem_op=-1; + sb.sem_flg = SEM_UNDO; + while(semop(semid,&sb,1)<0) { + if(errno!=EINTR) + throw konforka::exception(CODEPOINT,"failed to semop()"); + } + } + void semaphore::off() { + assert(semid>=0); + struct sembuf sb; + sb.sem_num=0; + sb.sem_op=1; + sb.sem_flg = SEM_UNDO; + while(semop(semid,&sb,1)<0) { + if(errno!=EINTR) + throw konforka::exception(CODEPOINT,"failed to semop()"); + } + } + + void semaphore_lock::lock() { + assert(sem); + if(locked) + return; + sem->on(); + locked = true; + } + void semaphore_lock::unlock() { + if(!sem) + return; + if(!locked) + return; + sem->off(); + locked=false; + } + + string combine_path(const string& origin,const string& relative,int opts) { + string r = normalize_path(relative,0); + string rv; + // XXX: what to do if relative is empty is a question, really. + if(r.empty()) { + return normalize_path( (opts&origin_is_file)?dir_name(origin):origin ,strip_leading_slash|restrict_dotdot|strip_trailing_slash); + }else{ + if(r[0]=='/') { + r.erase(0,1); + }else{ + rv = normalize_path((opts&origin_is_file)?dir_name(origin):origin,restrict_dotdot|strip_trailing_slash); + } + } + string::size_type lsl = rv.rfind('/'); + for(string::size_type sl=r.find('/');sl!=string::npos;sl=r.find('/')) { + assert(sl!=0); + if(sl==1 && r[0]=='.') { + // it's a "./" + r.erase(0,2); + }else if(sl==2 && r[0]=='.' && r[1]=='.') { + // we have a "../" + if(lsl==string::npos) { + if(rv.empty() && (opts&fail_beyond_root)) + throw utility_beyond_root(CODEPOINT,"went beyond root while combining path"); + rv.clear(); + }else{ + rv.erase(lsl); + lsl = rv.rfind('/'); + } + r.erase(0,3); + }else{ + // we have a "something/" + lsl = rv.length(); + rv += '/'; + rv += r.substr(0,sl); + r.erase(0,sl+1); + } + } + if(r.empty()) + return rv+'/'; + if(r.length()==2 && r[0]=='.' && r[0]=='.') { + if(lsl==string::npos) { + if(rv.empty() & (opts&fail_beyond_root)) + throw utility_beyond_root(CODEPOINT,"went beyond root while combining path"); + return "/"; + }else{ + rv.erase(lsl+1); + return rv; + } + } + rv += '/'; + rv += r; + return rv; + } + + void auto_chdir::pushdir(const string& td,bool ap) { + char *tmp = get_current_dir_name(); + assert(tmp); + saved_pwd = tmp; + free(tmp); + autopop=ap; + if(chdir(td.c_str())) + throw konforka::exception(CODEPOINT,"failed to chdir()"); + } + void auto_chdir::popdir() { + autopop=false; + if(chdir(saved_pwd.c_str())) + throw konforka::exception(CODEPOINT,"failed to chdir()"); + // XXX: or should it be thrown? after all we call it from destructor... + } + +} diff --git a/lib/sitespace.cc b/lib/sitespace.cc new file mode 100644 index 0000000..0406d11 --- a/dev/null +++ b/lib/sitespace.cc @@ -0,0 +1,52 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <cassert> + #include "sitecing/sitespace.h" + #include "sitecing/sitecing_util.h" +#endif + +namespace sitecing { + + sitespace::sitespace(configuration& c) + : config(c), factory(c) { } + + sitespace::~sitespace() { + for(sentenced_t::iterator i = sentenced.begin();i!=sentenced.end();++i) { + assert((*i)->chickens_used.empty()); + delete *i; + } + } + + so_component sitespace::fetch(const string& c,sitecing_interface* scif) { + execute_sentenced(); + string sobase = normalize_path(c); + string sopath = factory.root_so+sobase+".so"; + config_options *co_build = config.lookup_config(sobase,config_options::flag_build); + if( (!co_build) || co_build->build ) + factory.make(sopath); + components_t::iterator i = components.find(sopath); + if(i!=components.end()) { + if(i->second->is_uptodate()) + return so_component(i->second,scif); + if(i->second->chickens_used.empty()) { + delete i->second; + }else{ + sentenced.push_back(i->second); + } + components.erase(i); + } + pair<components_t::iterator,bool> ins = components.insert(components_t::value_type(sopath,new component_so(sopath))); + return so_component(ins.first->second,scif); + } + + void sitespace::execute_sentenced() { + for(sentenced_t::iterator i = sentenced.begin();i!=sentenced.end();++i) { + if((*i)->chickens_used.empty()) { + delete *i; + sentenced.erase(i); + } + } + } + +} diff --git a/lib/util.cc b/lib/util.cc new file mode 100644 index 0000000..1a81c56 --- a/dev/null +++ b/lib/util.cc @@ -0,0 +1,83 @@ +#ifdef USE_PCH + #include "pch.h" +#else + #include <cassert> + #include "sitecing/util.h" +#endif + +namespace sitecing { + + static const char *unsafeChars = "<>& \n\""; + + string html_escape(const string& str,int flags) { + string rv = str; + string::size_type screwed = 0; + for(;;) { + screwed = rv.find_first_of(unsafeChars,screwed); + if(screwed == string::npos) + break; + while(screwed<rv.length() && strchr(unsafeChars,rv.at(screwed))) { + char danger = rv.at(screwed); + switch(danger) { + case '<': + rv.replace(screwed,1,"<"); screwed+=4; + break; + case '>': + rv.replace(screwed,1,">"); screwed+=4; + break; + case '&': + rv.replace(screwed,1,"&"); screwed+=5; + break; + case ' ': + if(flags&html_escape_nbsp) { + rv.replace(screwed,1," "); screwed+=6; + }else + screwed++; + break; + case '\n': + if(flags&html_escape_br) { + if(flags&html_escape_br_noslash) { + rv.replace(screwed,1,"<br>\n"); screwed += 5; + }else{ + rv.replace(screwed,1,"<br/>\n"); screwed += 6; + } + }else + screwed++; + break; + case '\"': + if(flags&html_escape_quot) { + rv.replace(screwed,1,"""); screwed+=6; + }else + screwed++; + break; + default: + assert(false); + break; + } + } + } + return rv; + } + + void checkpoint::set() { + point = stream->tellp(); + if(last_will==will_intestate) + last_will = will_rollback; + } + + void checkpoint::make_will(will_t lw) { + last_will = lw; + } + + void checkpoint::rollback() { + stream->seekp(point); + if(last_will == will_rollback) + last_will = will_intestate; + } + void checkpoint::commit() { + point = stream->tellp(); + if(last_will == will_rollback) + last_will = will_intestate; + } + +} |