#include #include #include #include #include #define epicsExportSharedSymbols #include "helper.h" #include "pdbsingle.h" #include "pvif.h" #ifdef USE_MULTILOCK # include "pdbgroup.h" #endif namespace pvd = epics::pvData; namespace pva = epics::pvAccess; int PDBProviderDebug = 3; namespace { struct Splitter { const char sep, *cur, *end; Splitter(const char *s, char sep) :sep(sep), cur(s) { assert(s); end = strchr(cur, sep); } bool operator!() const { return !cur; } bool snip(std::string& ret) { if(!cur) return false; if(end) ret = std::string(cur, end-cur); else ret = std::string(cur); if(end) { cur = end+1; end = strchr(cur, sep); } else { cur = NULL; } return true; } }; struct GroupMemberInfo { // consumes builder GroupMemberInfo(const std::string& a, const std::string& b, p2p::auto_ptr& builder) :pvname(a), pvfldname(b), builder(PTRMOVE(builder)) {} std::string pvname, // aka. name passed to dbChannelOpen() pvfldname; // PVStructure sub-field std::string structID; // ID to assign to sub-field std::set triggers; // names in GroupInfo::members_names which are post()d on events from pvfldname std::tr1::shared_ptr builder; // not actually shared, but allows us to be copyable bool operator<(const GroupMemberInfo& o) const { return pvfldname members_t; members_t members; typedef std::map members_map_t; members_map_t members_map; typedef std::set triggers_set_t; typedef std::map triggers_t; triggers_t triggers; enum tribool {Unset,True,False} atomic; bool hastriggers; }; // Iterates all PDB records and gathers info() to construct PDB groups struct PDBProcessor { typedef std::map groups_t; groups_t groups; std::string recbase; GroupInfo *curgroup; // validate trigger mappings and process into bit map form void resolveTriggers() { FOREACH(it, end, groups) { // for each group GroupInfo& info = it->second; if(info.hastriggers) { FOREACH(it2, end2, info.triggers) { // for each trigger source const std::string& src = it2->first; GroupInfo::triggers_set_t& targets = it2->second; GroupInfo::members_map_t::iterator it2x = info.members_map.find(src); if(it2x==info.members_map.end()) { fprintf(stderr, "Group \"%s\" defines triggers from non-existant field \"%s\"\n", info.name.c_str(), src.c_str()); continue; } GroupMemberInfo& srcmem = info.members[it2x->second]; if(PDBProviderDebug>2) fprintf(stderr, " pdb trg '%s.%s' -> ", info.name.c_str(), src.c_str()); FOREACH(it3, end3, targets) { // for each trigger target const std::string& target = *it3; if(target=="*") { for(size_t i=0; i2) fprintf(stderr, "%s, ", info.members[i].pvfldname.c_str()); } } else { GroupInfo::members_map_t::iterator it3x = info.members_map.find(target); if(it3x==info.members_map.end()) { fprintf(stderr, "Group \"%s\" defines triggers to non-existant field \"%s\"\n", info.name.c_str(), target.c_str()); continue; } const GroupMemberInfo& targetmem = info.members[it3x->second]; if(targetmem.pvname.empty()) { if(PDBProviderDebug>2) fprintf(stderr, ", ", targetmem.pvfldname.c_str()); } else { // and finally, update source BitSet srcmem.triggers.insert(targetmem.pvfldname); if(PDBProviderDebug>2) fprintf(stderr, "%s, ", targetmem.pvfldname.c_str()); } } } if(PDBProviderDebug>2) fprintf(stderr, "\n"); } } else { if(PDBProviderDebug>1) fprintf(stderr, " pdb default triggers for '%s'\n", info.name.c_str()); FOREACH(it2, end2, info.members) { GroupMemberInfo& mem = *it2; if(mem.pvname.empty()) continue; mem.triggers.insert(mem.pvfldname); // default is self trigger } } } } PDBProcessor() : curgroup(NULL) { for(pdbRecordIterator rec; !rec.done(); rec.next()) { const char *json = rec.info("Q:group"); if(!json) continue; #ifndef USE_MULTILOCK static bool warned; if(!warned) { warned = true; fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name()); } #else if(PDBProviderDebug>2) { fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name()); } try { GroupConfig conf; GroupConfig::parse(json, conf); if(!conf.warning.empty()) fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.record()->name, conf.warning.c_str()); recbase = rec.record()->name; recbase += "."; for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end(); git!=gend; ++git) { const std::string& grpname = git->first; const GroupConfig::Group& grp = git->second; if(dbChannelTest(grpname.c_str())==0) { fprintf(stderr, "%s : Group name conflicts with record name. Ignoring...\n", grpname.c_str()); continue; } groups_t::iterator it = groups.find(grpname); if(it==groups.end()) { // lazy creation of group std::pair ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname)))); it = ins.first; } curgroup = &it->second; if(!grp.id.empty()) curgroup->structID = grp.id; for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end(); fit!=fend; ++fit) { const std::string& fldname = fit->first; const GroupConfig::Field& fld = fit->second; GroupInfo::members_map_t::const_iterator oldgrp(curgroup->members_map.find(fldname)); if(oldgrp!=curgroup->members_map.end()) { const GroupMemberInfo& other = curgroup->members[oldgrp->second]; fprintf(stderr, "%s.%s ignoring duplicate mapping %s%s and %s\n", grpname.c_str(), fldname.c_str(), recbase.c_str(), fld.channel.c_str(), other.pvname.c_str()); continue; } p2p::auto_ptr builder(PVIFBuilder::create(fld.type)); curgroup->members.push_back(GroupMemberInfo(fld.channel.empty() ? fld.channel : recbase + fld.channel, fldname, builder)); curgroup->members.back().structID = fld.id; curgroup->members_map[fldname] = (size_t)-1; // placeholder see below if(PDBProviderDebug>2) { fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n", curgroup->name.c_str(), curgroup->members.back().pvfldname.c_str(), curgroup->members.back().pvname.c_str()); } if(!fld.trigger.empty()) { GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname); if(it==curgroup->triggers.end()) { std::pair ins(curgroup->triggers.insert( std::make_pair(fldname, GroupInfo::triggers_set_t()))); it = ins.first; } Splitter sep(fld.trigger.c_str(), ','); std::string target; while(sep.snip(target)) { curgroup->hastriggers = true; it->second.insert(target); } } } if(grp.atomic_set) { GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False; if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V) fprintf(stderr, " pdb atomic setting inconsistent '%s'\n", curgroup->name.c_str()); curgroup->atomic=V; if(PDBProviderDebug>2) fprintf(stderr, " pdb atomic '%s' %s\n", curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO"); } } }catch(std::exception& e){ fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n", rec.record()->name, e.what()); } #endif // USE_MULTILOCK } // re-sort GroupInfo::members to ensure the shorter names appear first // allows use of 'existing' PVIFBuilder on leaves for(groups_t::iterator it = groups.begin(), end = groups.end(); it!=end; ++it) { GroupInfo& info = it->second; std::sort(info.members.begin(), info.members.end()); info.members_map.clear(); for(size_t i=0, N=info.members.size(); icreateFieldBuilder() ->addNestedStructure("_options") ->add("queueSize", pvd::pvUInt) ->add("atomic", pvd::pvBoolean) ->endNested() ->createStructure()); #ifdef USE_MULTILOCK // assemble group PVD structure definitions and build dbLockers FOREACH(it, end, proc.groups) { GroupInfo &info=it->second; try{ if(persist_pv_map.find(info.name)!=persist_pv_map.end()) throw std::runtime_error("name already in used"); PDBGroupPV::shared_pointer pv(new PDBGroupPV()); pv->weakself = pv; pv->name = info.name; pv->pgatomic = info.atomic!=GroupInfo::False; // default true if Unset pv->monatomic = info.hastriggers; // some gymnastics because Info isn't copyable pvd::shared_vector members; typedef std::map members_map_t; members_map_t members_map; { size_t nchans = 0; for(size_t i=0, N=info.members.size(); i temp(nchans); members.swap(temp); } std::vector records(members.size()); pvd::FieldBuilderPtr builder(fcreate->createFieldBuilder()); builder = builder->add("record", _options); if(!info.structID.empty()) builder = builder->setId(info.structID); for(size_t i=0, J=0, N=info.members.size(); iaddNestedStructureArray(parts[j].name); else builder = builder->addNestedStructure(parts[j].name); } if(!mem.structID.empty()) builder = builder->setId(mem.structID); DBCH chan; if(!mem.pvname.empty()) { DBCH temp(mem.pvname); chan.swap(temp); } builder->add(parts.back().name, mem.builder->dtype(chan)); for(size_t j=0; jendNested(); if(!mem.pvname.empty()) { members_map[mem.pvfldname] = J; PDBGroupPV::Info& info = members[J]; info.builder = PTRMOVE(mem.builder); assert(info.builder.get()); info.attachment.swap(parts); info.chan.swap(chan); // info.triggers populated below assert(info.chan); records[J] = dbChannelRecord(info.chan); J++; } } pv->members.swap(members); pv->fielddesc = builder->createStructure(); pv->complete = pvbuilder->createPVStructure(pv->fielddesc); pv->complete->getSubFieldT("record._options.atomic")->put(pv->monatomic); DBManyLock L(&records[0], records.size(), 0); pv->locker.swap(L); // construct locker for records triggered by each member for(size_t i=0, J=0, N=info.members.size(); imembers[J++]; if(mem.triggers.empty()) continue; std::vector trig_records; trig_records.reserve(mem.triggers.size()); FOREACH(it, end, mem.triggers) { members_map_t::const_iterator imap(members_map.find(*it)); if(imap==members_map.end()) throw std::logic_error("trigger resolution missed map to non-dbChannel"); info.triggers.push_back(imap->second); trig_records.push_back(records[imap->second]); } DBManyLock L(&trig_records[0], trig_records.size(), 0); info.locker.swap(L); } persist_pv_map[info.name] = pv; }catch(std::exception& e){ fprintf(stderr, "%s: Error Group not created: %s\n", info.name.c_str(), e.what()); } } #else if(!proc.groups.empty()) { fprintf(stderr, "Group(s) were defined, but need Base >=3.16.0.2 to function. Ignoring.\n"); } #endif // USE_MULTILOCK event_context = db_init_events(); if(!event_context) throw std::runtime_error("Failed to create dbEvent context"); int ret = db_start_events(event_context, "PDB-event", NULL, NULL, epicsThreadPriorityCAServerLow-1); if(ret!=DB_EVENT_OK) throw std::runtime_error("Failed to stsart dbEvent context"); // setup group monitors #ifdef USE_MULTILOCK for(persist_pv_map_t::iterator next = persist_pv_map.begin(), end = persist_pv_map.end(), it = next!=end ? next++ : end; it != end; it = next==end ? end : next++) { const PDBPV::shared_pointer& ppv = it->second; PDBGroupPV *pv = dynamic_cast(ppv.get()); if(!pv) continue; try { // prepare for monitor size_t i=0; FOREACH(it2, end2, pv->members) { PDBGroupPV::Info& info = *it2; info.evt_VALUE.index = info.evt_PROPERTY.index = i++; info.evt_VALUE.self = info.evt_PROPERTY.self = pv; assert(info.chan); info.pvif.reset(info.builder->attach(info.chan, pv->complete, info.attachment)); // TODO: don't need evt_PROPERTY for PVIF plain info.evt_PROPERTY.create(event_context, info.chan, &pdb_group_event, DBE_PROPERTY); if(!info.triggers.empty()) { info.evt_VALUE.create(event_context, info.chan, &pdb_group_event, DBE_VALUE|DBE_ALARM); } } }catch(std::exception& e){ fprintf(stderr, "%s: Error during dbEvent setup : %s\n", pv->name.c_str(), e.what()); persist_pv_map.erase(it); } } #endif // USE_MULTILOCK epics::atomic::increment(num_instances); } PDBProvider::~PDBProvider() { epics::atomic::decrement(num_instances); destroy(); } void PDBProvider::destroy() { dbEventCtx ctxt = NULL; persist_pv_map_t ppv; { epicsGuard G(transient_pv_map.mutex()); persist_pv_map.swap(ppv); std::swap(ctxt, event_context); } ppv.clear(); // indirectly calls all db_cancel_events() if(ctxt) db_close_events(ctxt); } std::string PDBProvider::getProviderName() { return "PDBProvider"; } namespace { struct ChannelFindRequesterNOOP : public pva::ChannelFind { const pva::ChannelProvider::weak_pointer provider; ChannelFindRequesterNOOP(const pva::ChannelProvider::shared_pointer& prov) : provider(prov) {} virtual ~ChannelFindRequesterNOOP() {} virtual void destroy() {} virtual std::tr1::shared_ptr getChannelProvider() { return provider.lock(); } virtual void cancel() {} }; } pva::ChannelFind::shared_pointer PDBProvider::channelFind(const std::string &channelName, const pva::ChannelFindRequester::shared_pointer &requester) { pva::ChannelFind::shared_pointer ret(new ChannelFindRequesterNOOP(shared_from_this())); bool found = false; { epicsGuard G(transient_pv_map.mutex()); if(persist_pv_map.find(channelName)!=persist_pv_map.end() || transient_pv_map.find(channelName) || dbChannelTest(channelName.c_str())==0) found = true; } requester->channelFindResult(pvd::Status(), ret, found); return ret; } pva::ChannelFind::shared_pointer PDBProvider::channelList(pva::ChannelListRequester::shared_pointer const & requester) { pva::ChannelFind::shared_pointer ret; requester->channelListResult(pvd::Status(pvd::Status::STATUSTYPE_ERROR, "not supported"), ret, pvd::PVStringArray::const_svector(), true); return ret; } pva::Channel::shared_pointer PDBProvider::createChannel(std::string const & channelName, pva::ChannelRequester::shared_pointer const & channelRequester, short priority) { return createChannel(channelName, channelRequester, priority, "???"); } pva::Channel::shared_pointer PDBProvider::createChannel(std::string const & channelName, pva::ChannelRequester::shared_pointer const & requester, short priority, std::string const & address) { pva::Channel::shared_pointer ret; PDBPV::shared_pointer pv; pvd::Status status; { epicsGuard G(transient_pv_map.mutex()); pv = transient_pv_map.find(channelName); if(!pv) { persist_pv_map_t::const_iterator it=persist_pv_map.find(channelName); if(it!=persist_pv_map.end()) { pv = it->second; } } if(!pv) { dbChannel *pchan = dbChannelCreate(channelName.c_str()); if(pchan) { DBCH chan(pchan); pv.reset(new PDBSinglePV(chan, shared_from_this())); transient_pv_map.insert(channelName, pv); PDBSinglePV::shared_pointer spv = std::tr1::static_pointer_cast(pv); spv->weakself = spv; spv->activate(); } } } if(pv) { ret = pv->connect(shared_from_this(), requester); } if(!ret) { status = pvd::Status(pvd::Status::STATUSTYPE_ERROR, "not found"); } requester->channelCreated(status, ret); return ret; } FieldName::FieldName(const std::string& pv) :has_sarr(false) { Splitter S(pv.c_str(), '.'); std::string part; while(S.snip(part)) { if(part.empty()) throw std::runtime_error("Empty field component in: "+pv); if(part[part.size()-1]==']') { const size_t open = part.find_last_of('['), N = part.size(); bool ok = open!=part.npos; epicsUInt32 index = 0; for(size_t i=open+1; ok && i<(N-1); i++) { ok &= part[i]>='0' && part[i]<='9'; index = 10*index + part[i] - '0'; } if(!ok) throw std::runtime_error("Invalid field array sub-script in : "+pv); parts.push_back(Component(part.substr(0, open), index)); has_sarr = true; } else { parts.push_back(Component(part)); } } if(parts.empty()) throw std::runtime_error("Empty field name"); if(parts.back().isArray()) throw std::runtime_error("leaf field may not have sub-script : "+pv); } epics::pvData::PVFieldPtr FieldName::lookup(const epics::pvData::PVStructurePtr& S, epics::pvData::PVField **ppsar) const { if(ppsar) *ppsar = 0; pvd::PVFieldPtr ret = S; for(size_t i=0, N=parts.size(); i(ret.get()); if(!parent) throw std::runtime_error("mid-field is not structure"); ret = parent->getSubFieldT(parts[i].name); if(parts[i].isArray()) { pvd::PVStructureArray* sarr = dynamic_cast(ret.get()); if(!sarr) throw std::runtime_error("indexed field is not structure array"); if(ppsar && !*ppsar) *ppsar = sarr; pvd::PVStructureArray::const_svector V(sarr->view()); if(V.size()<=parts[i].index || !V[parts[i].index]) { // automatic re-size and ensure non-null V.clear(); // drop our extra ref so that reuse() might avoid a copy pvd::PVStructureArray::svector E(sarr->reuse()); if(E.size()<=parts[i].index) E.resize(parts[i].index+1); if(!E[parts[i].index]) E[parts[i].index] = pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure()); ret = E[parts[i].index]; sarr->replace(pvd::freeze(E)); } else { ret = V[parts[i].index]; } } } return ret; }