1 /*  $Id: metareg.cpp 593248 2019-09-16 12:18:31Z grichenk $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author:  Aaron Ucko
27  *
28  * File Description:
29  *   CMetaRegistry
30  *
31  * ===========================================================================
32  */
33 
34 #include <ncbi_pch.hpp>
35 #include <corelib/metareg.hpp>
36 #include <corelib/ncbifile.hpp>
37 #include <corelib/ncbi_safe_static.hpp>
38 #include "ncbisys.hpp"
39 
40 // strstream (aka CNcbiStrstream) remains the default for historical
41 // reasons; however, MIPSpro's implementation is buggy, yielding
42 // truncated results in some cases. :-/
43 #ifdef NCBI_COMPILER_MIPSPRO
44 #  include <sstream>
45 typedef std::stringstream TStrStream;
46 #else
47 typedef ncbi::CNcbiStrstream TStrStream;
48 #endif
49 
50 BEGIN_NCBI_SCOPE
51 
52 
53 #ifdef NCBI_OS_MSWIN
54 static const CTempString kConfigPathDelim = ";";
55 #else
56 static const CTempString kConfigPathDelim = ":;";
57 #endif
58 
59 static CSafeStatic<CMetaRegistry> s_Instance;
60 
61 
Reload(CMetaRegistry::TFlags reload_flags)62 bool CMetaRegistry::SEntry::Reload(CMetaRegistry::TFlags reload_flags)
63 {
64     CFile file(actual_name);
65     if ( !file.Exists() ) {
66         _TRACE("No such registry file " << actual_name);
67         return false;
68     }
69     CMutexGuard GUARD(s_Instance->m_Mutex);
70     Int8  new_length = file.GetLength();
71     CTime new_timestamp;
72     file.GetTime(&new_timestamp);
73     if ( ((reload_flags & fAlwaysReload) != fAlwaysReload)
74          &&  new_length == length  &&  new_timestamp == timestamp ) {
75         _TRACE("Registry file " << actual_name
76                << " appears not to have changed since last loaded");
77         return false;
78     }
79     CNcbiIfstream ifs(actual_name.c_str(), IOS_BASE::in | IOS_BASE::binary);
80     if ( !ifs.good() ) {
81         _TRACE("Unable to (re)open registry file " << actual_name);
82         return false;
83     }
84     IRWRegistry* dest = NULL;
85     if (registry) {
86         CRegistryWriteGuard REG_GUARD(*registry);
87         TRegFlags rflags = IRWRegistry::AssessImpact(reg_flags,
88                                                      IRWRegistry::eRead);
89         if ((reload_flags & fKeepContents)  ||  registry->Empty(rflags)) {
90             dest = registry->Read(ifs, reg_flags | IRegistry::fJustCore);
91         } else {
92             // Go through a temporary so errors (exceptions) won't
93             // cause *registry to be incomplete.
94             CMemoryRegistry tmp_reg(reg_flags & IRegistry::fCaseFlags);
95             TStrStream      str;
96             tmp_reg.Read(ifs, reg_flags);
97             tmp_reg.Write(str, reg_flags);
98             str.seekg(0);
99             bool was_modified = registry->Modified(rflags);
100             registry->Clear(rflags);
101             dest = registry->Read(str, reg_flags | IRegistry::fJustCore);
102             if ( !was_modified ) {
103                 registry->SetModifiedFlag(false, rflags);
104             }
105         }
106 
107         if (dest) {
108             dest->WriteLock();
109         } else {
110             dest = registry.GetPointer();
111         }
112     } else {
113         registry.Reset(new CNcbiRegistry(ifs, reg_flags, file.GetDir()));
114     }
115 
116     CCompoundRWRegistry* crwreg = dynamic_cast<CCompoundRWRegistry*>(dest);
117     if (crwreg) {
118         crwreg->LoadBaseRegistries(reg_flags, reload_flags, file.GetDir());
119     }
120 
121     timestamp = new_timestamp;
122     length    = new_length;
123     return true;
124 }
125 
126 
Instance(void)127 CMetaRegistry& CMetaRegistry::Instance(void)
128 {
129     return *s_Instance;
130 }
131 
132 
~CMetaRegistry()133 CMetaRegistry::~CMetaRegistry()
134 {
135     // XX - optionally save modified registries?
136 }
137 
138 
Load(const string & name,CMetaRegistry::ENameStyle style,CMetaRegistry::TFlags flags,IRegistry::TFlags reg_flags,IRWRegistry * reg,const string & path)139 CMetaRegistry::SEntry CMetaRegistry::Load(const string& name,
140                                           CMetaRegistry::ENameStyle style,
141                                           CMetaRegistry::TFlags flags,
142                                           IRegistry::TFlags reg_flags,
143                                           IRWRegistry* reg,
144                                           const string& path)
145 {
146     SEntry scratch_entry;
147     if ( reg  &&  !reg->Empty() ) { // shouldn't share
148         flags |= fPrivate;
149     }
150     const SEntry& entry = Instance().x_Load(name, style, flags, reg_flags, reg,
151                                             name, style, scratch_entry, path);
152     if (reg  &&  entry.registry  &&  reg != entry.registry.GetPointer()) {
153         _ASSERT( !(flags & fPrivate) );
154         // Copy the relevant data in
155         if (&entry != &scratch_entry) {
156             scratch_entry = entry;
157         }
158         TRegFlags rflags = IRWRegistry::AssessImpact(reg_flags,
159                                                      IRWRegistry::eRead);
160         TStrStream str;
161         entry.registry->Write(str, rflags);
162         str.seekg(0);
163         CRegistryWriteGuard REG_GUARD(*reg);
164         if ( !(flags & fKeepContents) ) {
165             bool was_modified = reg->Modified(rflags);
166             reg->Clear(rflags);
167             if ( !was_modified ) {
168                 reg->SetModifiedFlag(false, rflags);
169             }
170         }
171         reg->Read(str, reg_flags | IRegistry::fJustCore);
172         scratch_entry.registry.Reset(reg);
173         CCompoundRWRegistry* crwreg = dynamic_cast<CCompoundRWRegistry*>(reg);
174         if (crwreg) {
175             REG_GUARD.Release();
176             string dir;
177             CDirEntry::SplitPath(scratch_entry.actual_name, &dir);
178             crwreg->LoadBaseRegistries(reg_flags, flags, dir);
179         }
180         return scratch_entry;
181     }
182     return entry;
183 }
184 
185 
186 const CMetaRegistry::SEntry&
x_Load(const string & name,CMetaRegistry::ENameStyle style,CMetaRegistry::TFlags flags,IRegistry::TFlags reg_flags,IRWRegistry * reg,const string & name0,CMetaRegistry::ENameStyle style0,CMetaRegistry::SEntry & scratch_entry,const string & path)187 CMetaRegistry::x_Load(const string& name, CMetaRegistry::ENameStyle style,
188                       CMetaRegistry::TFlags flags,
189                       IRegistry::TFlags reg_flags, IRWRegistry* reg,
190                       const string& name0, CMetaRegistry::ENameStyle style0,
191                       CMetaRegistry::SEntry& scratch_entry, const string& path)
192 {
193     _TRACE("CMetaRegistry::Load: looking for " << name);
194 
195     CMutexGuard GUARD(m_Mutex);
196 
197     if (flags & fPrivate) {
198         GUARD.Release();
199     }
200     else { // see if we already have it
201         TIndex::const_iterator iit
202             = m_Index.find(SKey(name, style, flags, reg_flags));
203         if (iit != m_Index.end()) {
204             _TRACE("found in cache");
205             _ASSERT(iit->second < m_Contents.size());
206             SEntry& result = m_Contents[iit->second];
207             result.Reload(flags);
208             return result;
209         }
210 
211         NON_CONST_ITERATE (vector<SEntry>, it, m_Contents) {
212             if (it->flags != flags  ||  it->reg_flags != reg_flags)
213                 continue;
214 
215             if (style == eName_AsIs  &&  it->actual_name == name) {
216                 _TRACE("found in cache");
217                 it->Reload(flags);
218                 return *it;
219             }
220         }
221     }
222 
223     scratch_entry.actual_name = x_FindRegistry(name, style, path);
224     scratch_entry.flags       = flags;
225     scratch_entry.reg_flags   = reg_flags;
226     scratch_entry.registry.Reset(reg);
227     scratch_entry.length      = 0;
228     if (scratch_entry.actual_name.empty()
229         ||  !scratch_entry.Reload(flags | fAlwaysReload | fKeepContents) ) {
230         scratch_entry.registry.Reset();
231         return scratch_entry;
232     } else if (flags & fPrivate) {
233         return scratch_entry;
234     } else {
235         m_Contents.push_back(scratch_entry);
236         m_Index[SKey(name0, style0, flags, reg_flags)]
237             = m_Contents.size() - 1;
238         return m_Contents.back();
239     }
240 }
241 
242 
x_FindRegistry(const string & name,ENameStyle style,const string & path)243 string CMetaRegistry::x_FindRegistry(const string& name, ENameStyle style,
244                                      const string& path)
245 {
246     _TRACE("CMetaRegistry::FindRegistry: looking for " << name);
247 
248     if ( !path.empty()  &&   !CDirEntry::IsAbsolutePath(name) ) {
249         const string& result
250             = x_FindRegistry(CDirEntry::ConcatPath(path, name), style);
251         if ( !result.empty() ) {
252             return result;
253         }
254     }
255 
256     string dir;
257     CDirEntry::SplitPath(name, &dir, 0, 0);
258     if ( dir.empty() ) {
259         ITERATE (TSearchPath, it, m_SearchPath) {
260             const string& result
261                 = x_FindRegistry(CDirEntry::MakePath(*it, name), style);
262             if ( !result.empty() ) {
263                 return result;
264             }
265         }
266     } else {
267         switch (style) {
268         case eName_AsIs:
269             if (CFile(name).Exists()) {
270                 string abs_name;
271                 if ( CDirEntry::IsAbsolutePath(name) ) {
272                     abs_name = name;
273                 } else {
274                     abs_name = CDirEntry::ConcatPath(CDir::GetCwd(), name);
275                 }
276                 return CDirEntry::NormalizePath(abs_name);
277             }
278             break;
279         case eName_Ini:
280             for (string name2(name); ; ) {
281                 string result = x_FindRegistry(name2 + ".ini", eName_AsIs);
282                 if ( !result.empty() ) {
283                     return result;
284                 }
285 
286                 string base, ext; // dir already known
287                 CDirEntry::SplitPath(name2, 0, &base, &ext);
288                 if ( ext.empty() ) {
289                     break;
290                 }
291                 name2 = CDirEntry::MakePath(dir, base);
292             }
293             break;
294         case eName_DotRc: {
295             string base, ext;
296             CDirEntry::SplitPath(name, 0, &base, &ext);
297             return x_FindRegistry(CDirEntry::MakePath(dir, '.' + base, ext)
298                                   + "rc", eName_AsIs);
299         }
300         }  // switch (style)
301     }
302     return kEmptyStr;
303 }
304 
305 
x_Reload(const string & path,IRWRegistry & reg,TFlags flags,TRegFlags reg_flags)306 bool CMetaRegistry::x_Reload(const string& path, IRWRegistry& reg,
307                              TFlags flags, TRegFlags reg_flags)
308 {
309     SEntry* entryp = 0;
310     NON_CONST_ITERATE (vector<SEntry>, it, m_Contents) {
311         if (it->registry == &reg  ||  it->actual_name == path) {
312             entryp = &*it;
313             break;
314         }
315     }
316     if (entryp) {
317         return entryp->Reload(flags);
318     } else {
319         SEntry entry = Load(path, eName_AsIs, flags, reg_flags, &reg);
320         _ASSERT(entry.registry.IsNull()  ||  entry.registry == &reg);
321         return !entry.registry.IsNull();
322     }
323 }
324 
325 
GetDefaultSearchPath(CMetaRegistry::TSearchPath & path)326 void CMetaRegistry::GetDefaultSearchPath(CMetaRegistry::TSearchPath& path)
327 {
328     path.clear();
329 
330     const TXChar* cfg_path = NcbiSys_getenv(_TX("NCBI_CONFIG_PATH"));
331     TSearchPath   path_tail;
332     if (cfg_path) {
333         NStr::Split(_T_STDSTRING(cfg_path), kConfigPathDelim, path);
334         TSearchPath::iterator it = find(path.begin(), path.end(), kEmptyStr);
335         if (it == path.end()) {
336             return;
337         } else {
338             path_tail.assign(it + 1, path.end());
339             path.erase(it, path.end());
340         }
341     }
342 
343     if (NcbiSys_getenv(_TX("NCBI_DONT_USE_LOCAL_CONFIG")) == NULL) {
344         path.push_back(".");
345         string home = CDir::GetHome();
346         if ( !home.empty() ) {
347             path.push_back(home);
348         }
349     }
350 
351     {{
352         const TXChar* ncbi = NcbiSys_getenv(_TX("NCBI"));
353         if (ncbi  &&  *ncbi) {
354             path.push_back(_T_STDSTRING(ncbi));
355         }
356     }}
357 
358 #ifdef NCBI_OS_MSWIN
359     {{
360         const TXChar* sysroot = NcbiSys_getenv(_TX("SYSTEMROOT"));
361         if (sysroot  &&  *sysroot) {
362             path.push_back(_T_STDSTRING(sysroot));
363         }
364     }}
365 #else
366     path.push_back("/etc");
367 #endif
368 
369     {{
370         CNcbiApplicationGuard the_app = CNcbiApplication::InstanceGuard();
371         if ( the_app ) {
372             const CNcbiArguments& args = the_app->GetArguments();
373             string                dir  = args.GetProgramDirname(eIgnoreLinks);
374             string                dir2 = args.GetProgramDirname(eFollowLinks);
375             if (dir.size()) {
376                 path.push_back(dir);
377             }
378             if (dir2.size() && dir2 != dir) {
379                 path.push_back(dir2);
380             }
381         }
382     }}
383 
384     if ( !path_tail.empty() ) {
385         ITERATE (TSearchPath, it, path_tail) {
386             if ( !it->empty() ) {
387                 path.push_back(*it);
388             }
389         }
390     }
391 }
392 
393 
operator <(const SKey & k) const394 bool CMetaRegistry::SKey::operator <(const SKey& k) const
395 {
396     if (requested_name < k.requested_name) {
397         return true;
398     } else if (requested_name > k.requested_name) {
399         return false;
400     }
401 
402     if (style < k.style) {
403         return true;
404     } else if (style > k.style) {
405         return false;
406     }
407 
408     if (flags < k.flags) {
409         return true;
410     } else if (flags > k.flags) {
411         return false;
412     }
413 
414     if (reg_flags < k.reg_flags) {
415         return true;
416     } else if (reg_flags > k.reg_flags) {
417         return false;
418     }
419 
420     return false;
421 }
422 
423 
424 END_NCBI_SCOPE
425