From 02ef8fab7e511f50e901676eac15229eb456b01c Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 25 Nov 2008 16:44:11 +0100 Subject: Add a new class for handling a process's environment variables. First of all, make it a lot easier to access individual variables by having them in an associative container (a QHash). This fixes task 232427, albeit one release later than I had originally planned. On Windows, the variable names in the environment are case-insensitive, so a direct QHash isn't a good solution. Implement code that does the uppercasing on Windows and leaves untransformed on other platforms. Since we're doing this anyways, use QByteArray on Unix systems, since, in theory, the environment could contain any random binary data, which is not representable in QString. Task-number: 232427 --- doc/src/snippets/qprocess-environment/main.cpp | 6 +- src/corelib/io/qprocess.cpp | 242 +++++++++++++++++++------ src/corelib/io/qprocess.h | 44 ++++- src/corelib/io/qprocess_p.h | 17 +- src/corelib/io/qprocess_unix.cpp | 26 +-- src/corelib/io/qprocess_win.cpp | 14 +- tests/auto/qprocess/tst_qprocess.cpp | 37 +++- 7 files changed, 300 insertions(+), 86 deletions(-) diff --git a/doc/src/snippets/qprocess-environment/main.cpp b/doc/src/snippets/qprocess-environment/main.cpp index a143bf8..0fa0896 100644 --- a/doc/src/snippets/qprocess-environment/main.cpp +++ b/doc/src/snippets/qprocess-environment/main.cpp @@ -57,10 +57,10 @@ process.start("myapp"); { //! [1] QProcess process; -QHash env = QProcess::systemEnvironmentHash(); +QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("TMPDIR", "C:\\MyApp\\temp"); // Add an environment variable -env["PATH"] += ";C:\\Bin"; -process.setEnvironment(env); +env.insert("PATH", env.value("Path") + ";C:\\Bin"); +process.setProcessEnvironment(env); process.start("myapp"); //! [1] } diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index ccc16b2..3d143e9 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -101,27 +101,169 @@ QT_END_NAMESPACE QT_BEGIN_NAMESPACE -static QHash environmentHashFromList(const QStringList &environment) +#ifdef Q_OS_WIN +static inline QProcessEnvironmentPrivate::Unit prepareName(const QString &name) +{ return name.toUpper(); } +static inline QProcessEnvironmentPrivate::Unit prepareName(const QByteArray &name) +{ return QString::fromLocal8Bit(name).toUpper(); } +static inline QString nameToString(const QProcessEnvironmentPrivate::Unit &name) +{ return name; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QString &value) +{ return value; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QByteArray &value) +{ return QString::fromLocal8Bit(value); } +static inline QString valueToString(const QProcessEnvironmentPrivate::Unit &value) +{ return value; } +static inline QByteArray valueToByteArray(const QProcessEnvironmentPrivate::Unit &value) +{ return value.toLocal8Bit(); } +#else +static inline QProcessEnvironmentPrivate::Unit prepareName(const QByteArray &name) +{ return name; } +static inline QProcessEnvironmentPrivate::Unit prepareName(const QString &name) +{ return name.toLocal8Bit(); } +static inline QString nameToString(const QProcessEnvironmentPrivate::Unit &name) +{ return QString::fromLocal8Bit(name); } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QByteArray &value) +{ return value; } +static inline QProcessEnvironmentPrivate::Unit prepareValue(const QString &value) +{ return value.toLocal8Bit(); } +static inline QString valueToString(const QProcessEnvironmentPrivate::Unit &value) +{ return QString::fromLocal8Bit(value); } +static inline QByteArray valueToByteArray(const QProcessEnvironmentPrivate::Unit &value) +{ return value; } +#endif + +template<> void QSharedDataPointer::detach() { - QHash result; - QStringList::ConstIterator it = environment.constBegin(), - end = environment.constEnd(); + if (d && d->ref == 1) + return; + QProcessEnvironmentPrivate *x = (d ? new QProcessEnvironmentPrivate(*d) + : new QProcessEnvironmentPrivate); + x->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = x; +} + +QStringList QProcessEnvironmentPrivate::toList() const +{ + QStringList result; + QHash::ConstIterator it = hash.constBegin(), + end = hash.constEnd(); for ( ; it != end; ++it) { - int equals = it->indexOf(QLatin1Char('=')); + QString data = nameToString(it.key()); + QString value = valueToString(it.value()); + data.reserve(data.length() + value.length() + 1); + data.append(QLatin1Char('=')); + data.append(value); + result << data; + } + return result; +} + +QProcessEnvironment QProcessEnvironmentPrivate::fromList(const QStringList &list) +{ + QProcessEnvironment env; + QStringList::ConstIterator it = list.constBegin(), + end = list.constEnd(); + for ( ; it != end; ++it) { + int pos = it->indexOf(QLatin1Char('=')); + if (pos < 1) + continue; + QString value = it->mid(pos + 1); QString name = *it; - QString value; - if (equals != -1) { - name.truncate(equals); -#ifdef Q_OS_WIN - name = name.toUpper(); -#endif - value = it->mid(equals + 1); - } - result.insert(name, value); + name.truncate(pos); + env.insert(name, value); } + return env; +} - return result; +QProcessEnvironment::QProcessEnvironment() + : d(0) +{ +} + +QProcessEnvironment::~QProcessEnvironment() +{ +} + +QProcessEnvironment::QProcessEnvironment(const QProcessEnvironment &other) + : d(other.d) +{ +} + +QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &other) +{ + d = other.d; + return *this; +} + +bool QProcessEnvironment::operator==(const QProcessEnvironment &other) const +{ + return d->hash == other.d->hash; +} + +bool QProcessEnvironment::isEmpty() const +{ + return d ? d->hash.isEmpty() : true; +} + +void QProcessEnvironment::clear() +{ + d->hash.clear(); +} + +bool QProcessEnvironment::contains(const QString &name) const +{ + return d ? d->hash.contains(prepareName(name)) : false; +} + +void QProcessEnvironment::insert(const QString &name, const QString &value) +{ + d->hash.insert(prepareName(name), prepareValue(value)); +} + +void QProcessEnvironment::remove(const QString &name) +{ + d->hash.remove(prepareName(name)); +} + +QString QProcessEnvironment::value(const QString &name, const QString &defaultValue) const +{ + if (!d) + return defaultValue; + + QProcessEnvironmentPrivate::Unit result = d->hash.value(prepareName(name), prepareValue(defaultValue)); + return valueToString(result); +} + +bool QProcessEnvironment::containsRaw(const QByteArray &name) const +{ + return d ? d->hash.contains(prepareName(name)) : false; +} + +void QProcessEnvironment::insertRaw(const QByteArray &name, const QByteArray &value) +{ + d->hash.insert(prepareName(name), prepareValue(value)); +} + +void QProcessEnvironment::removeRaw(const QByteArray &name) +{ + d->hash.remove(prepareName(name)); +} + +QByteArray QProcessEnvironment::valueRaw(const QByteArray &name, const QByteArray &defaultValue) const +{ + if (!d) + return defaultValue; + QProcessEnvironmentPrivate::Unit result = d->hash.value(prepareName(name), prepareValue(defaultValue)); + return valueToByteArray(result); +} + +QStringList QProcessEnvironment::toStringList() const +{ + return d ? d->toList() : QStringList(); } void QProcessPrivate::Channel::clear() @@ -446,7 +588,6 @@ QProcessPrivate::QProcessPrivate() sequenceNumber = 0; exitCode = 0; exitStatus = QProcess::NormalExit; - environment = 0; startupSocketNotifier = 0; deathNotifier = 0; notifier = 0; @@ -473,7 +614,6 @@ QProcessPrivate::QProcessPrivate() */ QProcessPrivate::~QProcessPrivate() { - delete environment; if (stdinChannel.process) stdinChannel.process->stdoutChannel.clear(); if (stdoutChannel.process) @@ -1215,6 +1355,7 @@ QProcess::ProcessState QProcess::state() const } /*! + \deprecated Sets the environment that QProcess will use when starting a process to the \a environment specified which consists of a list of key=value pairs. @@ -1223,14 +1364,15 @@ QProcess::ProcessState QProcess::state() const \snippet doc/src/snippets/qprocess-environment/main.cpp 0 - \sa environment(), systemEnvironment(), setEnvironmentHash() + \sa environment(), setProcessEnvironment(), systemEnvironment() */ void QProcess::setEnvironment(const QStringList &environment) { - setEnvironmentHash(environmentHashFromList(environment)); + setProcessEnvironment(QProcessEnvironmentPrivate::fromList(environment)); } /*! + \deprecated Returns the environment that QProcess will use when starting a process, or an empty QStringList if no environment has been set using setEnvironment() or setEnvironmentHash(). If no environment @@ -1239,67 +1381,50 @@ void QProcess::setEnvironment(const QStringList &environment) \note The environment settings are ignored on Windows CE, as there is no concept of an environment. - \sa environmentHash(), setEnvironment(), systemEnvironment() + \sa processEnvironment(), setEnvironment(), systemEnvironment() */ QStringList QProcess::environment() const { Q_D(const QProcess); - - QStringList result; - if (!d->environment) - return result; - - QHash::ConstIterator it = d->environment->constBegin(), - end = d->environment->constEnd(); - for ( ; it != end; ++it) { - QString data = it.key(); - data.reserve(data.length() + it.value().length() + 1); - data.append(QLatin1Char('=')); - data.append(it.value()); - result << data; - } - return result; + return d->environment.toStringList(); } /*! \since 4.5 Sets the environment that QProcess will use when starting a process to the - \a environment hash map. + \a environment object. For example, the following code adds the \c{C:\\BIN} directory to the list of executable paths (\c{PATHS}) on Windows and sets \c{TMPDIR}: \snippet doc/src/snippets/qprocess-environment/main.cpp 1 - \sa environment(), systemEnvironmentHash(), setEnvironment() + Note how, on Windows, environment variable names are case-insensitive. + + \sa processEnvironment(), QProcessEnvironment::systemEnvironment(), setEnvironment() */ -void QProcess::setEnvironmentHash(const QHash &environment) +void QProcess::setProcessEnvironment(const QProcessEnvironment &environment) { Q_D(QProcess); - if (!d->environment) - d->environment = new QHash(environment); - else - *d->environment = environment; + d->environment = environment; } /*! \since 4.5 Returns the environment that QProcess will use when starting a - process, or an empty QHash if no environment has been set using - setEnvironment() or setEnvironmentHash(). If no environment has + process, or an empty object if no environment has been set using + setEnvironment() or setProcessEnvironment(). If no environment has been set, the environment of the calling process will be used. \note The environment settings are ignored on Windows CE, as there is no concept of an environment. - \sa setEnvironmentHash(), setEnvironment(), systemEnvironmentHash() + \sa setProcessEnvironment(), setEnvironment(), QProcessEnvironment::isValid() */ -QHash QProcess::environmentHash() const +QProcessEnvironment QProcess::processEnvironment() const { Q_D(const QProcess); - if (d->environment) - return *d->environment; - return QHash(); + return d->environment; } /*! @@ -1898,7 +2023,7 @@ QT_END_INCLUDE_NAMESPACE \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 8 - \sa systemEnvironmentHash(), environment(), setEnvironment() + \sa QProcessEnvironment::systemEnvironment(), environment(), setEnvironment() */ QStringList QProcess::systemEnvironment() { @@ -1915,11 +2040,22 @@ QStringList QProcess::systemEnvironment() Returns the environment of the calling process as a QHash. - \sa systemEnvironment(), environmentHash(), setEnvironmentHash() + \sa QProcess::systemEnvironment() */ -QHash QProcess::systemEnvironmentHash() +QProcessEnvironment QProcessEnvironment::systemEnvironment() { - return environmentHashFromList(systemEnvironment()); + QProcessEnvironment env; + const char *entry; + for (int count = 0; (entry = environ[count]); ++count) { + const char *equal = strchr(entry, '='); + if (!equal) + continue; + + QByteArray name(entry, equal - entry); + QByteArray value(equal + 1); + env.insertRaw(name, value); + } + return env; } /*! diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h index 096f625..116168b 100644 --- a/src/corelib/io/qprocess.h +++ b/src/corelib/io/qprocess.h @@ -44,6 +44,7 @@ #include #include +#include QT_BEGIN_HEADER @@ -53,8 +54,6 @@ QT_MODULE(Core) #ifndef QT_NO_PROCESS -template class QHash; - #if (!defined(Q_OS_WIN32) && !defined(Q_OS_WINCE)) || defined(qdoc) typedef qint64 Q_PID; #else @@ -64,6 +63,42 @@ QT_BEGIN_NAMESPACE #endif class QProcessPrivate; +class QProcessEnvironmentPrivate; + +class Q_CORE_EXPORT QProcessEnvironment +{ +public: + QProcessEnvironment(); + QProcessEnvironment(const QProcessEnvironment &other); + ~QProcessEnvironment(); + QProcessEnvironment &operator=(const QProcessEnvironment &other); + + bool operator==(const QProcessEnvironment &other) const; + inline bool operator!=(const QProcessEnvironment &other) const + { return !(*this == other); } + + bool isEmpty() const; + void clear(); + + bool contains(const QString &name) const; + void insert(const QString &name, const QString &value); + void remove(const QString &name); + QString value(const QString &name, const QString &defaultValue = QString()) const; + + bool containsRaw(const QByteArray &name) const; + void insertRaw(const QByteArray &name, const QByteArray &value); + void removeRaw(const QByteArray &name); + QByteArray valueRaw(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const; + + QStringList toStringList() const; + + static QProcessEnvironment systemEnvironment(); + +private: + friend class QProcessPrivate; + friend class QProcessEnvironmentPrivate; + QSharedDataPointer d; +}; class Q_CORE_EXPORT QProcess : public QIODevice { @@ -123,8 +158,8 @@ public: void setEnvironment(const QStringList &environment); QStringList environment() const; - void setEnvironmentHash(const QHash &environment); - QHash environmentHash() const; + void setProcessEnvironment(const QProcessEnvironment &environment); + QProcessEnvironment processEnvironment() const; QProcess::ProcessError error() const; QProcess::ProcessState state() const; @@ -160,7 +195,6 @@ public: static bool startDetached(const QString &program); static QStringList systemEnvironment(); - static QHash systemEnvironmentHash(); public Q_SLOTS: void terminate(); diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h index 5482871..3b04d88 100644 --- a/src/corelib/io/qprocess_p.h +++ b/src/corelib/io/qprocess_p.h @@ -55,6 +55,7 @@ #include "QtCore/qprocess.h" #include "QtCore/qstringlist.h" +#include "QtCore/qhash.h" #include "private/qringbuffer_p.h" #include "private/qiodevice_p.h" @@ -76,6 +77,20 @@ class QWindowsPipeWriter; class QWinEventNotifier; class QTimer; +class QProcessEnvironmentPrivate: public QSharedData +{ +public: +#ifdef Q_OS_WIN + typedef QString Unit; +#else + typedef QByteArray Unit; +#endif + QHash hash; + + static QProcessEnvironment fromList(const QStringList &list); + QStringList toList() const; +}; + class QProcessPrivate : public QIODevicePrivate { public: @@ -161,7 +176,7 @@ public: QString program; QStringList arguments; - QHash *environment; + QProcessEnvironment environment; QRingBuffer outputReadBuffer; QRingBuffer errorReadBuffer; diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index d28cdc4..dfeeb71 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -458,11 +458,11 @@ bool QProcessPrivate::createChannel(Channel &channel) } } -static char **_q_dupEnvironment(const QHash *environment, int *envc) +static char **_q_dupEnvironment(const QHash &environment, int *envc) { *envc = 0; - if (!environment) - return 0; // use the default environment + if (environment.isEmpty()) + return 0; // if LD_LIBRARY_PATH exists in the current environment, but // not in the environment list passed by the programmer, then @@ -474,17 +474,17 @@ static char **_q_dupEnvironment(const QHash *environment, int #endif const QByteArray envLibraryPath = qgetenv(libraryPath); bool needToAddLibraryPath = !envLibraryPath.isEmpty() && - !environment->contains(QLatin1String(libraryPath)); + !environment.contains(libraryPath); - char **envp = new char *[environment->count() + 2]; - envp[environment->count()] = 0; - envp[environment->count() + 1] = 0; + char **envp = new char *[environment.count() + 2]; + envp[environment.count()] = 0; + envp[environment.count() + 1] = 0; - QHash::ConstIterator it = environment->constBegin(); - const QHash::ConstIterator end = environment->constEnd(); + QHash::ConstIterator it = environment.constBegin(); + const QHash::ConstIterator end = environment.constEnd(); for ( ; it != end; ++it) { - QByteArray key = it.key().toLocal8Bit(); - QByteArray value = it.value().toLocal8Bit(); + QByteArray key = it.key(); + QByteArray value = it.value(); key.reserve(key.length() + 1 + value.length()); key.append('='); key.append(value); @@ -590,7 +590,9 @@ void QProcessPrivate::startProcess() // Duplicate the environment. int envc = 0; - char **envp = _q_dupEnvironment(environment, &envc); + char **envp = 0; + if (environment.d.constData()) + envp = _q_dupEnvironment(environment.d.constData()->hash, &envc); // Encode the working directory if it's non-empty, otherwise just pass 0. const char *workingDirPtr = 0; diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp index acb169f..50a4a00 100644 --- a/src/corelib/io/qprocess_win.cpp +++ b/src/corelib/io/qprocess_win.cpp @@ -278,17 +278,17 @@ static QString qt_create_commandline(const QString &program, const QStringList & return args; } -static QByteArray qt_create_environment(const QHash *environment) +static QByteArray qt_create_environment(const QHash &environment) { QByteArray envlist; - if (environment) { - QHash copy = *environment; + if (!environment.isEmpty()) { + QHash copy = environment; // add PATH if necessary (for DLL loading) if (!copy.contains(QLatin1String("PATH"))) { QByteArray path = qgetenv("PATH"); if (!path.isEmpty()) - copy.insert(QLatin1String("PATH"), QString::fromLocal8Bit(path)); + copy.insert(QLatin1String("PATH"), QString::fromLocal8Bit(path)); } // add systemroot if needed @@ -362,7 +362,9 @@ void QProcessPrivate::startProcess() QString args = qt_create_commandline(QString(), arguments); #else QString args = qt_create_commandline(program, arguments); - QByteArray envlist = qt_create_environment(environment); + QByteArray envlist; + if (environment.d.constData()) + envlist = qt_create_environment(environment.d.constData()->hash); #endif #if defined QPROCESS_DEBUG @@ -393,7 +395,7 @@ void QProcessPrivate::startProcess() }; success = CreateProcess(0, (wchar_t*)args.utf16(), 0, 0, TRUE, dwCreationFlags, - environment ? envlist.data() : 0, + environment.isEmpty() ? 0 : envlist.data(), workingDirectory.isEmpty() ? 0 : (wchar_t*)QDir::toNativeSeparators(workingDirectory).utf16(), &startupInfo, pid); diff --git a/tests/auto/qprocess/tst_qprocess.cpp b/tests/auto/qprocess/tst_qprocess.cpp index b57139b..d2af86a 100644 --- a/tests/auto/qprocess/tst_qprocess.cpp +++ b/tests/auto/qprocess/tst_qprocess.cpp @@ -130,6 +130,8 @@ private slots: void exitCodeTest(); void setEnvironment_data(); void setEnvironment(); + void setProcessEnvironment_data(); + void setProcessEnvironment(); void systemEnvironment(); void spaceInName(); void lockupsInStartDetached(); @@ -1697,16 +1699,39 @@ void tst_QProcess::setEnvironment() QCOMPARE(process.readAll(), value.toLocal8Bit()); } +#endif +} + +//----------------------------------------------------------------------------- +void tst_QProcess::setProcessEnvironment_data() +{ + setEnvironment_data(); +} + +void tst_QProcess::setProcessEnvironment() +{ +#if !defined (Q_OS_WINCE) + // there is no concept of system variables on Windows CE as there is no console + + // make sure our environment variables are correct + QVERIFY(qgetenv("tst_QProcess").isEmpty()); + QVERIFY(!qgetenv("PATH").isEmpty()); +#ifdef Q_OS_WIN + QVERIFY(!qgetenv("PROMPT").isEmpty()); +#endif + + QFETCH(QString, name); + QFETCH(QString, value); + QString executable = QDir::currentPath() + "/testProcessEnvironment/testProcessEnvironment"; - // use the hash variant now { QProcess process; - QHash environment = QProcess::systemEnvironmentHash(); + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); if (value.isNull()) environment.remove(name); else environment.insert(name, value); - process.setEnvironmentHash(environment); + process.setProcessEnvironment(environment); process.start(executable, QStringList() << name); QVERIFY(process.waitForFinished()); @@ -1725,12 +1750,12 @@ void tst_QProcess::systemEnvironment() #if defined (Q_OS_WINCE) // there is no concept of system variables on Windows CE as there is no console QVERIFY(QProcess::systemEnvironment().isEmpty()); - QVERIFY(QProcess::systemEnvironmentHash().isEmpty()); + QVERIFY(QProcessEnvironment::systemEnvironment().isEmpty()); #else QVERIFY(!QProcess::systemEnvironment().isEmpty()); - QVERIFY(!QProcess::systemEnvironmentHash().isEmpty()); + QVERIFY(!QProcessEnvironment::systemEnvironment().isEmpty()); - QVERIFY(QProcess::systemEnvironmentHash().contains("PATH")); + QVERIFY(QProcessEnvironment::systemEnvironment().contains("PATH")); QVERIFY(!QProcess::systemEnvironment().filter(QRegExp("^PATH=", Qt::CaseInsensitive)).isEmpty()); #endif } -- cgit v0.12