1 /*  $Id: env_reg.cpp 617973 2020-10-08 18:26:26Z 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  * Authors:  Aaron Ucko
27  *
28  * File Description:
29  *   Classes to support using environment variables as a backend for
30  *   the registry framework.
31  *
32  */
33 
34 #include <ncbi_pch.hpp>
35 #include <corelib/env_reg.hpp>
36 #include <corelib/ncbienv.hpp>
37 #include <corelib/error_codes.hpp>
38 #include <set>
39 
40 
41 #define NCBI_USE_ERRCODE_X   Corelib_Env
42 
43 
44 BEGIN_NCBI_SCOPE
45 
46 
CEnvironmentRegistry(TFlags flags)47 CEnvironmentRegistry::CEnvironmentRegistry(TFlags flags)
48     : m_Env(new CNcbiEnvironment, eTakeOwnership),
49       m_Modified(false), m_Flags(flags)
50 {
51     AddMapper(*new CNcbiEnvRegMapper);
52 }
53 
54 
CEnvironmentRegistry(CNcbiEnvironment & env,EOwnership own,TFlags flags)55 CEnvironmentRegistry::CEnvironmentRegistry(CNcbiEnvironment& env,
56                                            EOwnership own, TFlags flags)
57     : m_Env(&env, own), m_Modified(false), m_Flags(flags)
58 {
59     AddMapper(*new CNcbiEnvRegMapper);
60 }
61 
62 
~CEnvironmentRegistry()63 CEnvironmentRegistry::~CEnvironmentRegistry()
64 {
65 }
66 
67 
AddMapper(const IEnvRegMapper & mapper,TPriority prio)68 void CEnvironmentRegistry::AddMapper(const IEnvRegMapper& mapper,
69                                      TPriority prio)
70 {
71     m_PriorityMap.insert(TPriorityMap::value_type
72                      (prio, CConstRef<IEnvRegMapper>(&mapper)));
73 }
74 
75 
RemoveMapper(const IEnvRegMapper & mapper)76 void CEnvironmentRegistry::RemoveMapper(const IEnvRegMapper& mapper)
77 {
78     NON_CONST_ITERATE (TPriorityMap, it, m_PriorityMap) {
79         if (it->second == &mapper) {
80             m_PriorityMap.erase(it);
81             return; // mappers should be unique
82         }
83     }
84     // already returned if found...
85     NCBI_THROW2(CRegistryException, eErr,
86                 "CEnvironmentRegistry::RemoveMapper:"
87                 " unknown mapper (already removed?)", 0);
88 }
89 
x_Empty(TFlags) const90 bool CEnvironmentRegistry::x_Empty(TFlags /*flags*/) const
91 {
92     // return (flags & fTransient) ? m_PriorityMap.empty() : true;
93     list<string> l;
94     string       section, name;
95     ITERATE (TPriorityMap, mapper, m_PriorityMap) {
96         m_Env->Enumerate(l, mapper->second->GetPrefix());
97         ITERATE (list<string>, it, l) {
98             if (mapper->second->EnvToReg(*it, section, name)) {
99                 return false;
100             }
101         }
102     }
103     return true;
104 }
105 
106 
x_Modified(TFlags flags) const107 bool CEnvironmentRegistry::x_Modified(TFlags flags) const
108 {
109     return (flags & fTransient) ? m_Modified : false;
110 }
111 
112 
x_SetModifiedFlag(bool modified,TFlags flags)113 void CEnvironmentRegistry::x_SetModifiedFlag(bool modified, TFlags flags)
114 {
115     if (flags & fTransient) {
116         m_Modified = modified;
117     }
118 }
119 
120 
x_Get(const string & section,const string & name,TFlags flags) const121 const string& CEnvironmentRegistry::x_Get(const string& section,
122                                           const string& name,
123                                           TFlags flags) const
124 {
125     bool found;
126     return x_Get(section, name, flags, found);
127 }
128 
129 
x_Get(const string & section,const string & name,TFlags flags,bool & found) const130 const string& CEnvironmentRegistry::x_Get(const string& section,
131                                           const string& name,
132                                           TFlags flags,
133                                           bool& found) const
134 {
135     if ((flags & fTPFlags) == fPersistent) {
136         return kEmptyStr;
137     }
138     REVERSE_ITERATE (TPriorityMap, it, m_PriorityMap) {
139         string        var_name = it->second->RegToEnv(section, name);
140         const string* resultp  = &m_Env->Get(var_name, &found);
141         if ((m_Flags & fCaseFlags) == 0  &&  !found) {
142             // try capitalizing the name
143             resultp = &m_Env->Get(NStr::ToUpper(var_name), &found);
144         }
145         if (found) {
146             return *resultp;
147         }
148     }
149     return kEmptyStr;
150 }
151 
152 
x_HasEntry(const string & section,const string & name,TFlags flags) const153 bool CEnvironmentRegistry::x_HasEntry(const string& section,
154                                       const string& name,
155                                       TFlags flags) const
156 {
157     bool found = false;
158     x_Get(section, name, flags, found);
159     return found;
160 }
161 
162 
x_GetComment(const string &,const string &,TFlags) const163 const string& CEnvironmentRegistry::x_GetComment(const string&, const string&,
164                                                  TFlags) const
165 {
166     return kEmptyStr;
167 }
168 
169 
x_Enumerate(const string & section,list<string> & entries,TFlags flags) const170 void CEnvironmentRegistry::x_Enumerate(const string& section,
171                                        list<string>& entries,
172                                        TFlags flags) const
173 {
174     // Environment does not provide comments, so if we came for in-section
175     // comments, we can just return doing nothing
176     if (flags & fInSectionComments) {
177         return;
178     }
179     if ( !(flags & fTransient) ) {
180         return;
181     }
182 
183     typedef set<string, PNocase> TEntrySet;
184 
185     list<string> l;
186     TEntrySet    entry_set;
187     string       parsed_section, parsed_name;
188 
189     ITERATE (TPriorityMap, mapper, m_PriorityMap) {
190         m_Env->Enumerate(l, mapper->second->GetPrefix());
191         ITERATE (list<string>, it, l) {
192             if (mapper->second->EnvToReg(*it, parsed_section, parsed_name)) {
193                 if (section.empty()) {
194                     entry_set.insert(parsed_section);
195                 } else if (section == parsed_section) {
196                     entry_set.insert(parsed_name);
197                 }
198             }
199         }
200     }
201     ITERATE (TEntrySet, it, entry_set) {
202         entries.push_back(*it);
203     }
204 }
205 
206 
x_ChildLockAction(FLockAction)207 void CEnvironmentRegistry::x_ChildLockAction(FLockAction)
208 {
209 }
210 
211 
x_Clear(TFlags flags)212 void CEnvironmentRegistry::x_Clear(TFlags flags)
213 {
214     if (flags & fTransient) {
215         // m_Mappers.clear();
216     }
217 }
218 
219 
x_Set(const string & section,const string & name,const string & value,TFlags flags,const string &)220 bool CEnvironmentRegistry::x_Set(const string& section, const string& name,
221                                  const string& value, TFlags flags,
222                                  const string& /*comment*/)
223 {
224     REVERSE_ITERATE (TPriorityMap, it,
225                  const_cast<const TPriorityMap&>(m_PriorityMap)) {
226         string var_name = it->second->RegToEnv(section, name);
227         if ( !var_name.empty() ) {
228             string cap_name = var_name;
229             NStr::ToUpper(cap_name);
230             string old_value = m_Env->Get(var_name);
231             if ((m_Flags & fCaseFlags) == 0  &&  old_value.empty()) {
232                 old_value = m_Env->Get(cap_name);
233             }
234             if (MaybeSet(old_value, value, flags)) {
235                 m_Env->Set(var_name, value);
236                 // m_Env->Set(cap_name, value);
237                 return true;
238             }
239             return false;
240         }
241     }
242 
243     ERR_POST_X(1, Warning << "CEnvironmentRegistry::x_Set: "
244                   "no mapping defined for [" << section << ']' << name);
245     return false;
246 }
247 
248 
x_Unset(const string & section,const string & name,TFlags)249 bool CEnvironmentRegistry::x_Unset(const string& section, const string& name,
250                                    TFlags /*flags*/)
251 {
252     bool result = false;
253     ITERATE (TPriorityMap, it, m_PriorityMap) {
254         string var_name = it->second->RegToEnv(section, name);
255         bool   found;
256         if (var_name.empty()) {
257             continue;
258         }
259         m_Env->Get(var_name, &found);
260         if (found) {
261             result = true;
262             m_Env->Unset(var_name);
263         }
264         if ((m_Flags & fCaseFlags) == 0) {
265             string cap_name = var_name;
266             NStr::ToUpper(cap_name);
267             m_Env->Get(cap_name, &found);
268             if (found) {
269                 result = true;
270                 m_Env->Unset(cap_name);
271             }
272         }
273     }
274     return result;
275 }
276 
x_SetComment(const string &,const string &,const string &,TFlags)277 bool CEnvironmentRegistry::x_SetComment(const string&, const string&,
278                                         const string&, TFlags)
279 {
280     ERR_POST_X(2, Warning
281                   << "CEnvironmentRegistry::x_SetComment: unsupported operation");
282     return false;
283 }
284 
285 
CSimpleEnvRegMapper(const string & section,const string & prefix,const string & suffix)286 CSimpleEnvRegMapper::CSimpleEnvRegMapper(const string& section,
287                                          const string& prefix,
288                                          const string& suffix)
289     : m_Section(section), m_Prefix(prefix), m_Suffix(suffix)
290 {
291 }
292 
293 
RegToEnv(const string & section,const string & name) const294 string CSimpleEnvRegMapper::RegToEnv(const string& section, const string& name)
295     const
296 {
297     return (section == m_Section ? (m_Prefix + name + m_Suffix) : kEmptyStr);
298 }
299 
300 
EnvToReg(const string & env,string & section,string & name) const301 bool CSimpleEnvRegMapper::EnvToReg(const string& env, string& section,
302                                    string& name) const
303 {
304     SIZE_TYPE plen = m_Prefix.length();
305     SIZE_TYPE tlen = plen + m_Suffix.length();
306     if (env.size() > tlen  &&  NStr::StartsWith(env, m_Prefix, NStr::eNocase)
307         &&  NStr::EndsWith(name, m_Suffix, NStr::eNocase)) {
308         section = m_Section;
309         name    = env.substr(plen, env.length() - tlen);
310         return true;
311     }
312     return false;
313 }
314 
315 
GetPrefix(void) const316 string CSimpleEnvRegMapper::GetPrefix(void) const
317 {
318     return m_Prefix;
319 }
320 
321 
322 const char* CNcbiEnvRegMapper::sm_Prefix = "NCBI_CONFIG_";
323 
324 
RegToEnv(const string & section,const string & name) const325 string CNcbiEnvRegMapper::RegToEnv(const string& section, const string& name)
326     const
327 {
328     string result(sm_Prefix);
329     if (NStr::StartsWith(name, ".")) {
330         result += name.substr(1) + "__" + section;
331     } else {
332         result += "_" + section + "__" + name;
333     }
334     if (result.find_first_of(".-/ ") != NPOS) {
335         NStr::ReplaceInPlace(result, ".", "_DOT_");
336         NStr::ReplaceInPlace(result, "-", "_HYPHEN_");
337         NStr::ReplaceInPlace(result, "/", "_SLASH_");
338         NStr::ReplaceInPlace(result, " ", "_SPACE_");
339     }
340     return result;
341 }
342 
343 
344 inline
s_IdentifySubstitution(const CTempString & s)345 static char s_IdentifySubstitution(const CTempString& s) {
346     switch (s[0]) {
347     case 'D':
348         if (s.size() == 3  &&  s == "DOT") {
349             return '.';
350         }
351         break;
352     case 'H':
353         if (s.size() == 6  &&  s == "HYPHEN") {
354             return '-';
355         }
356         break;
357     case 'S':
358         if (s.size() == 5) {
359             if (s == "SLASH") {
360                 return '/';
361             } else if (s == "SPACE") {
362                 return ' ';
363             }
364         }
365         break;
366     default:
367         break;
368     }
369     return '\0';
370 }
371 
372 
EnvToReg(const string & env_in,string & section,string & name) const373 bool CNcbiEnvRegMapper::EnvToReg(const string& env_in, string& section,
374                                  string& name) const
375 {
376     static const SIZE_TYPE kPfxLen = strlen(sm_Prefix);
377     if (env_in.size() <= kPfxLen  ||  !NStr::StartsWith(env_in, sm_Prefix) ) {
378         return false;
379     }
380     vector<CTempString> v;
381     NStr::Split(env_in, "_", v);
382     string env;
383     env.reserve(env_in.size());
384     for (const auto& it : v) {
385         SIZE_TYPE l = env.size();
386         char c = '\0';
387         if (&it != &v.back()  &&  !env.empty()  &&  env[l - 1] == '_'
388             &&  !it.empty()) {
389             c = s_IdentifySubstitution(it);
390         }
391         if (c == '\0') {
392             env += it;
393             if (&it != &v.back()) {
394                 env += '_';
395             }
396         } else {
397             env[l - 1] = c;
398         }
399     }
400     // Make an offset until the first symbol that could be section name
401     // (alphanumeric character)
402     SIZE_TYPE section_start_pos = kPfxLen;
403     for (  ; section_start_pos < env.size(); section_start_pos++) {
404         if (isalnum(env[section_start_pos])) break;
405     }
406     SIZE_TYPE uu_pos = env.find("__", section_start_pos + 1);
407     if (uu_pos == NPOS  ||  uu_pos == env.size() - 2) {
408         return false;
409     }
410     /* Parse section and entry names from the variable */
411     if (env[kPfxLen] == '_') { // regular entry
412         section = env.substr(kPfxLen + 1, uu_pos - kPfxLen - 1);
413         name    = env.substr(uu_pos + 2);
414     } else {
415         name    = env.substr(kPfxLen - 1, uu_pos - kPfxLen + 1);
416         _ASSERT(name[0] == '_');
417         name[0] = '.';
418         section = env.substr(uu_pos + 2);
419     }
420     if (!IRegistry::IsNameSection(section, IRegistry::fInternalSpaces)) {
421         ERR_POST(Info << "Invalid registry section name in environment "
422                             "variable " << env);
423     }
424     if (!IRegistry::IsNameEntry(name, IRegistry::fInternalSpaces)) {
425         ERR_POST(Info << "Invalid registry entry name in environment "
426                             "variable " << env);
427     }
428     return true;
429 }
430 
431 
GetPrefix(void) const432 string CNcbiEnvRegMapper::GetPrefix(void) const
433 {
434     return sm_Prefix;
435 }
436 
437 
438 END_NCBI_SCOPE
439