1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtenvironmentvariables.h"
5#include "qtenvironmentvariables_p.h"
6
7#include <qplatformdefs.h>
8#include <QtCore/qbytearray.h>
9#include <QtCore/qmutex.h>
10#include <QtCore/qstring.h>
11#include <QtCore/qvarlengtharray.h>
12
13#include <QtCore/private/qlocking_p.h>
14
15QT_BEGIN_NAMESPACE
16
17// In the C runtime on all platforms access to the environment is not thread-safe. We
18// add thread-safety for the Qt wrappers.
19Q_CONSTINIT static QBasicMutex environmentMutex;
20
21/*!
22 \relates <QtEnvironmentVariables>
23 \threadsafe
24
25 Returns the value of the environment variable with name \a varName as a
26 QByteArray. If no variable by that name is found in the environment, this
27 function returns a default-constructed QByteArray.
28
29 The Qt environment manipulation functions are thread-safe, but this
30 requires that the C library equivalent functions like getenv and putenv are
31 not directly called.
32
33 To convert the data to a QString use QString::fromLocal8Bit().
34
35 \note on desktop Windows, qgetenv() may produce data loss if the
36 original string contains Unicode characters not representable in the
37 ANSI encoding. Use qEnvironmentVariable() instead.
38 On Unix systems, this function is lossless.
39
40 \sa qputenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet(),
41 qEnvironmentVariableIsEmpty()
42*/
43QByteArray qgetenv(const char *varName)
44{
45 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
46#ifdef Q_CC_MSVC
47 size_t requiredSize = 0;
48 QByteArray buffer;
49 getenv_s(&requiredSize, 0, 0, varName);
50 if (requiredSize == 0)
51 return buffer;
52 buffer.resize(qsizetype(requiredSize));
53 getenv_s(&requiredSize, buffer.data(), requiredSize, varName);
54 // requiredSize includes the terminating null, which we don't want.
55 Q_ASSERT(buffer.endsWith('\0'));
56 buffer.chop(1);
57 return buffer;
58#else
59 return QByteArray(::getenv(name: varName));
60#endif
61}
62
63/*!
64 \fn QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
65 \fn QString qEnvironmentVariable(const char *varName)
66
67 \relates <QtEnvironmentVariables>
68 \since 5.10
69
70 These functions return the value of the environment variable, \a varName, as a
71 QString. If no variable \a varName is found in the environment and \a defaultValue
72 is provided, \a defaultValue is returned. Otherwise QString() is returned.
73
74 The Qt environment manipulation functions are thread-safe, but this
75 requires that the C library equivalent functions like getenv and putenv are
76 not directly called.
77
78 The following table describes how to choose between qgetenv() and
79 qEnvironmentVariable():
80 \table
81 \header \li Condition \li Recommendation
82 \row
83 \li Variable contains file paths or user text
84 \li qEnvironmentVariable()
85 \row
86 \li Windows-specific code
87 \li qEnvironmentVariable()
88 \row
89 \li Unix-specific code, destination variable is not QString and/or is
90 used to interface with non-Qt APIs
91 \li qgetenv()
92 \row
93 \li Destination variable is a QString
94 \li qEnvironmentVariable()
95 \row
96 \li Destination variable is a QByteArray or std::string
97 \li qgetenv()
98 \endtable
99
100 \note on Unix systems, this function may produce data loss if the original
101 string contains arbitrary binary data that cannot be decoded by the locale
102 codec. Use qgetenv() instead for that case. On Windows, this function is
103 lossless.
104
105 \note the variable name \a varName must contain only US-ASCII characters.
106
107 \sa qputenv(), qgetenv(), qEnvironmentVariableIsSet(), qEnvironmentVariableIsEmpty()
108*/
109QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
110{
111#if defined(Q_OS_WIN)
112 QVarLengthArray<wchar_t, 32> wname(qsizetype(strlen(varName)) + 1);
113 for (qsizetype i = 0; i < wname.size(); ++i) // wname.size() is correct: will copy terminating null
114 wname[i] = uchar(varName[i]);
115 size_t requiredSize = 0;
116 auto locker = qt_unique_lock(environmentMutex);
117 _wgetenv_s(&requiredSize, 0, 0, wname.data());
118 if (requiredSize == 0)
119 return defaultValue;
120 QString buffer(qsizetype(requiredSize), Qt::Uninitialized);
121 _wgetenv_s(&requiredSize, reinterpret_cast<wchar_t *>(buffer.data()), requiredSize,
122 wname.data());
123 locker.unlock();
124 // requiredSize includes the terminating null, which we don't want.
125 Q_ASSERT(buffer.endsWith(QChar(u'\0')));
126 buffer.chop(1);
127 return buffer;
128#else
129 QByteArray value = qgetenv(varName);
130 if (value.isNull())
131 return defaultValue;
132// duplicated in qfile.h (QFile::decodeName)
133#if defined(Q_OS_DARWIN)
134 return QString::fromUtf8(value).normalized(QString::NormalizationForm_C);
135#else // other Unix
136 return QString::fromLocal8Bit(ba: value);
137#endif
138#endif
139}
140
141QString qEnvironmentVariable(const char *varName)
142{
143 return qEnvironmentVariable(varName, defaultValue: QString());
144}
145
146/*!
147 \relates <QtEnvironmentVariables>
148 \since 5.1
149
150 Returns whether the environment variable \a varName is empty.
151
152 Equivalent to
153 \snippet code/src_corelib_global_qglobal.cpp is-empty
154 except that it's potentially much faster, and can't throw exceptions.
155
156 \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet()
157*/
158bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
159{
160 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
161#ifdef Q_CC_MSVC
162 // we provide a buffer that can only hold the empty string, so
163 // when the env.var isn't empty, we'll get an ERANGE error (buffer
164 // too small):
165 size_t dummy;
166 char buffer = '\0';
167 return getenv_s(&dummy, &buffer, 1, varName) != ERANGE;
168#else
169 const char * const value = ::getenv(name: varName);
170 return !value || !*value;
171#endif
172}
173
174/*!
175 \relates <QtEnvironmentVariables>
176 \since 5.5
177
178 Returns the numerical value of the environment variable \a varName.
179 If \a ok is not null, sets \c{*ok} to \c true or \c false depending
180 on the success of the conversion.
181
182 Equivalent to
183 \snippet code/src_corelib_global_qglobal.cpp to-int
184 except that it's much faster, and can't throw exceptions.
185
186 \note there's a limit on the length of the value, which is sufficient for
187 all valid values of int, not counting leading zeroes or spaces. Values that
188 are too long will either be truncated or this function will set \a ok to \c
189 false.
190
191 \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet()
192*/
193int qEnvironmentVariableIntValue(const char *varName, bool *ok) noexcept
194{
195 static const int NumBinaryDigitsPerOctalDigit = 3;
196 static const int MaxDigitsForOctalInt =
197 (std::numeric_limits<uint>::digits + NumBinaryDigitsPerOctalDigit - 1) / NumBinaryDigitsPerOctalDigit;
198
199 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
200 size_t size;
201#ifdef Q_CC_MSVC
202 // we provide a buffer that can hold any int value:
203 char buffer[MaxDigitsForOctalInt + 2]; // +1 for NUL +1 for optional '-'
204 size_t dummy;
205 if (getenv_s(&dummy, buffer, sizeof buffer, varName) != 0) {
206 if (ok)
207 *ok = false;
208 return 0;
209 }
210 size = strlen(buffer);
211#else
212 const char * const buffer = ::getenv(name: varName);
213 if (!buffer || (size = strlen(s: buffer)) > MaxDigitsForOctalInt + 2) {
214 if (ok)
215 *ok = false;
216 return 0;
217 }
218#endif
219 return QByteArrayView(buffer, size).toInt(ok, base: 0);
220}
221
222/*!
223 \relates <QtEnvironmentVariables>
224 \since 5.1
225
226 Returns whether the environment variable \a varName is set.
227
228 Equivalent to
229 \snippet code/src_corelib_global_qglobal.cpp is-null
230 except that it's potentially much faster, and can't throw exceptions.
231
232 \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsEmpty()
233*/
234bool qEnvironmentVariableIsSet(const char *varName) noexcept
235{
236 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
237#ifdef Q_CC_MSVC
238 size_t requiredSize = 0;
239 (void)getenv_s(&requiredSize, 0, 0, varName);
240 return requiredSize != 0;
241#else
242 return ::getenv(name: varName) != nullptr;
243#endif
244}
245
246/*!
247 \fn bool qputenv(const char *varName, QByteArrayView value)
248 \relates <QtEnvironmentVariables>
249
250 This function sets the \a value of the environment variable named
251 \a varName. It will create the variable if it does not exist. It
252 returns 0 if the variable could not be set.
253
254 Calling qputenv with an empty value removes the environment variable on
255 Windows, and makes it set (but empty) on Unix. Prefer using qunsetenv()
256 for fully portable behavior.
257
258 \note qputenv() was introduced because putenv() from the standard
259 C library was deprecated in VC2005 (and later versions). qputenv()
260 uses the replacement function in VC, and calls the standard C
261 library's implementation on all other platforms.
262
263 \note In Qt versions prior to 6.5, the \a value argument was QByteArray,
264 not QByteArrayView.
265
266 \sa qgetenv(), qEnvironmentVariable()
267*/
268bool qputenv(const char *varName, QByteArrayView raw)
269{
270 auto protect = [](const char *str) { return str ? str : ""; };
271
272 std::string value{protect(raw.data()), size_t(raw.size())}; // NUL-terminates w/SSO
273
274#if defined(Q_CC_MSVC)
275 const auto locker = qt_scoped_lock(environmentMutex);
276 return _putenv_s(varName, value.data()) == 0;
277#elif (defined(_POSIX_VERSION) && (_POSIX_VERSION-0) >= 200112L) || defined(Q_OS_HAIKU)
278 // POSIX.1-2001 has setenv
279 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
280 return setenv(name: varName, value: value.data(), replace: true) == 0;
281#else
282 std::string buffer;
283 buffer += protect(varName);
284 buffer += '=';
285 buffer += value;
286 char *envVar = qstrdup(buffer.data());
287 int result = [&] {
288 const auto locker = qt_scoped_lock(environmentMutex);
289 return putenv(envVar);
290 }();
291 if (result != 0) // error. we have to delete the string.
292 delete[] envVar;
293 return result == 0;
294#endif
295}
296
297/*!
298 \relates <QtEnvironmentVariables>
299
300 This function deletes the variable \a varName from the environment.
301
302 Returns \c true on success.
303
304 \since 5.1
305
306 \sa qputenv(), qgetenv(), qEnvironmentVariable()
307*/
308bool qunsetenv(const char *varName)
309{
310#if defined(Q_CC_MSVC)
311 const auto locker = qt_scoped_lock(environmentMutex);
312 return _putenv_s(varName, "") == 0;
313#elif (defined(_POSIX_VERSION) && (_POSIX_VERSION-0) >= 200112L) || defined(Q_OS_BSD4) || defined(Q_OS_HAIKU)
314 // POSIX.1-2001, BSD and Haiku have unsetenv
315 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
316 return unsetenv(name: varName) == 0;
317#elif defined(Q_CC_MINGW)
318 // On mingw, putenv("var=") removes "var" from the environment
319 QByteArray buffer(varName);
320 buffer += '=';
321 const auto locker = qt_scoped_lock(environmentMutex);
322 return putenv(buffer.constData()) == 0;
323#else
324 // Fallback to putenv("var=") which will insert an empty var into the
325 // environment and leak it
326 QByteArray buffer(varName);
327 buffer += '=';
328 char *envVar = qstrdup(buffer.constData());
329 const auto locker = qt_scoped_lock(environmentMutex);
330 return putenv(envVar) == 0;
331#endif
332}
333
334/* Various time-related APIs that need to consult system settings also need
335 protection with the same lock as the environment, since those system settings
336 include part of the environment (principally TZ).
337
338 First, tzset(), which POSIX explicitly says accesses the environment.
339*/
340void qTzSet()
341{
342 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
343#if defined(Q_OS_WIN)
344 _tzset();
345#else
346 tzset();
347#endif // Q_OS_WIN
348}
349
350/* Wrap mktime(), which is specified to behave as if it called tzset(), hence
351 shares its implicit environment-dependence.
352*/
353time_t qMkTime(struct tm *when)
354{
355 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
356 return mktime(tp: when);
357}
358
359/* For localtime(), POSIX mandates that it behave as if it called tzset().
360 For the alternatives to it, we need (if only for compatibility) to do the
361 same explicitly, which should ensure a re-parse of timezone info.
362*/
363bool qLocalTime(time_t utc, struct tm *local)
364{
365 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
366#if defined(Q_OS_WIN)
367 // The doc of localtime_s() says that it corrects for the same things
368 // _tzset() sets the globals for, but QTBUG-109974 reveals a need for an
369 // explicit call, all the same.
370 _tzset();
371 return !localtime_s(local, &utc);
372#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
373 // Use the reentrant version of localtime() where available, as it is
374 // thread-safe and doesn't use a shared static data area.
375 // As localtime_r() is not specified to work as if it called tzset(),
376 // make an explicit call.
377 tzset();
378 if (tm *res = localtime_r(timer: &utc, tp: local)) {
379 Q_ASSERT(res == local);
380 Q_UNUSED(res);
381 return true;
382 }
383 return false;
384#else
385 // POSIX mandates that localtime() behaves as if it called tzset().
386 // Returns shared static data which may be overwritten at any time (albeit
387 // our lock probably keeps it safe). So copy the result promptly:
388 if (tm *res = localtime(&utc)) {
389 *local = *res;
390 return true;
391 }
392 return false;
393#endif
394}
395
396/* Access to the tzname[] global in one thread is UB if any other is calling
397 tzset() or anything that behaves as if it called tzset(). So also lock this
398 access to prevent such collisions.
399
400 Parameter dstIndex must be 1 for DST or 0 for standard time.
401 Returns the relevant form of the name of local-time's zone.
402*/
403QString qTzName(int dstIndex)
404{
405 char name[512];
406 bool ok;
407#if defined(Q_CC_MSVC)
408 size_t s = 0;
409 {
410 const auto locker = qt_scoped_lock(environmentMutex);
411 ok = _get_tzname(&s, name, 512, dstIndex) != 0;
412 }
413#else
414 {
415 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
416 const char *const src = tzname[dstIndex];
417 ok = src != nullptr;
418 if (ok)
419 memcpy(dest: name, src: src, n: std::min(a: sizeof(name), b: strlen(s: src) + 1));
420 }
421#endif // Q_OS_WIN
422 return ok ? QString::fromLocal8Bit(ba: name) : QString();
423}
424
425QT_END_NAMESPACE
426

source code of qtbase/src/corelib/global/qtenvironmentvariables.cpp