diff options
Diffstat (limited to 'src/corelib/global/qtenvironmentvariables.cpp')
-rw-r--r-- | src/corelib/global/qtenvironmentvariables.cpp | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/src/corelib/global/qtenvironmentvariables.cpp b/src/corelib/global/qtenvironmentvariables.cpp new file mode 100644 index 0000000000..cf5955902a --- /dev/null +++ b/src/corelib/global/qtenvironmentvariables.cpp @@ -0,0 +1,429 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtenvironmentvariables.h" +#include "qtenvironmentvariables_p.h" + +#include <qplatformdefs.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qmutex.h> +#include <QtCore/qstring.h> +#include <QtCore/qvarlengtharray.h> + +#include <QtCore/private/qlocking_p.h> + +QT_BEGIN_NAMESPACE + +// In the C runtime on all platforms access to the environment is not thread-safe. We +// add thread-safety for the Qt wrappers. +Q_CONSTINIT static QBasicMutex environmentMutex; + +/*! + \relates <QtEnvironmentVariables> + \threadsafe + + Returns the value of the environment variable with name \a varName as a + QByteArray. If no variable by that name is found in the environment, this + function returns a default-constructed QByteArray. + + The Qt environment manipulation functions are thread-safe, but this + requires that the C library equivalent functions like getenv and putenv are + not directly called. + + To convert the data to a QString use QString::fromLocal8Bit(). + + \note on desktop Windows, qgetenv() may produce data loss if the + original string contains Unicode characters not representable in the + ANSI encoding. Use qEnvironmentVariable() instead. + On Unix systems, this function is lossless. + + \sa qputenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet(), + qEnvironmentVariableIsEmpty() +*/ +QByteArray qgetenv(const char *varName) +{ + const auto locker = qt_scoped_lock(environmentMutex); +#ifdef Q_CC_MSVC + size_t requiredSize = 0; + QByteArray buffer; + getenv_s(&requiredSize, 0, 0, varName); + if (requiredSize == 0) + return buffer; + buffer.resize(qsizetype(requiredSize)); + getenv_s(&requiredSize, buffer.data(), requiredSize, varName); + // requiredSize includes the terminating null, which we don't want. + Q_ASSERT(buffer.endsWith('\0')); + buffer.chop(1); + return buffer; +#else + return QByteArray(::getenv(varName)); +#endif +} + +/*! + \fn QString qEnvironmentVariable(const char *varName, const QString &defaultValue) + \fn QString qEnvironmentVariable(const char *varName) + + \relates <QtEnvironmentVariables> + \since 5.10 + + These functions return the value of the environment variable, \a varName, as a + QString. If no variable \a varName is found in the environment and \a defaultValue + is provided, \a defaultValue is returned. Otherwise QString() is returned. + + The Qt environment manipulation functions are thread-safe, but this + requires that the C library equivalent functions like getenv and putenv are + not directly called. + + The following table describes how to choose between qgetenv() and + qEnvironmentVariable(): + \table + \header \li Condition \li Recommendation + \row + \li Variable contains file paths or user text + \li qEnvironmentVariable() + \row + \li Windows-specific code + \li qEnvironmentVariable() + \row + \li Unix-specific code, destination variable is not QString and/or is + used to interface with non-Qt APIs + \li qgetenv() + \row + \li Destination variable is a QString + \li qEnvironmentVariable() + \row + \li Destination variable is a QByteArray or std::string + \li qgetenv() + \endtable + + \note on Unix systems, this function may produce data loss if the original + string contains arbitrary binary data that cannot be decoded by the locale + codec. Use qgetenv() instead for that case. On Windows, this function is + lossless. + + \note the variable name \a varName must contain only US-ASCII characters. + + \sa qputenv(), qgetenv(), qEnvironmentVariableIsSet(), qEnvironmentVariableIsEmpty() +*/ +QString qEnvironmentVariable(const char *varName, const QString &defaultValue) +{ +#if defined(Q_OS_WIN) + QVarLengthArray<wchar_t, 32> wname(qsizetype(strlen(varName)) + 1); + for (qsizetype i = 0; i < wname.size(); ++i) // wname.size() is correct: will copy terminating null + wname[i] = uchar(varName[i]); + size_t requiredSize = 0; + auto locker = qt_unique_lock(environmentMutex); + _wgetenv_s(&requiredSize, 0, 0, wname.data()); + if (requiredSize == 0) + return defaultValue; + QString buffer(qsizetype(requiredSize), Qt::Uninitialized); + _wgetenv_s(&requiredSize, reinterpret_cast<wchar_t *>(buffer.data()), requiredSize, + wname.data()); + locker.unlock(); + // requiredSize includes the terminating null, which we don't want. + Q_ASSERT(buffer.endsWith(QChar(u'\0'))); + buffer.chop(1); + return buffer; +#else + QByteArray value = qgetenv(varName); + if (value.isNull()) + return defaultValue; +// duplicated in qfile.h (QFile::decodeName) +#if defined(Q_OS_DARWIN) + return QString::fromUtf8(value).normalized(QString::NormalizationForm_C); +#else // other Unix + return QString::fromLocal8Bit(value); +#endif +#endif +} + +QString qEnvironmentVariable(const char *varName) +{ + return qEnvironmentVariable(varName, QString()); +} + +/*! + \relates <QtEnvironmentVariables> + \since 5.1 + + Returns whether the environment variable \a varName is empty. + + Equivalent to + \snippet code/src_corelib_global_qglobal.cpp is-empty + except that it's potentially much faster, and can't throw exceptions. + + \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet() +*/ +bool qEnvironmentVariableIsEmpty(const char *varName) noexcept +{ + const auto locker = qt_scoped_lock(environmentMutex); +#ifdef Q_CC_MSVC + // we provide a buffer that can only hold the empty string, so + // when the env.var isn't empty, we'll get an ERANGE error (buffer + // too small): + size_t dummy; + char buffer = '\0'; + return getenv_s(&dummy, &buffer, 1, varName) != ERANGE; +#else + const char * const value = ::getenv(varName); + return !value || !*value; +#endif +} + +/*! + \relates <QtEnvironmentVariables> + \since 5.5 + + Returns the numerical value of the environment variable \a varName. + If \a ok is not null, sets \c{*ok} to \c true or \c false depending + on the success of the conversion. + + Equivalent to + \snippet code/src_corelib_global_qglobal.cpp to-int + except that it's much faster, and can't throw exceptions. + + \note there's a limit on the length of the value, which is sufficient for + all valid values of int, not counting leading zeroes or spaces. Values that + are too long will either be truncated or this function will set \a ok to \c + false. + + \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsSet() +*/ +int qEnvironmentVariableIntValue(const char *varName, bool *ok) noexcept +{ + static const int NumBinaryDigitsPerOctalDigit = 3; + static const int MaxDigitsForOctalInt = + (std::numeric_limits<uint>::digits + NumBinaryDigitsPerOctalDigit - 1) / NumBinaryDigitsPerOctalDigit; + + const auto locker = qt_scoped_lock(environmentMutex); + size_t size; +#ifdef Q_CC_MSVC + // we provide a buffer that can hold any int value: + char buffer[MaxDigitsForOctalInt + 2]; // +1 for NUL +1 for optional '-' + size_t dummy; + if (getenv_s(&dummy, buffer, sizeof buffer, varName) != 0) { + if (ok) + *ok = false; + return 0; + } + size = strlen(buffer); +#else + const char * const buffer = ::getenv(varName); + if (!buffer || (size = strlen(buffer)) > MaxDigitsForOctalInt + 2) { + if (ok) + *ok = false; + return 0; + } +#endif + return QByteArrayView(buffer, size).toInt(ok, 0); +} + +/*! + \relates <QtEnvironmentVariables> + \since 5.1 + + Returns whether the environment variable \a varName is set. + + Equivalent to + \snippet code/src_corelib_global_qglobal.cpp is-null + except that it's potentially much faster, and can't throw exceptions. + + \sa qgetenv(), qEnvironmentVariable(), qEnvironmentVariableIsEmpty() +*/ +bool qEnvironmentVariableIsSet(const char *varName) noexcept +{ + const auto locker = qt_scoped_lock(environmentMutex); +#ifdef Q_CC_MSVC + size_t requiredSize = 0; + (void)getenv_s(&requiredSize, 0, 0, varName); + return requiredSize != 0; +#else + return ::getenv(varName) != nullptr; +#endif +} + +/*! + \fn bool qputenv(const char *varName, QByteArrayView value) + \relates <QtEnvironmentVariables> + + This function sets the \a value of the environment variable named + \a varName. It will create the variable if it does not exist. It + returns 0 if the variable could not be set. + + Calling qputenv with an empty value removes the environment variable on + Windows, and makes it set (but empty) on Unix. Prefer using qunsetenv() + for fully portable behavior. + + \note qputenv() was introduced because putenv() from the standard + C library was deprecated in VC2005 (and later versions). qputenv() + uses the replacement function in VC, and calls the standard C + library's implementation on all other platforms. + + \note In Qt versions prior to 6.5, the \a value argument was QByteArray, + not QByteArrayView. + + \sa qgetenv(), qEnvironmentVariable() +*/ +bool qputenv(const char *varName, QByteArrayView raw) +{ + auto protect = [](const char *str) { return str ? str : ""; }; + + std::string value{protect(raw.data()), size_t(raw.size())}; // NUL-terminates w/SSO + +#if defined(Q_CC_MSVC) + const auto locker = qt_scoped_lock(environmentMutex); + return _putenv_s(varName, value.data()) == 0; +#elif (defined(_POSIX_VERSION) && (_POSIX_VERSION-0) >= 200112L) || defined(Q_OS_HAIKU) + // POSIX.1-2001 has setenv + const auto locker = qt_scoped_lock(environmentMutex); + return setenv(varName, value.data(), true) == 0; +#else + std::string buffer; + buffer += protect(varName); + buffer += '='; + buffer += value; + char *envVar = qstrdup(buffer.data()); + int result = [&] { + const auto locker = qt_scoped_lock(environmentMutex); + return putenv(envVar); + }(); + if (result != 0) // error. we have to delete the string. + delete[] envVar; + return result == 0; +#endif +} + +/*! + \relates <QtEnvironmentVariables> + + This function deletes the variable \a varName from the environment. + + Returns \c true on success. + + \since 5.1 + + \sa qputenv(), qgetenv(), qEnvironmentVariable() +*/ +bool qunsetenv(const char *varName) +{ +#if defined(Q_CC_MSVC) + const auto locker = qt_scoped_lock(environmentMutex); + return _putenv_s(varName, "") == 0; +#elif (defined(_POSIX_VERSION) && (_POSIX_VERSION-0) >= 200112L) || defined(Q_OS_BSD4) || defined(Q_OS_HAIKU) + // POSIX.1-2001, BSD and Haiku have unsetenv + const auto locker = qt_scoped_lock(environmentMutex); + return unsetenv(varName) == 0; +#elif defined(Q_CC_MINGW) + // On mingw, putenv("var=") removes "var" from the environment + QByteArray buffer(varName); + buffer += '='; + const auto locker = qt_scoped_lock(environmentMutex); + return putenv(buffer.constData()) == 0; +#else + // Fallback to putenv("var=") which will insert an empty var into the + // environment and leak it + QByteArray buffer(varName); + buffer += '='; + char *envVar = qstrdup(buffer.constData()); + const auto locker = qt_scoped_lock(environmentMutex); + return putenv(envVar) == 0; +#endif +} + +/* Various time-related APIs that need to consult system settings also need + protection with the same lock as the environment, since those system settings + include part of the environment (principally TZ). + + First, tzset(), which POSIX explicitly says accesses the environment. +*/ +void qTzSet() +{ + const auto locker = qt_scoped_lock(environmentMutex); +#if defined(Q_OS_WIN) + _tzset(); +#else + tzset(); +#endif // Q_OS_WIN +} + +/* Wrap mktime(), which is specified to behave as if it called tzset(), hence + shares its implicit environment-dependence. +*/ +time_t qMkTime(struct tm *when) +{ + const auto locker = qt_scoped_lock(environmentMutex); +#if defined(Q_OS_WIN) + // QTBUG-83881 MS's mktime() seems to need _tzset() called first. + _tzset(); +#endif + return mktime(when); +} + +/* For localtime(), POSIX mandates that it behave as if it called tzset(). + For the alternatives to it, we need (if only for compatibility) to do the + same explicitly, which should ensure a re-parse of timezone info. +*/ +bool qLocalTime(time_t utc, struct tm *local) +{ + const auto locker = qt_scoped_lock(environmentMutex); +#if defined(Q_OS_WIN) + // The doc of localtime_s() says that it corrects for the same things + // _tzset() sets the globals for, but QTBUG-109974 reveals a need for an + // explicit call, all the same. + _tzset(); + return !localtime_s(local, &utc); +#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // Use the reentrant version of localtime() where available, as it is + // thread-safe and doesn't use a shared static data area. + // As localtime_r() is not specified to work as if it called tzset(), + // make an explicit call. + tzset(); + if (tm *res = localtime_r(&utc, local)) { + Q_ASSERT(res == local); + Q_UNUSED(res); + return true; + } + return false; +#else + // POSIX mandates that localtime() behaves as if it called tzset(). + // Returns shared static data which may be overwritten at any time (albeit + // our lock probably keeps it safe). So copy the result promptly: + if (tm *res = localtime(&utc)) { + *local = *res; + return true; + } + return false; +#endif +} + +/* Access to the tzname[] global in one thread is UB if any other is calling + tzset() or anything that behaves as if it called tzset(). So also lock this + access to prevent such collisions. + + Parameter dstIndex must be 1 for DST or 0 for standard time. + Returns the relevant form of the name of local-time's zone. +*/ +QString qTzName(int dstIndex) +{ + char name[512]; + bool ok; +#if defined(_UCRT) // i.e., MSVC and MinGW-UCRT + size_t s = 0; + { + const auto locker = qt_scoped_lock(environmentMutex); + ok = _get_tzname(&s, name, 512, dstIndex) != 0; + } +#else + { + const auto locker = qt_scoped_lock(environmentMutex); + const char *const src = tzname[dstIndex]; + ok = src != nullptr; + if (ok) + memcpy(name, src, std::min(sizeof(name), strlen(src) + 1)); + } +#endif // Q_OS_WIN + return ok ? QString::fromLocal8Bit(name) : QString(); +} + +QT_END_NAMESPACE |