aur/0001-Simplify-Server-s-python-setup.patch
Jan Alexander Steffens (heftig) 86838d46a3 1.5
2017-05-20 09:18:11 +02:00

633 lines
22 KiB
Diff

From a5a1c332e23a094e0c2a1c9513fb9f6b05cbbf05 Mon Sep 17 00:00:00 2001
From: "Jan Alexander Steffens (heftig)" <jan.steffens@gmail.com>
Date: Sat, 28 Jan 2017 01:19:06 +0100
Subject: [PATCH] Simplify Server's python setup
---
runtime/BrowserWindow.cpp | 6 +-
runtime/ConfigWindow.cpp | 16 ++-
runtime/ConfigWindow.h | 6 +-
runtime/ConfigWindow.ui | 22 +++-
runtime/Server.cpp | 253 +++++++++++-----------------------------------
runtime/Server.h | 11 +-
runtime/pgAdmin4.cpp | 106 ++++++++++---------
7 files changed, 149 insertions(+), 271 deletions(-)
diff --git a/runtime/BrowserWindow.cpp b/runtime/BrowserWindow.cpp
index 516b8689818bddc9..a2ee4895f5351565 100644
--- a/runtime/BrowserWindow.cpp
+++ b/runtime/BrowserWindow.cpp
@@ -1286,17 +1286,17 @@ void BrowserWindow::preferences()
ConfigWindow *dlg = new ConfigWindow();
dlg->setWindowTitle(QWidget::tr("Configuration"));
- dlg->setPythonPath(settings.value("PythonPath").toString());
+ dlg->setPythonExecutable(settings.value("PythonExecutable").toString());
dlg->setApplicationPath(settings.value("ApplicationPath").toString());
dlg->setModal(true);
ok = dlg->exec();
- QString pythonpath = dlg->getPythonPath();
+ QString pythonexecutable = dlg->getPythonExecutable();
QString applicationpath = dlg->getApplicationPath();
if (ok)
{
- settings.setValue("PythonPath", pythonpath);
+ settings.setValue("PythonExecutable", pythonexecutable);
settings.setValue("ApplicationPath", applicationpath);
}
}
diff --git a/runtime/ConfigWindow.cpp b/runtime/ConfigWindow.cpp
index 3fb1a2738eb89ec0..c31345bf08d06d88 100644
--- a/runtime/ConfigWindow.cpp
+++ b/runtime/ConfigWindow.cpp
@@ -17,37 +17,45 @@ ConfigWindow::ConfigWindow(QWidget *parent) :
ui(new Ui::ConfigWindow)
{
ui->setupUi(this);
+
+#ifdef PYTHON2
+ ui->pythonExecutableLineEdit->setPlaceholderText(
+ QString(QWidget::tr("The Python 2 executable to use (may be within a virtual environment)")));
+#else
+ ui->pythonExecutableLineEdit->setPlaceholderText(
+ QString(QWidget::tr("The Python 3 executable to use (may be within a virtual environment)")));
+#endif
}
ConfigWindow::~ConfigWindow()
{
delete ui;
}
void ConfigWindow::on_buttonBox_accepted()
{
this->close();
}
void ConfigWindow::on_buttonBox_rejected()
{
this->close();
}
-QString ConfigWindow::getPythonPath()
+QString ConfigWindow::getPythonExecutable()
{
- return ui->pythonPathLineEdit->text();
+ return ui->pythonExecutableLineEdit->text();
}
QString ConfigWindow::getApplicationPath()
{
return ui->applicationPathLineEdit->text();
}
-void ConfigWindow::setPythonPath(QString path)
+void ConfigWindow::setPythonExecutable(QString path)
{
- ui->pythonPathLineEdit->setText(path);
+ ui->pythonExecutableLineEdit->setText(path);
}
void ConfigWindow::setApplicationPath(QString path)
diff --git a/runtime/ConfigWindow.h b/runtime/ConfigWindow.h
index 0027a742aca7b762..c4487fcc6e97f354 100644
--- a/runtime/ConfigWindow.h
+++ b/runtime/ConfigWindow.h
@@ -26,19 +26,19 @@ public:
explicit ConfigWindow(QWidget *parent = 0);
~ConfigWindow();
- QString getPythonPath();
+ QString getPythonExecutable();
QString getApplicationPath();
- void setPythonPath(QString path);
+ void setPythonExecutable(QString path);
void setApplicationPath(QString path);
private slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
private:
Ui::ConfigWindow *ui;
- QString m_pythonpath, m_applicationpath;
+ QString m_pythonexecutable, m_applicationpath;
};
#endif // CONFIGWINDOW_H
diff --git a/runtime/ConfigWindow.ui b/runtime/ConfigWindow.ui
index 40d87be43803204a..8b379052a1da8cd0 100644
--- a/runtime/ConfigWindow.ui
+++ b/runtime/ConfigWindow.ui
@@ -29,24 +29,38 @@
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
- <widget class="QLabel" name="pythonPathLabel">
+ <widget class="QLabel" name="pythonExecutableLabel">
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>1</height>
+ </size>
+ </property>
<property name="text">
- <string>Python Path</string>
+ <string>Python Executable</string>
</property>
</widget>
</item>
<item row="0" column="1">
- <widget class="QLineEdit" name="pythonPathLineEdit"/>
+ <widget class="QLineEdit" name="pythonExecutableLineEdit">
+ <property name="placeholderText">
+ <string>The python executable to use (may be within a virtual environment)</string>
+ </property>
+ </widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="applicationPathLabel">
<property name="text">
<string>Application Path</string>
</property>
</widget>
</item>
<item row="1" column="1">
- <widget class="QLineEdit" name="applicationPathLineEdit"/>
+ <widget class="QLineEdit" name="applicationPathLineEdit">
+ <property name="placeholderText">
+ <string>The directory containing pgAdmin4.py (do not include the file name)</string>
+ </property>
+ </widget>
</item>
</layout>
</item>
diff --git a/runtime/Server.cpp b/runtime/Server.cpp
index 33b43583480440b7..83bcdeedb09197c8 100644
--- a/runtime/Server.cpp
+++ b/runtime/Server.cpp
@@ -22,201 +22,85 @@
// App headers
#include "Server.h"
-static void add_to_path(QString &python_path, QString path, bool prepend=false)
-{
- if (!python_path.contains(path))
- {
- if (!prepend)
- {
-#if defined(Q_OS_WIN)
- if (!python_path.isEmpty() && !python_path.endsWith(";"))
- python_path.append(";");
-#else
- if (!python_path.isEmpty() && !python_path.endsWith(":"))
- python_path.append(":");
-#endif
-
- python_path.append(path);
- }
- else
- {
-#if defined(Q_OS_WIN)
- if (!python_path.isEmpty() && !python_path.startsWith(";"))
- python_path.prepend(";");
-#else
- if (!python_path.isEmpty() && !python_path.startsWith(":"))
- python_path.prepend(":");
-#endif
-
- python_path.prepend(path);
- }
- }
-}
-
Server::Server(quint16 port, QString key)
{
// Appserver port etc
m_port = port;
m_key = key;
- m_wcAppName = NULL;
- m_wcPythonHome = NULL;
// Initialise Python
- Py_NoSiteFlag=1;
- Py_DontWriteBytecodeFlag=1;
+ Py_DontWriteBytecodeFlag = 1;
- PGA_APP_NAME_UTF8 = PGA_APP_NAME.toUtf8();
+ // Get the application directory
+ QString appDir = qApp->applicationDirPath();
+
+ // Build (and canonicalise) the virtual environment path
+#ifdef Q_OS_MAC
+ QFileInfo venvBinPath(appDir + "/../Resources/venv/bin");
+#elif defined(Q_OS_WIN)
+ QFileInfo venvBinPath(appDir + "/../venv");
+#else
+ QFileInfo venvBinPath(appDir + "/../venv/bin");
+#endif
+ QString binPath = venvBinPath.canonicalFilePath();
+
+ if (binPath.isEmpty())
+ {
+ // Use default Python environment from the config, or lastly, PATH
+ QSettings settings;
+ binPath.append(settings.value("PythonExecutable").toString());
+ if (binPath.isEmpty())
+ binPath.append("python");
+ }
+ else
+ {
+ binPath.append("/python");
+ }
// Python3 requires conversion of char * to wchar_t *, so...
#ifdef PYTHON2
- Py_SetProgramName(PGA_APP_NAME_UTF8.data());
+ QByteArray appName = binPath.toLocal8Bit();
+ m_pyAppName = new char[strlen(appName.data()) + 1];
+ strcpy(m_pyAppName, appName.data());
+ qDebug() << "Python interpreter: " << QString::fromLocal8Bit(m_pyAppName);
#else
- char *appName = PGA_APP_NAME_UTF8.data();
- const size_t cSize = strlen(appName)+1;
- m_wcAppName = new wchar_t[cSize];
- mbstowcs (m_wcAppName, appName, cSize);
- Py_SetProgramName(m_wcAppName);
+ std::wstring appName = binPath.toStdWString();
+ m_pyAppName = new wchar_t[wcslen(appName.c_str()) + 1];
+ wcscpy(m_pyAppName, appName.c_str());
+ qDebug() << "Python interpreter: " << QString::fromWCharArray(m_pyAppName);
#endif
-
- // Setup the search path
- QSettings settings;
- QString python_path = settings.value("PythonPath").toString();
-
- // Get the application directory
- QString app_dir = qApp->applicationDirPath(),
- path_env = qgetenv("PATH"),
- pythonHome;
- QStringList path_list;
- int i;
-
-#ifdef Q_OS_MAC
- // In the case we're running in a release appbundle, we need to ensure the
- // bundled virtual env is included in the Python path. We include it at the
- // end, so expert users can override the path, but we do not save it, because
- // if users move the app bundle, we'll end up with dead entries
-
- // Build (and canonicalise) the virtual environment path
- QFileInfo venvBinPath(app_dir + "/../Resources/venv/bin");
- QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python");
- QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload");
- QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages");
- QFileInfo venvPath(app_dir + "/../Resources/venv");
-
- // Prepend the bin directory to the path
- add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
- // Append the path, if it's not already there
- add_to_path(python_path, venvLibPath.canonicalFilePath());
- add_to_path(python_path, venvDynLibPath.canonicalFilePath());
- add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
- add_to_path(pythonHome, venvPath.canonicalFilePath());
-#elif defined(Q_OS_WIN)
-
- // In the case we're running in a release application, we need to ensure the
- // bundled virtual env is included in the Python path. We include it at the
- // end, so expert users can override the path, but we do not save it.
-
- // Build (and canonicalise) the virtual environment path
- QFileInfo venvBinPath(app_dir + "/../venv");
- QFileInfo venvLibPath(app_dir + "/../venv/Lib");
- QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs");
- QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages");
- QFileInfo venvPath(app_dir + "/../venv");
-
- // Prepend the bin directory to the path
- add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
- // Append paths, if they're not already there
- add_to_path(python_path, venvLibPath.canonicalFilePath());
- add_to_path(python_path, venvDLLsPath.canonicalFilePath());
- add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
- add_to_path(pythonHome, venvPath.canonicalFilePath());
-#else
- // Build (and canonicalise) the virtual environment path
- QFileInfo venvBinPath(app_dir + "/../venv/bin");
- QFileInfo venvLibPath(app_dir + "/../venv/lib/python");
- QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload");
- QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages");
- QFileInfo venvPath(app_dir + "/../venv");
-
- // Prepend the bin directory to the path
- add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
- // Append the path, if it's not already there
- add_to_path(python_path, venvLibPath.canonicalFilePath());
- add_to_path(python_path, venvDynLibPath.canonicalFilePath());
- add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
- add_to_path(pythonHome, venvPath.canonicalFilePath());
-#endif
-
- qputenv("PATH", path_env.toUtf8().data());
-
- if (python_path.length() > 0)
- {
- // Split the path setting into individual entries
- path_list = python_path.split(";", QString::SkipEmptyParts);
- python_path = QString();
-
- // Add new additional path elements
- for (i = path_list.size() - 1; i >= 0 ; --i)
- {
- python_path.append(path_list.at(i));
- if (i > 0)
- {
-#if defined(Q_OS_WIN)
- python_path.append(";");
-#else
- python_path.append(":");
-#endif
- }
- }
- qputenv("PYTHONPATH", python_path.toUtf8().data());
- }
-
- qDebug() << "Python path: " << python_path
- << "\nPython Home: " << pythonHome;
- if (!pythonHome.isEmpty())
- {
- pythonHome_utf8 = pythonHome.toUtf8();
-#ifdef PYTHON2
- Py_SetPythonHome(pythonHome_utf8.data());
-#else
- char *python_home = pythonHome_utf8.data();
- const size_t cSize = strlen(python_home) + 1;
- m_wcPythonHome = new wchar_t[cSize];
- mbstowcs (m_wcPythonHome, python_home, cSize);
-
- Py_SetPythonHome(m_wcPythonHome);
-#endif
- }
+ Py_SetProgramName(m_pyAppName);
Py_Initialize();
+ /*
+ * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
+ * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
+ * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
+ * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
+ */
+ PySys_SetArgvEx(1, &m_pyAppName, 0);
+
// Get the current path
PyObject* sysPath = PySys_GetObject((char*)"path");
-
- // Add new additional path elements
- for (i = path_list.size() - 1; i >= 0 ; --i)
- {
+ PyObject* sysPathRepr = PyObject_Repr(sysPath);
#ifdef PYTHON2
- PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data()));
+ qDebug() << "Python path: " << QString::fromLocal8Bit(PyString_AsString(sysPathRepr));
#else
-#if PY_MINOR_VERSION > 2
- PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data()));
-#else
- PyList_Append(sysPath, PyBytes_FromString(path_list.at(i).toUtf8().data()));
+ PyObject* sysPathBytes = PyUnicode_AsEncodedString(sysPathRepr, "utf8", "replace");
+ qDebug() << "Python path: " << QString::fromUtf8(PyBytes_AsString(sysPathBytes));
+ Py_DECREF(sysPathBytes);
#endif
-#endif
- }
+ Py_DECREF(sysPathRepr);
}
Server::~Server()
{
- if (m_wcAppName)
- delete m_wcAppName;
-
- if (m_wcPythonHome)
- delete m_wcPythonHome;
-
// Shutdown Python
Py_Finalize();
+
+ if (m_pyAppName)
+ delete [] m_pyAppName;
}
bool Server::Init()
@@ -264,52 +148,29 @@ bool Server::Init()
void Server::run()
{
// Open the application code and run it.
- FILE *cp = fopen(m_appfile.toUtf8().data(), "r");
+ QByteArray m_appfile_utf8 = m_appfile.toUtf8();
+ FILE *cp = fopen(m_appfile_utf8.data(), "r");
if (!cp)
{
setError(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
return;
}
// Set the port number
PyRun_SimpleString(QString("PGADMIN_PORT = %1").arg(m_port).toLatin1());
PyRun_SimpleString(QString("PGADMIN_KEY = '%1'").arg(m_key).toLatin1());
// Run the app!
- QByteArray m_appfile_utf8 = m_appfile.toUtf8();
#ifdef PYTHON2
- /*
- * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
- * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
- * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
- * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
- */
- char* n_argv[] = { m_appfile_utf8.data() };
- PySys_SetArgv(1, n_argv);
-
PyObject* PyFileObject = PyFile_FromString(m_appfile_utf8.data(), (char *)"r");
int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile_utf8.data());
- if (ret != 0)
- setError(tr("Failed to launch the application server, server thread exiting."));
#else
- /*
- * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
- * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
- * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
- * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
- */
- char *appName = m_appfile_utf8.data();
- const size_t cSize = strlen(appName)+1;
- wchar_t* wcAppName = new wchar_t[cSize];
- mbstowcs (wcAppName, appName, cSize);
- wchar_t* n_argv[] = { wcAppName };
- PySys_SetArgv(1, n_argv);
-
int fd = fileno(cp);
PyObject* PyFileObject = PyFile_FromFd(fd, m_appfile_utf8.data(), (char *)"r", -1, NULL, NULL,NULL,1);
- if (PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile_utf8.data()) != 0)
- setError(tr("Failed to launch the application server, server thread exiting."));
+ int ret = PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile_utf8.data());
#endif
+ if (ret != 0)
+ setError(tr("Failed to launch the application server, server thread exiting."));
fclose(cp);
}
diff --git a/runtime/Server.h b/runtime/Server.h
index 488f9fcedc6d7dc7..207e2f157db7d7a5 100644
--- a/runtime/Server.h
+++ b/runtime/Server.h
@@ -40,12 +40,11 @@ private:
quint16 m_port;
QString m_key;
- // Application name in UTF-8 for Python
- wchar_t *m_wcAppName;
- QByteArray PGA_APP_NAME_UTF8;
- // PythonHome for Python
- wchar_t *m_wcPythonHome;
- QByteArray pythonHome_utf8;
+#ifdef PYTHON2
+ char *m_pyAppName;
+#else
+ wchar_t *m_pyAppName;
+#endif
};
#endif // SERVER_H
diff --git a/runtime/pgAdmin4.cpp b/runtime/pgAdmin4.cpp
index a2dbe7451557befc..ebcb4ff4d4899aaf 100644
--- a/runtime/pgAdmin4.cpp
+++ b/runtime/pgAdmin4.cpp
@@ -228,69 +228,65 @@ int main(int argc, char * argv[])
// Fire up the webserver
Server *server;
- bool done = false;
+ server = new Server(port, key);
- while (done != true)
+ if (!server->Init())
{
- server = new Server(port, key);
+ splash->finish(NULL);
- if (!server->Init())
+ qDebug() << server->getError();
+
+ QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
+ QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
+
+ exit(1);
+ }
+
+ server->start();
+
+ // This is a hack. Wait a second and then check to see if the server thread
+ // is still running. If it's not, we probably had a startup error
+ delay(1000);
+
+ // Any errors?
+ if (server->isFinished() || server->getError().length() > 0)
+ {
+ splash->finish(NULL);
+
+ qDebug() << server->getError();
+
+ QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
+ QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
+
+ // Allow the user to tweak the Python Path if needed
+ QSettings settings;
+ bool ok;
+
+ ConfigWindow *dlg = new ConfigWindow();
+ dlg->setWindowTitle(QWidget::tr("Configuration"));
+ dlg->setPythonExecutable(settings.value("PythonExecutable").toString());
+ dlg->setApplicationPath(settings.value("ApplicationPath").toString());
+ dlg->setModal(true);
+ ok = dlg->exec();
+
+ QString pythonexecutable = dlg->getPythonExecutable();
+ QString applicationpath = dlg->getApplicationPath();
+
+ if (ok)
{
- splash->finish(NULL);
+ settings.setValue("PythonExecutable", pythonexecutable);
+ settings.setValue("ApplicationPath", applicationpath);
+ settings.sync();
- qDebug() << server->getError();
+ QString msg = QString(QWidget::tr("Please restart the application to allow the changes to take effect."));
+ QMessageBox::information(NULL, QString(QWidget::tr("Restart")), msg);
- QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
- QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
-
- exit(1);
- }
-
- server->start();
-
- // This is a hack. Wait a second and then check to see if the server thread
- // is still running. If it's not, we probably had a startup error
- delay(1000);
-
- // Any errors?
- if (server->isFinished() || server->getError().length() > 0)
- {
- splash->finish(NULL);
-
- qDebug() << server->getError();
-
- QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
- QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
-
- // Allow the user to tweak the Python Path if needed
- QSettings settings;
- bool ok;
-
- ConfigWindow *dlg = new ConfigWindow();
- dlg->setWindowTitle(QWidget::tr("Configuration"));
- dlg->setPythonPath(settings.value("PythonPath").toString());
- dlg->setApplicationPath(settings.value("ApplicationPath").toString());
- dlg->setModal(true);
- ok = dlg->exec();
-
- QString pythonpath = dlg->getPythonPath();
- QString applicationpath = dlg->getApplicationPath();
-
- if (ok)
- {
- settings.setValue("PythonPath", pythonpath);
- settings.setValue("ApplicationPath", applicationpath);
- settings.sync();
- }
- else
- {
- exit(1);
- }
-
- delete server;
+ exit(0);
}
else
- done = true;
+ {
+ exit(1);
+ }
}
--
2.13.0