summaryrefslogtreecommitdiffstats
path: root/src/corelib/global/qtenvironmentvariables.cpp
blob: 4046beae1f4eb7de0a275a1746227f29a119f5cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// 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 <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
}

/*
    Wraps tzset(), which accesses the environment, so should only be called while
    we hold the lock on the environment mutex.
*/
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);
    return mktime(when);
}

QT_END_NAMESPACE