#include "debug.h" #include "acfg.h" #include "meta.h" #include "filereader.h" #include "fileio.h" #include "sockio.h" #include "lockable.h" #include "cleaner.h" #include #include #include #include #include #include #include #include using namespace std; namespace acng { // hint to use the main configuration excluding the complex directives //bool g_testMode=false; bool bIsHashedPwd=false; #define BARF(x) {if(!g_bQuiet) { cerr << x << endl;} exit(EXIT_FAILURE); } #define BADSTUFF_PATTERN "\\.\\.($|%|/)" namespace rex { bool CompileExpressions(); } namespace cfg { bool g_bQuiet=false, g_bNoComplex=false; extern std::atomic_bool degraded; // internal stuff: string sPopularPath("/debian/"); string tmpDontcache, tmpDontcacheReq, tmpDontcacheTgt, optProxyCheckCmd; int optProxyCheckInt = 99; tStrMap localdirs; static class : public base_with_mutex, public NoCaseStringMap {} mimemap; std::bitset *pUserPorts = nullptr; tHttpUrl proxy_info; struct MapNameToString { const char *name; mstring *ptr; }; struct MapNameToInt { const char *name; int *ptr; const char *warn; uint8_t base; uint8_t hidden; // just a hint }; struct tProperty { const char *name; std::function set; std::function get; // returns a string value. A string starting with # tells to skip the output }; #ifndef MINIBUILD // predeclare some void _ParseLocalDirs(cmstring &value); void AddRemapInfo(bool bAsBackend, const string & token, const string &repname); void AddRemapFlag(const string & token, const string &repname); void _AddHooksFile(cmstring& vname); unsigned ReadBackendsFile(const string & sFile, const string &sRepName); unsigned ReadRewriteFile(const string & sFile, cmstring& sRepName); map repoparms; typedef decltype(repoparms)::iterator tPairRepoNameData; // maps hostname:port -> { , ... } std::unordered_map>> mapUrl2pVname; MapNameToString n2sTbl[] = { { "Port", &port} ,{ "CacheDir", &cachedir} ,{ "LogDir", &logdir} ,{ "SupportDir", &suppdir} ,{ "SocketPath", &fifopath} ,{ "PidFile", &pidfile} ,{ "ReportPage", &reportpage} ,{ "VfilePattern", &vfilepat} ,{ "PfilePattern", &pfilepat} ,{ "SPfilePattern", &spfilepat} ,{ "SVfilePattern", &svfilepat} ,{ "WfilePattern", &wfilepat} ,{ "VfilePatternEx", &vfilepatEx} ,{ "PfilePatternEx", &pfilepatEx} ,{ "WfilePatternEx", &wfilepatEx} ,{ "SPfilePatternEx", &spfilepatEx} ,{ "SVfilePatternEx", &svfilepatEx} // ,{ "AdminAuth", &adminauth} ,{ "BindAddress", &bindaddr} ,{ "UserAgent", &agentname} ,{ "DontCache", &tmpDontcache} ,{ "DontCacheRequested", &tmpDontcacheReq} ,{ "DontCacheResolved", &tmpDontcacheTgt} ,{ "PrecacheFor", &mirrorsrcs} ,{ "RequestAppendix", &requestapx} ,{ "PassThroughPattern", &connectPermPattern} ,{ "CApath", &capath} ,{ "CAfile", &cafile} ,{ "BadRedirDetectMime", &badredmime} ,{ "OptProxyCheckCommand", &optProxyCheckCmd} ,{ "BusAction", &sigbuscmd} // "Special debugging helper, see manual!" }; MapNameToInt n2iTbl[] = { { "Debug", &debug, nullptr, 10, false} ,{ "OfflineMode", &offlinemode, nullptr, 10, false} ,{ "ForeGround", &foreground, nullptr, 10, false} ,{ "ForceManaged", &forcemanaged, nullptr, 10, false} ,{ "StupidFs", &stupidfs, nullptr, 10, false} ,{ "VerboseLog", &verboselog, nullptr, 10, false} ,{ "ExThreshold", &extreshhold, nullptr, 10, false} ,{ "ExTreshold", &extreshhold, nullptr, 10, true} // wrong spelling :-( ,{ "MaxStandbyConThreads", &tpstandbymax, nullptr, 10, false} ,{ "MaxConThreads", &tpthreadmax, nullptr, 10, false} ,{ "DnsCacheSeconds", &dnscachetime, nullptr, 10, false} ,{ "UnbufferLogs", &debug, nullptr, 10, false} ,{ "ExAbortOnProblems", &exfailabort, nullptr, 10, false} ,{ "ExposeOrigin", &exporigin, nullptr, 10, false} ,{ "LogSubmittedOrigin", &logxff, nullptr, 10, false} ,{ "RecompBz2", &recompbz2, nullptr, 10, false} ,{ "NetworkTimeout", &nettimeout, nullptr, 10, false} ,{ "MinUpdateInterval", &updinterval, nullptr, 10, false} ,{ "ForwardBtsSoap", &forwardsoap, nullptr, 10, false} ,{ "KeepExtraVersions", &keepnver, nullptr, 10, false} ,{ "UseWrap", &usewrap, nullptr, 10, false} ,{ "FreshIndexMaxAge", &maxtempdelay, nullptr, 10, false} ,{ "RedirMax", &redirmax, nullptr, 10, false} ,{ "VfileUseRangeOps", &vrangeops, nullptr, 10, false} ,{ "ResponseFreezeDetectTime", &stucksecs, nullptr, 10, false} ,{ "ReuseConnections", &persistoutgoing, nullptr, 10, false} ,{ "PipelineDepth", &pipelinelen, nullptr, 10, false} ,{ "ExSuppressAdminNotification", &exsupcount, nullptr, 10, false} ,{ "OptProxyTimeout", &optproxytimeout, nullptr, 10, false} ,{ "MaxDlSpeed", &maxdlspeed, nullptr, 10, false} ,{ "MaxInresponsiveDlSize", &maxredlsize, nullptr, 10, false} ,{ "OptProxyCheckInterval", &optProxyCheckInt, nullptr, 10, false} ,{ "TrackFileUse", &trackfileuse, nullptr, 10, false} ,{ "ReserveSpace", &allocspace, nullptr , 10, false} // octal base interpretation of UNIX file permissions ,{ "DirPerms", &dirperms, nullptr, 8, false} ,{ "FilePerms", &fileperms, nullptr, 8, false} ,{ "Verbose", nullptr, "Option is deprecated, ignoring the value." , 10, true} ,{ "MaxSpareThreadSets",&tpstandbymax, "Deprecated option name, mapped to MaxStandbyConThreads", 10, true} ,{ "OldIndexUpdater", &oldupdate, "Option is deprecated, ignoring the value." , 10, true} ,{ "Patrace", &patrace, "Don't use in config files!" , 10, false} ,{ "NoSSLchecks", &nsafriendly, "Disable SSL security checks" , 10, false} }; tProperty n2pTbl[] = { { "Proxy", [](cmstring& key, cmstring& value) { if(value.empty()) proxy_info=tHttpUrl(); else { if (!proxy_info.SetHttpUrl(value) || proxy_info.sHost.empty()) BARF("Invalid proxy specification, aborting..."); } return true; }, [](bool superUser) -> string { if(!superUser && !proxy_info.sUserPass.empty()) return string("#"); return proxy_info.sHost.empty() ? sEmptyString : proxy_info.ToURI(false); } }, { "LocalDirs", [](cmstring& key, cmstring& value) -> bool { if(g_bNoComplex) return true; _ParseLocalDirs(value); return !localdirs.empty(); }, [](bool) -> string { string ret; for(auto kv : localdirs) ret += kv.first + " " + kv.second + "; "; return ret; } }, { "Remap-", [](cmstring& key, cmstring& value) -> bool { if(g_bNoComplex) return true; string vname=key.substr(6, key.npos); if(vname.empty()) { if(!g_bQuiet) cerr << "Bad repository name in " << key << endl; return false; } int type(-1); // nothing =-1; prefixes =0 ; backends =1; flags =2 for(tSplitWalk split(&value); split.Next();) { cmstring s(split); if(s.empty()) continue; if(s.at(0)=='#') break; if(type<0) type=0; if(s.at(0)==';') ++type; else if(0 == type) AddRemapInfo(false, s, vname); else if(1 == type) AddRemapInfo(true, s, vname); else if(2 == type) AddRemapFlag(s, vname); } if(type<0) { if(!g_bQuiet) cerr << "Invalid entry, no configuration: " << key << ": " << value < string { return "# mixed options"; } }, { "AllowUserPorts", [](cmstring& key, cmstring& value) -> bool { if(!pUserPorts) pUserPorts=new bitset; for(tSplitWalk split(&value); split.Next();) { cmstring s(split); const char *start(s.c_str()); char *p(0); unsigned long n=strtoul(start, &p, 10); if(n>=TCP_PORT_MAX || !p || '\0' != *p || p == start) BARF("Bad port in AllowUserPorts: " << start); if(n == 0) { pUserPorts->set(); break; } pUserPorts->set(n, true); } return true; }, [](bool) -> string { tSS ret; if(pUserPorts) { for(auto i=0; i bool { int *p = conprotos; for (tSplitWalk split(&value); split.Next(); ++p) { cmstring val(split); if (val.empty()) break; if (p >= conprotos + _countof(conprotos)) BARF("Too many protocols specified: " << val); if (val == "v6") *p = PF_INET6; else if (val == "v4") *p = PF_INET; else BARF("IP protocol not supported: " << val); } return true; }, [](bool) -> string { string ret(conprotos[0] == PF_INET6 ? "v6" : "v4"); if(conprotos[0] != conprotos[1]) ret += string(" ") + (conprotos[1] == PF_INET6 ? "v6" : "v4"); return ret; } }, { "AdminAuth", [](cmstring& key, cmstring& value) -> bool { adminauth=value; adminauthB64=EncodeBase64Auth(value); return true; }, [](bool) -> string { return "#"; // TOP SECRET"; } } , { "ExStartTradeOff", [](cmstring& key, cmstring& value) -> bool { exstarttradeoff = strsizeToOfft(value.c_str()); return true; }, [](bool) -> string { return ltos(exstarttradeoff); } } #if SUPPWHASH bIsHashedPwd=false; } else if(CHECKOPTKEY("AdminAuthHash")) { adminauth=value; bIsHashedPwd=true; #endif }; string * GetStringPtr(LPCSTR key) { for(auto &ent : n2sTbl) if(0==strcasecmp(key, ent.name)) return ent.ptr; return nullptr; } int * GetIntPtr(LPCSTR key, int &base) { for(auto &ent : n2iTbl) { if(0==strcasecmp(key, ent.name)) { if(ent.warn) cerr << "Warning, " << key << ": " << ent.warn << endl; base = ent.base; return ent.ptr; } } return nullptr; } tProperty* GetPropPtr(cmstring& key) { auto sep = key.find('-'); auto szkey = key.c_str(); for (auto &ent : n2pTbl) { if (0 == strcasecmp(szkey, ent.name)) return &ent; // identified as prefix, with matching length? if(sep != stmiss && 0==strncasecmp(szkey, ent.name, sep) && 0 == ent.name[sep+1]) return &ent; } return nullptr; } int * GetIntPtr(LPCSTR key) { for(auto &ent : n2iTbl) if(0==strcasecmp(key, ent.name)) return ent.ptr; return nullptr; } // shortcut for frequently needed code, opens the config file, reads step-by-step // and skips comment and empty lines struct tCfgIter { filereader reader; string sLine; string sFilename; tCfgIter(cmstring &fn) : sFilename(fn) { reader.OpenFile(fn, false, 1); } inline operator bool() const { return reader.CheckGoodState(false, &sFilename); } inline bool Next() { while(reader.GetOneLine(sLine)) { trimFront(sLine); if(sLine.empty() || sLine[0] == '#') continue; return true; } return false; } }; inline bool qgrep(cmstring &needle, cmstring &file) { for(cfg::tCfgIter itor(file); itor.Next();) if(StrHas(itor.sLine, needle)) return true; return false; } bool DegradedMode() { return degraded.load(); } inline void _FixPostPreSlashes(string &val) { // fix broken entries if (val.empty() || val.at(val.length()-1) != '/') val.append("/"); if (val.at(0) != '/') val.insert(0, "/", 1); } bool ReadOneConfFile(const string & szFilename, bool bReadErrorIsFatal=true) { tCfgIter itor(szFilename); itor.reader.CheckGoodState(bReadErrorIsFatal, &szFilename); NoCaseStringMap dupeCheck; while(itor.Next()) { #ifdef DEBUG cerr << itor.sLine < alt_proxies; tHttpUrl cand; if(value.empty() || cand.SetHttpUrl(value)) { alt_proxies.emplace_back(cand); where.m_pProxy = & alt_proxies.back(); } else { cerr << "Warning, failed to parse proxy setting " << value << " , " << endl << "ignoring it" <= --m_nRefCnt) { //system(cmdRel.c_str()); downTimeNext = ::time(0) + downDuration; g_victor.ScheduleFor(downTimeNext, cleaner::TYPE_ACFGHOOKS); } } virtual void OnAccess() override { setLockGuard; if (0 == m_nRefCnt++) { if(downTimeNext) // huh, already ticking? reset downTimeNext=0; else if(system(cmdCon.c_str())) log::err(tSS() << "Warning: " << cmdCon << " returned with error code."); } } }; inline void _AddHooksFile(cmstring& vname) { tCfgIter itor(cfg::confdir+"/"+vname+".hooks"); if(!itor) return; struct tHookHandler &hs = *(new tHookHandler(vname)); mstring key,val; while (itor.Next()) { if(!ParseOptionLine(itor.sLine, key, val)) continue; const char *p = key.c_str(); if (strcasecmp("PreUp", p) == 0) { hs.cmdCon = val; } else if (strcasecmp("Down", p) == 0) { hs.cmdRel = val; } else if (strcasecmp("DownTimeout", p) == 0) { errno = 0; unsigned n = strtoul(val.c_str(), nullptr, 10); if (!errno) hs.downDuration = n; } } repoparms[vname].m_pHooks = &hs; } inline void _ParseLocalDirs(cmstring &value) { for(tSplitWalk splitter(&value, ";"); splitter.Next(); ) { mstring token=splitter.str(); trimString(token); tStrPos pos = token.find_first_of(SPACECHARS); if(stmiss == pos) { cerr << "Cannot map " << token << ", needed format: virtualdir realdir, ignoring it"; continue; } string from(token, 0, pos); trimString(from, "/"); string what(token, pos); trimString(what, SPACECHARS "'\""); if(what.empty()) { cerr << "Unsupported target of " << from << ": " << what << ", ignoring it" << endl; continue; } localdirs[from]=what; } } cmstring & GetMimeType(cmstring &path) { { lockguard g(mimemap); static bool inited = false; if (!inited) { inited = true; for (tCfgIter itor("/etc/mime.types"); itor.Next();) { // # regular types: // text/plain asc txt text pot brf # plain ascii files tSplitWalk split(&itor.sLine); if (!split.Next()) continue; mstring mimetype = split; if (startsWithSz(mimetype, "#")) continue; while (split.Next()) { mstring suf = split; if (startsWithSz(suf, "#")) break; mimemap[suf] = mimetype; } } } } tStrPos dpos = path.find_last_of('.'); if (dpos != stmiss) { NoCaseStringMap::const_iterator it = cfg::mimemap.find(path.substr( dpos + 1)); if (it != cfg::mimemap.end()) return it->second; } // try some educated guess... assume binary if we are sure, text if we are almost sure static cmstring os("application/octet-stream"), tp("text/plain"); filereader f; if(f.OpenFile(path, true)) { size_t maxLen = std::min(size_t(255), f.GetSize()); for(unsigned i=0; i< maxLen; ++i) { if(!isascii((uint) *(f.GetBuffer()+i))) return os; } return tp; } return sEmptyString; } bool SetOption(const string &sLine, NoCaseStringMap *pDupeCheck) { string key, value; if(!ParseOptionLine(sLine, key, value)) return false; string * psTarget; int * pnTarget; tProperty * ppTarget; int nNumBase(10); if ( nullptr != (psTarget = GetStringPtr(key.c_str()))) { if(pDupeCheck && !g_bQuiet) { mstring &w = (*pDupeCheck)[key]; if(w.empty()) w = value; else cerr << "WARNING: " << key << " was previously set to " << w << endl; } *psTarget=value; } else if ( nullptr != (pnTarget = GetIntPtr(key.c_str(), nNumBase))) { if(pDupeCheck && !g_bQuiet) { mstring &w = (*pDupeCheck)[key]; if(w.empty()) w = value; else cerr << "WARNING: " << key << " was already set to " << w << endl; } const char *pStart=value.c_str(); if(! *pStart) { cerr << "Missing value for " << key << " option!" <set(key, value); } else { if(!g_bQuiet) cerr << "Warning, unknown configuration directive: " << key <second) { // rewrite rule path must be a real prefix // it's also surrounded by /, ensured during construction const string & prefix=repo.first; // path of the rewrite entry tStrPos len=prefix.length(); if (len>bestMatchLen && in.sPath.size() > len && 0==in.sPath.compare(0, len, prefix)) { bestMatchLen=len; pBestHit=repo.second; } } if(pBestHit != repoparms.end()) { result.psRepoName = & pBestHit->first; result.sRestPath = in.sPath.substr(bestMatchLen); result.repodata = & pBestHit->second; } } const tRepoData * GetRepoData(cmstring &vname) { auto it=repoparms.find(vname); if(it==repoparms.end()) return nullptr; return & it->second; } unsigned ReadBackendsFile(const string & sFile, const string &sRepName) { unsigned nAddCount=0; string key, val; tHttpUrl entry; tCfgIter itor(sFile); if(debug&6) cerr << "Reading backend file: " << sFile <4) cerr << "Reading rewrite file: " << sFile < " << sRepName << endl; #endif ++nAddCount; continue; } // otherwise deal with the complicated RFC-822 format for legacy reasons if (sLine.empty()) // end of block, eof, ... -> commit it { if (hosts.empty() && paths.empty()) continue; // dummy run or whitespace in a URL style list if ( !hosts.empty() && paths.empty()) { cerr << "Warning, missing path spec for the site " << hosts[0] <<", ignoring mirror."<< endl; continue; } if ( !paths.empty() && hosts.empty()) { BARF("Parse error, missing Site: field around line " << sFile << ":"<< reader.GetCurrentLine()); } for (const auto& host : hosts) { for (const auto& path : paths) { //mapUrl2pVname[*itHost+*itPath]= &itHostiVec->first; tHttpUrl url; url.sHost=host; url.sPath=path; mapUrl2pVname[url.sHost+":"+url.GetPort()].emplace_back(url.sPath, GetRepoEntryRef(sRepName)); #ifdef DEBUG cerr << "Mapping: "<< host << path << " -> "<< sRepName <4) BARF("Invalid umask length\n"); for(unsigned int i=0; i7) BARF("Invalid umask value\n"); nUmask |= (val<<(3*i)); } //cerr << "Got umask: " << nUmask <= 5); #ifdef _SC_NPROCESSORS_ONLN numcores = (int) sysconf(_SC_NPROCESSORS_ONLN); #elif defined(_SC_NPROC_ONLN) numcores = (int) sysconf(_SC_NPROC_ONLN); #endif if(!rex::CompileUncExpressions(rex::NOCACHE_REQ, tmpDontcacheReq.empty() ? tmpDontcache : tmpDontcacheReq) || !rex::CompileUncExpressions(rex::NOCACHE_TGT, tmpDontcacheTgt.empty() ? tmpDontcache : tmpDontcacheTgt)) { BARF("An error occurred while compiling regular expression for non-cached paths!"); } tmpDontcache.clear(); tmpDontcacheTgt.clear(); tmpDontcacheReq.clear(); if(usewrap == RESERVED_DEFVAL) { usewrap=(qgrep("apt-cacher-ng", "/etc/hosts.deny") || qgrep("apt-cacher-ng", "/etc/hosts.allow")); #ifndef HAVE_LIBWRAP cerr << "Warning: configured to use libwrap filters but feature is not built-in." <first); p->downDuration=10; p->cmdCon = "logger wanna/connect"; p->cmdRel = "logger wanna/disconnect"; it->second.m_pHooks = p; } if(debug == -42) { /* for(tMapString2Hostivec::const_iterator it=mapRepName2Backends.begin(); it!=mapRepName2Backends.end(); it++) { for(tRepoData::const_iterator jit=it->second.begin(); jit != it->second.end(); jit++) { cout << jit->ToURI() <first.ToURI(false) << " ___" << *(it->second) << endl; } exit(1); */ } #endif } //! @brief Fires hook callbacks in the background thread time_t BackgroundCleanup() { time_t ret(END_OF_TIME), now(time(0)); for (const auto& parm : repoparms) { if (!parm.second.m_pHooks) continue; tHookHandler & hooks = *(static_cast (parm.second.m_pHooks)); lockguard g(hooks); if (hooks.downTimeNext) { if (hooks.downTimeNext <= now) // time to execute { if(cfg::debug & log::LOG_MORE) log::misc(hooks.cmdRel, 'X'); if(cfg::debug & log::LOG_FLUSH) log::flush(); if(system(hooks.cmdRel.c_str())) log::err(tSS() << "Warning: " << hooks.cmdRel << " returned with error code."); hooks.downTimeNext = 0; } else // in future, use the soonest time ret = min(ret, hooks.downTimeNext); } } return ret; } acmutex authLock; int CheckAdminAuth(LPCSTR auth) { if(cfg::adminauthB64.empty()) return 0; if(!auth || !*auth) return 1; // request it from user if(strncmp(auth, "Basic", 5)) return -1; // looks like crap auto p=auth+5; while(*p && isspace((uint) *p)) ++p; #ifndef SUPPWHASH return adminauthB64.compare(p) == 0 ? 0 : 1; #else if(!bIsHashedPwd) return adminauth.compare(p) == 0 ? 0 : 1; #ifndef HAVE_SSL #warning You really want to add SSL support in order to support hashed passwords return -1; #endif acbuf bufDecoded; if(!DecodeBase64(p, bufDecoded)) return -1; #if 0 // there is always a char reserved cerr << "huhu, user sent: " << bufDecoded.c_str() < optProxyCheckInt) { last_check = now; if(optProxyCheckCmd.empty()) proxy_failstate = false; else proxy_failstate = (bool) system(optProxyCheckCmd.c_str()); } return proxy_failstate ? nullptr : &proxy_info; } void MarkProxyFailure() { lockguard g(proxy_fail_lock); if(optProxyCheckInt <= 0) // urgs, would never recover return; proxy_failstate = true; } } // namespace acfg namespace rex { // this has the exact order of the "regular" types in the enum struct { regex_t *pat=nullptr, *extra=nullptr; } rex[ematchtype_max]; vector vecReqPatters, vecTgtPatterns; bool CompileExpressions() { auto compat = [](regex_t* &re, LPCSTR ps) { if(!ps ||! *ps ) return true; re=new regex_t; int nErr=regcomp(re, ps, REG_EXTENDED); if(!nErr) return true; char buf[1024]; regerror(nErr, re, buf, sizeof(buf)); delete re; re=nullptr; buf[_countof(buf)-1]=0; // better be safe... std::cerr << buf << ": " << ps << std::endl; return false; }; using namespace cfg; return (compat(rex[FILE_SOLID].pat, pfilepat.c_str()) && compat(rex[FILE_VOLATILE].pat, vfilepat.c_str()) && compat(rex[FILE_WHITELIST].pat, wfilepat.c_str()) && compat(rex[FILE_SOLID].extra, pfilepatEx.c_str()) && compat(rex[FILE_VOLATILE].extra, vfilepatEx.c_str()) && compat(rex[FILE_WHITELIST].extra, wfilepatEx.c_str()) && compat(rex[NASTY_PATH].pat, BADSTUFF_PATTERN) && compat(rex[FILE_SPECIAL_SOLID].pat, spfilepat.c_str()) && compat(rex[FILE_SPECIAL_SOLID].extra, spfilepatEx.c_str()) && compat(rex[FILE_SPECIAL_VOLATILE].pat, svfilepat.c_str()) && compat(rex[FILE_SPECIAL_VOLATILE].extra, svfilepatEx.c_str()) && (connectPermPattern == "~~~" ? true : compat(rex[PASSTHROUGH].pat, connectPermPattern.c_str()))); } // match the specified type by internal pattern PLUS the user-added pattern inline bool MatchType(cmstring &in, eMatchType type) { if(rex[type].pat && !regexec(rex[type].pat, in.c_str(), 0, nullptr, 0)) return true; if(rex[type].extra && !regexec(rex[type].extra, in.c_str(), 0, nullptr, 0)) return true; return false; } bool Match(cmstring &in, eMatchType type) { if(MatchType(in, type)) return true; // very special behavior... for convenience return (type == FILE_SOLID && MatchType(in, FILE_SPECIAL_SOLID)) || (type == FILE_VOLATILE && MatchType(in, FILE_SPECIAL_VOLATILE)); } eMatchType GetFiletype(const string & in) { if (MatchType(in, FILE_SPECIAL_VOLATILE)) return FILE_VOLATILE; if (MatchType(in, FILE_SPECIAL_SOLID)) return FILE_SOLID; if (MatchType(in, FILE_VOLATILE)) return FILE_VOLATILE; if (MatchType(in, FILE_SOLID)) return FILE_SOLID; return FILE_INVALID; } #ifndef MINIBUILD inline bool CompileUncachedRex(const string & token, NOCACHE_PATTYPE type, bool bRecursiveCall) { auto & patvec = (NOCACHE_TGT == type) ? vecTgtPatterns : vecReqPatters; if (0!=token.compare(0, 5, "file:")) // pure pattern { unsigned pos = patvec.size(); patvec.resize(pos+1); return 0==regcomp(&patvec[pos], token.c_str(), REG_EXTENDED); } else if(!bRecursiveCall) // don't go further than one level { tStrDeq srcs = cfg::ExpandFileTokens(token); for(const auto& src: srcs) { cfg::tCfgIter itor(src); if(!itor) { cerr << "Error opening pattern file: " << src <=rex::ematchtype_max) return "NOMATCH"; return names[t]; } #endif }