1 /*  $Id: ncbienv.cpp 624723 2021-02-03 18:51:43Z ivanov $
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:  Denis Vakatov, Eugene Vasilchenko
27  *
28  * File Description:
29  *   Unified interface to application:
30  *      environment     -- CNcbiEnvironment
31  *      cmd.-line args  -- CNcbiArguments
32  *
33  */
34 
35 #include <ncbi_pch.hpp>
36 #include <corelib/ncbienv.hpp>
37 #include <corelib/ncbifile.hpp>
38 #include <corelib/ncbi_safe_static.hpp>
39 #include <corelib/error_codes.hpp>
40 #include <common/ncbi_sanitizers.h>
41 #include <algorithm>
42 #include <stdarg.h>
43 #include "ncbisys.hpp"
44 
45 #ifdef NCBI_OS_LINUX
46 #  include <unistd.h>
47 #endif
48 
49 #ifdef NCBI_OS_MSWIN
50 #  include <stdlib.h>
51 #elif defined (NCBI_OS_DARWIN)
52 #  include <crt_externs.h>
53 #  define environ (*_NSGetEnviron())
54 #else
55 extern char** environ;
56 #endif
57 
58 
59 #define NCBI_USE_ERRCODE_X   Corelib_Env
60 
61 
62 BEGIN_NCBI_SCOPE
63 
64 
65 ///////////////////////////////////////////////////////
66 //  CNcbiEnvironment::
67 
68 
CNcbiEnvironment(void)69 CNcbiEnvironment::CNcbiEnvironment(void)
70 {
71     Reset(environ);
72 }
73 
74 
CNcbiEnvironment(const char * const * envp)75 CNcbiEnvironment::CNcbiEnvironment(const char* const* envp)
76 {
77     Reset(envp);
78 }
79 
80 
~CNcbiEnvironment(void)81 CNcbiEnvironment::~CNcbiEnvironment(void)
82 {
83     return;
84 }
85 
86 
Reset(const char * const * envp)87 void CNcbiEnvironment::Reset(const char* const* envp)
88 {
89     // load new environment values from "envp"
90     if ( !envp )
91         return;
92 
93     CFastMutexGuard LOCK(m_CacheMutex);
94     // delete old environment values
95     m_Cache.clear();
96 
97     for ( ;  *envp;  envp++) {
98         const char* s = *envp;
99         const char* eq = strchr(s, '=');
100         if ( !eq ) {
101             ERR_POST_X(3, "CNcbiEnvironment: bad string '" << s << "'");
102             continue;
103         }
104         m_Cache[string(s, (size_t)(eq - s))] = SEnvValue(eq + 1, kEmptyXCStr);
105     }
106 }
107 
108 
Get(const string & name,bool * found) const109 const string& CNcbiEnvironment::Get(const string& name, bool* found) const
110 {
111     CFastMutexGuard LOCK(m_CacheMutex);
112     TCache::const_iterator i = m_Cache.find(name);
113     bool fake_found;
114     if (found == NULL) {
115         found = &fake_found;
116     }
117     if ( i != m_Cache.end() ) {
118         if (i->second.ptr == NULL  &&  i->second.value.empty()) {
119             *found = false;
120             return kEmptyStr;
121         } else {
122             *found = true;
123             return i->second.value;
124         }
125     }
126     string loaded_value = Load(name, *found);
127     m_Cache[name] = SEnvValue(loaded_value, *found ? kEmptyXCStr : NULL);
128     const string& s = m_Cache[name].value;
129     return s.empty() ? kEmptyStr : s;
130 }
131 
132 
Enumerate(list<string> & names,const string & prefix) const133 void CNcbiEnvironment::Enumerate(list<string>& names, const string& prefix)
134     const
135 {
136     names.clear();
137     CFastMutexGuard LOCK(m_CacheMutex);
138     for (TCache::const_iterator it = m_Cache.lower_bound(prefix);
139          it != m_Cache.end()  &&  NStr::StartsWith(it->first, prefix);  ++it) {
140         if ( !it->second.value.empty()  ||  it->second.ptr == kEmptyXCStr) {
141             // ignore entries the app cleared out
142             names.push_back(it->first);
143         }
144     }
145 }
146 
Set(const string & name,const string & value)147 void CNcbiEnvironment::Set(const string& name, const string& value)
148 {
149 
150     TXChar* str = nullptr;
151     {{
152         // Deliberate leak
153         NCBI_LSAN_DISABLE_GUARD;
154         str = NcbiSys_strdup(_T_XCSTRING(name + "=" + value));
155     }}
156     if ( !str ) {
157         throw bad_alloc();
158     }
159 
160     if (NcbiSys_putenv(str) != 0) {
161         free(str);
162         NCBI_THROW(CErrnoTemplException<CCoreException>, eErrno,
163                    "failed to set environment variable " + name);
164     }
165 
166     CFastMutexGuard LOCK(m_CacheMutex);
167     TCache::const_iterator i = m_Cache.find(name);
168     if ( i != m_Cache.end() ) {
169         if (i->second.ptr != NULL && i->second.ptr != kEmptyXCStr) {
170             free(const_cast<TXChar*>(i->second.ptr));
171         }
172     }
173     m_Cache[name] = SEnvValue(value, str);
174 }
175 
Unset(const string & name)176 void CNcbiEnvironment::Unset(const string& name)
177 {
178 #ifdef NCBI_OS_MSWIN
179     Set(name, kEmptyStr);
180 #elif defined(NCBI_OS_IRIX)
181     {{
182         char* p = getenv(name.c_str());
183         if (p) {
184             _ASSERT(p[-1] == '=');
185             _ASSERT( !memcmp(p - name.size() - 1, name.data(), name.size()) );
186             p[-1] = '\0';
187         }
188     }}
189 #else
190     unsetenv(name.c_str());
191 #endif
192 
193     CFastMutexGuard LOCK(m_CacheMutex);
194     TCache::iterator i = m_Cache.find(name);
195     if ( i != m_Cache.end() ) {
196         if (i->second.ptr != NULL && i->second.ptr != kEmptyXCStr) {
197             free(const_cast<TXChar*>(i->second.ptr));
198         }
199         m_Cache.erase(i);
200     }
201 }
202 
Load(const string & name,bool & found) const203 string CNcbiEnvironment::Load(const string& name, bool& found) const
204 {
205     const TXChar* s = NcbiSys_getenv(_T_XCSTRING(name));
206     if (s == NULL) {
207         found = false;
208         return NcbiEmptyString;
209     } else {
210         found = true;
211         return _T_STDSTRING(s);
212     }
213 }
214 
215 
216 
217 
218 ///////////////////////////////////////////////////////
219 //  CAutoEnvironmentVariable::
220 
221 
CAutoEnvironmentVariable(const CTempString & var_name,const CTempString & value,CNcbiEnvironment * env)222 CAutoEnvironmentVariable::CAutoEnvironmentVariable(const CTempString& var_name,
223                                                    const CTempString& value,
224                                                    CNcbiEnvironment*  env)
225     : m_Env(env, eNoOwnership), m_VariableName(var_name)
226 {
227     if ( !env ) {
228         CNcbiApplicationGuard app = CNcbiApplication::InstanceGuard();
229         if (app) {
230             m_Env.reset(&app->SetEnvironment(), eNoOwnership);
231         } else {
232             m_Env.reset(new CNcbiEnvironment(NULL), eTakeOwnership);
233         }
234     }
235     m_PrevValue = m_Env->Get(m_VariableName, &m_WasSet);
236     if ( value.empty() ) {
237         m_Env->Unset(m_VariableName);
238     } else {
239         m_Env->Set(m_VariableName, value);
240     }
241 }
242 
~CAutoEnvironmentVariable()243 CAutoEnvironmentVariable::~CAutoEnvironmentVariable()
244 {
245     if (m_WasSet) {
246         m_Env->Set(m_VariableName, m_PrevValue);
247     } else {
248         m_Env->Unset(m_VariableName);
249     }
250 }
251 
252 
253 
254 
255 ///////////////////////////////////////////////////////
256 //  CEnvironmentCleaner::
257 
258 
CEnvironmentCleaner(const char * s,...)259 CEnvironmentCleaner::CEnvironmentCleaner(const char* s, ...)
260 {
261     if (s != NULL) {
262         Clean(s);
263         va_list ap;
264         va_start(ap, s);
265         for (;;) {
266             const char* p = va_arg(ap, const char*);
267             if (p == NULL) {
268                 break;
269             }
270             Clean(p);
271         }
272         va_end(ap);
273     }
274 }
275 
Clean(const string & name)276 void CEnvironmentCleaner::Clean(const string& name)
277 {
278     CNcbiApplicationGuard app = CNcbiApplication::InstanceGuard();
279     if (app) {
280         app->SetEnvironment().Unset(name);
281     } else {
282 #ifdef NCBI_OS_MSWIN
283         ::SetEnvironmentVariable(_T_XCSTRING(name), NULL);
284 #elif defined(NCBI_OS_IRIX)
285         char* p = getenv(name.c_str());
286         if (p) {
287             _ASSERT(p[-1] == '=');
288             _ASSERT( !memcmp(p - name.size() - 1, name.data(), name.size()) );
289             p[-1] = '\0';
290         }
291 #else
292         unsetenv(name.c_str());
293 #endif
294     }
295 }
296 
297 
298 
299 
300 ///////////////////////////////////////////////////////
301 //  CNcbiArguments::
302 
303 
CNcbiArguments(int argc,const char * const * argv,const string & program_name,const string & real_name)304 CNcbiArguments::CNcbiArguments(int argc, const char* const* argv,
305                                const string& program_name,
306                                const string& real_name)
307 {
308     Reset(argc, argv, program_name, real_name);
309 }
310 
311 
~CNcbiArguments(void)312 CNcbiArguments::~CNcbiArguments(void)
313 {
314     return;
315 }
316 
317 
CNcbiArguments(const CNcbiArguments & args)318 CNcbiArguments::CNcbiArguments(const CNcbiArguments& args)
319     : m_ProgramName(args.m_ProgramName),
320       m_Args(args.m_Args),
321       m_ResolvedName(args.m_ResolvedName)
322 {
323     return;
324 }
325 
326 
operator =(const CNcbiArguments & args)327 CNcbiArguments& CNcbiArguments::operator= (const CNcbiArguments& args)
328 {
329     if (&args == this)
330         return *this;
331 
332     m_ProgramName = args.m_ProgramName;
333     m_Args.clear();
334     copy(args.m_Args.begin(), args.m_Args.end(), back_inserter(m_Args));
335     return *this;
336 }
337 
338 
Reset(int argc,const char * const * argv,const string & program_name,const string & real_name)339 void CNcbiArguments::Reset(int argc, const char* const* argv,
340                            const string& program_name, const string& real_name)
341 {
342     // check args
343     if (argc < 0) {
344         NCBI_THROW(CArgumentsException,eNegativeArgc,
345             "Negative number of command-line arguments");
346     }
347 
348     if ((argc == 0) != (argv == 0)) {
349         if (argv == 0) {
350             NCBI_THROW(CArgumentsException,eNoArgs,
351                 "Command-line arguments are absent");
352         }
353         ERR_POST_X(4, Info <<
354                       "CNcbiArguments(): zero \"argc\", non-zero \"argv\"");
355     }
356 
357     // clear old args, store new ones
358     m_Args.clear();
359     for (int i = 0;  i < argc;  i++) {
360         if ( !argv[i] ) {
361             ERR_POST_X(5, Warning <<
362                           "CNcbiArguments() -- NULL cmd.-line arg #" << i);
363             continue;
364         }
365         m_Args.push_back(argv[i]);
366     }
367 
368     // set application name
369     SetProgramName(program_name, real_name);
370 }
371 
372 
GetProgramName(EFollowLinks follow_links) const373 const string& CNcbiArguments::GetProgramName(EFollowLinks follow_links) const
374 {
375     if (follow_links) {
376         CFastMutexGuard LOCK(m_ResolvedNameMutex);
377         if ( !m_ResolvedName.size() ) {
378 #ifdef NCBI_OS_LINUX
379             string proc_link = "/proc/" + NStr::IntToString(getpid()) + "/exe";
380             m_ResolvedName = CDirEntry::NormalizePath(proc_link, follow_links);
381 #else
382             m_ResolvedName = CDirEntry::NormalizePath
383                 (GetProgramName(eIgnoreLinks), follow_links);
384 #endif
385         }
386         return m_ResolvedName;
387     } else if ( !m_ProgramName.empty() ) {
388         return m_ProgramName;
389     } else if ( m_Args.size() ) {
390         return m_Args[0];
391     } else {
392         static CSafeStatic<string> kDefProgramName;
393         kDefProgramName->assign("ncbi");
394         return kDefProgramName.Get();
395     }
396 }
397 
398 
GetProgramBasename(EFollowLinks follow_links) const399 string CNcbiArguments::GetProgramBasename(EFollowLinks follow_links) const
400 {
401     const string& name = GetProgramName(follow_links);
402     SIZE_TYPE base_pos = name.find_last_of("/\\:");
403     if (base_pos == NPOS)
404         return name;
405     return name.substr(base_pos + 1);
406 }
407 
408 
GetProgramDirname(EFollowLinks follow_links) const409 string CNcbiArguments::GetProgramDirname(EFollowLinks follow_links) const
410 {
411     const string& name = GetProgramName(follow_links);
412     SIZE_TYPE base_pos = name.find_last_of("/\\:");
413     if (base_pos == NPOS)
414         return NcbiEmptyString;
415     return name.substr(0, base_pos + 1);
416 }
417 
418 
SetProgramName(const string & program_name,const string & real_name)419 void CNcbiArguments::SetProgramName(const string& program_name,
420                                     const string& real_name)
421 {
422     m_ProgramName = program_name;
423     CFastMutexGuard LOCK(m_ResolvedNameMutex);
424     m_ResolvedName = real_name;
425 }
426 
427 
Add(const string & arg)428 void CNcbiArguments::Add(const string& arg)
429 {
430     m_Args.push_back(arg);
431 }
432 
Shift(int n)433 void CNcbiArguments::Shift(int n)
434 {
435     while (n-- > 0) {
436         if (m_Args.size() > 1) {
437             m_Args.erase( ++m_Args.begin());
438         }
439     }
440 }
441 
GetErrCodeString(void) const442 const char* CArgumentsException::GetErrCodeString(void) const
443 {
444     switch (GetErrCode()) {
445     case eNegativeArgc:  return "eNegativeArgc";
446     case eNoArgs:        return "eNoArgs";
447     default:    return CException::GetErrCodeString();
448     }
449 }
450 
451 
452 END_NCBI_SCOPE
453