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#if defined(Q_OS_WIN)
357 // QTBUG-83881 MS's mktime() seems to need _tzset() called first.
358 _tzset();
359#endif
360 return mktime(tp: when);
361}
362
363/* For localtime(), POSIX mandates that it behave as if it called tzset().
364 For the alternatives to it, we need (if only for compatibility) to do the
365 same explicitly, which should ensure a re-parse of timezone info.
366*/
367bool qLocalTime(time_t utc, struct tm *local)
368{
369 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
370#if defined(Q_OS_WIN)
371 // The doc of localtime_s() says that it corrects for the same things
372 // _tzset() sets the globals for, but QTBUG-109974 reveals a need for an
373 // explicit call, all the same.
374 _tzset();
375 return !localtime_s(local, &utc);
376#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
377 // Use the reentrant version of localtime() where available, as it is
378 // thread-safe and doesn't use a shared static data area.
379 // As localtime_r() is not specified to work as if it called tzset(),
380 // make an explicit call.
381 tzset();
382 if (tm *res = localtime_r(timer: &utc, tp: local)) {
383 Q_ASSERT(res == local);
384 Q_UNUSED(res);
385 return true;
386 }
387 return false;
388#else
389 // POSIX mandates that localtime() behaves as if it called tzset().
390 // Returns shared static data which may be overwritten at any time (albeit
391 // our lock probably keeps it safe). So copy the result promptly:
392 if (tm *res = localtime(&utc)) {
393 *local = *res;
394 return true;
395 }
396 return false;
397#endif
398}
399
400/* Access to the tzname[] global in one thread is UB if any other is calling
401 tzset() or anything that behaves as if it called tzset(). So also lock this
402 access to prevent such collisions.
403
404 Parameter dstIndex must be 1 for DST or 0 for standard time.
405 Returns the relevant form of the name of local-time's zone.
406*/
407QString qTzName(int dstIndex)
408{
409 char name[512];
410 bool ok;
411#if defined(_UCRT) // i.e., MSVC and MinGW-UCRT
412 size_t s = 0;
413 {
414 const auto locker = qt_scoped_lock(environmentMutex);
415 ok = _get_tzname(&s, name, 512, dstIndex) != 0;
416 }
417#else
418 {
419 const auto locker = qt_scoped_lock(mutex&: environmentMutex);
420 const char *const src = tzname[dstIndex];
421 ok = src != nullptr;
422 if (ok)
423 memcpy(dest: name, src: src, n: std::min(a: sizeof(name), b: strlen(s: src) + 1));
424 }
425#endif // Q_OS_WIN
426 return ok ? QString::fromLocal8Bit(ba: name) : QString();
427}
428
429QT_END_NAMESPACE
430

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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