diff --git a/src/plugins/coreplugin/ssh/sftpchannel.h b/src/plugins/coreplugin/ssh/sftpchannel.h
index cbdc072283e91e106ab6bcdf1de4757dbf24ef1f..06ad52de5eca7aabb025eff210a6273607bba2d2 100644
--- a/src/plugins/coreplugin/ssh/sftpchannel.h
+++ b/src/plugins/coreplugin/ssh/sftpchannel.h
@@ -108,6 +108,7 @@ signals:
     /*
      * This signal is only emitted by the "List Directory" operation,
      * one file at a time.
+     // TODO: Also emit for each file copied by uploadDir().
      */
     void dataAvailable(SftpJobId job, const QString &data);
 
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
index 6ab65f99a6a2f8a7f725e2c6634bef93de4949c1..4998112f300fc54787f9f9a2ec443d01c255035e 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
@@ -190,6 +190,12 @@ MaemoDeployStep *MaemoRunConfiguration::deployStep() const
     return step;
 }
 
+QString MaemoRunConfiguration::localHostAddressFromDevice() const
+{
+    // TODO: From user
+    return QLatin1String("192.168.2.14");
+}
+
 QString MaemoRunConfiguration::maddeRoot() const
 {
     if (const MaemoToolChain *tc = toolchain())
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
index 9f2f9b2536d208a8348bcfd8519b32923d3014de..e7fa2c53e8d55c1370a7f0441df647548671e2db 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
@@ -75,7 +75,10 @@ public:
     Qt4BuildConfiguration *activeQt4BuildConfiguration() const;
 
     MaemoDeployStep *deployStep() const;
+    MaemoRemoteMountsModel *remoteMounts() const { return m_remoteMounts; }
+    QString localHostAddressFromDevice() const;
 
+    const MaemoToolChain *toolchain() const;
     QString maddeRoot() const;
     QString localExecutableFilePath() const;
     QString remoteExecutableFilePath() const;
@@ -106,7 +109,6 @@ private slots:
 
 private:
     void init();
-    const MaemoToolChain *toolchain() const;
 
     QString m_proFilePath;
     mutable QString m_gdbPath;
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.cpp
index 81b7ded2466aacc8a3879835fac9a3cd5a930a2d..b79898b9896642545995a3737375ec04909498f1 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.cpp
@@ -35,12 +35,17 @@
 #include "maemosshrunner.h"
 
 #include "maemodeviceconfigurations.h"
+#include "maemoglobal.h"
+#include "maemoremotemountsmodel.h"
 #include "maemorunconfiguration.h"
+#include "maemotoolchain.h"
 
+#include <coreplugin/ssh/sftpchannel.h>
 #include <coreplugin/ssh/sshconnection.h>
 #include <coreplugin/ssh/sshremoteprocess.h>
 
 #include <QtCore/QFileInfo>
+#include <QtCore/QProcess>
 
 using namespace Core;
 
@@ -50,9 +55,12 @@ namespace Internal {
 MaemoSshRunner::MaemoSshRunner(QObject *parent,
     MaemoRunConfiguration *runConfig)
     : QObject(parent), m_runConfig(runConfig),
-      m_devConfig(runConfig->deviceConfig())
+      m_devConfig(runConfig->deviceConfig()),
+      m_uploadJobId(SftpInvalidJob)
 {
-    m_procsToKill << QFileInfo(m_runConfig->localExecutableFilePath()).fileName();
+    m_procsToKill
+        << QFileInfo(m_runConfig->localExecutableFilePath()).fileName()
+        << QLatin1String("utfs-client");
 }
 
 MaemoSshRunner::~MaemoSshRunner() {}
@@ -93,10 +101,18 @@ void MaemoSshRunner::stop()
     disconnect(m_connection.data(), 0, this, 0);
     if (m_initialCleaner)
         disconnect(m_initialCleaner.data(), 0, this, 0);
+    if (m_utfsClientUploader) {
+        disconnect(m_utfsClientUploader.data(), 0, this, 0);
+        m_utfsClientUploader->closeChannel();
+    }
+    if (m_mountProcess) {
+        disconnect(m_mountProcess.data(), 0, this, 0);
+        m_mountProcess->closeChannel();
+    }
     if (m_runner) {
         disconnect(m_runner.data(), 0, this, 0);
         m_runner->closeChannel();
-        killRemoteProcs(false);
+        cleanup(false);
     }
 }
 
@@ -105,7 +121,7 @@ void MaemoSshRunner::handleConnected()
     if (m_stop)
         return;
 
-    killRemoteProcs(true);
+    cleanup(true);
 }
 
 void MaemoSshRunner::handleConnectionFailure()
@@ -114,7 +130,7 @@ void MaemoSshRunner::handleConnectionFailure()
         .arg(m_connection->errorString()));
 }
 
-void MaemoSshRunner::killRemoteProcs(bool initialCleanup)
+void MaemoSshRunner::cleanup(bool initialCleanup)
 {
     QString niceKill;
     QString brutalKill;
@@ -123,6 +139,15 @@ void MaemoSshRunner::killRemoteProcs(bool initialCleanup)
         brutalKill += QString::fromLocal8Bit("pkill -x -9 %1;").arg(proc);
     }
     QString remoteCall = niceKill + QLatin1String("sleep 1; ") + brutalKill;
+
+    const MaemoRemoteMountsModel * const remoteMounts
+        = m_runConfig->remoteMounts();
+    for (int i = 0; i < remoteMounts->mountSpecificationCount(); ++i) {
+        remoteCall += QString::fromLocal8Bit("%1 umount %2;")
+            .arg(MaemoGlobal::remoteSudo(),
+                 remoteMounts->mountSpecificationAt(i).remoteMountPoint);
+    }
+
     remoteCall.remove(remoteCall.count() - 1, 1); // Get rid of trailing semicolon.
     SshRemoteProcess::Ptr proc
         = m_connection->createRemoteProcess(remoteCall.toUtf8());
@@ -143,14 +168,142 @@ void MaemoSshRunner::handleInitialCleanupFinished(int exitStatus)
     if (m_stop)
         return;
 
+    foreach (QProcess *utfsServer, m_utfsServers) {
+        utfsServer->terminate();
+        utfsServer->waitForFinished(1000);
+        utfsServer->kill();
+    }
+    qDeleteAll(m_utfsServers);
     if (exitStatus != SshRemoteProcess::ExitedNormally) {
         emit error(tr("Initial cleanup failed: %1")
             .arg(m_initialCleaner->errorString()));
+    } else if (m_runConfig->remoteMounts()->mountSpecificationCount() != 0) {
+        deployUtfsClient();
     } else {
         emit readyForExecution();
     }
 }
 
+void MaemoSshRunner::deployUtfsClient()
+{
+    m_utfsClientUploader = m_connection->createSftpChannel();
+    connect(m_utfsClientUploader.data(), SIGNAL(initialized()), this,
+        SLOT(handleUploaderInitialized()));
+    connect(m_utfsClientUploader.data(), SIGNAL(initializationFailed(QString)),
+        this, SLOT(handleUploaderInitializationFailed(QString)));
+    m_utfsClientUploader->initialize();
+}
+
+void MaemoSshRunner::handleUploaderInitializationFailed(const QString &reason)
+{
+    if (m_stop)
+        return;
+
+    emit error(tr("Failed to establish SFTP connection: %1").arg(reason));
+}
+
+void MaemoSshRunner::handleUploaderInitialized()
+{
+    if (m_stop)
+        return;
+
+    connect(m_utfsClientUploader.data(),
+        SIGNAL(finished(Core::SftpJobId, QString)), this,
+        SLOT(handleUploadFinished(Core::SftpJobId,QString)));
+    const MaemoToolChain * const toolChain
+        = dynamic_cast<const MaemoToolChain *>(m_runConfig->toolchain());
+    Q_ASSERT_X(toolChain, Q_FUNC_INFO,
+        "Impossible: Maemo run configuration has no Maemo Toolchain.");
+    const QString localFile
+        = toolChain->maddeRoot() + QLatin1String("/madlib/utfs-client");
+    m_uploadJobId
+        = m_utfsClientUploader->uploadFile(localFile, utfsClientOnDevice(),
+              SftpOverwriteExisting);
+    if (m_uploadJobId == SftpInvalidJob)
+        emit error(tr("Could not upload UTFS client (%1).").arg(localFile));
+}
+
+void MaemoSshRunner::handleUploadFinished(Core::SftpJobId jobId,
+    const QString &errorMsg)
+{
+    if (m_stop)
+        return;
+
+    if (jobId != m_uploadJobId) {
+        qWarning("Warning: unknown upload job %d finished.", jobId);
+        return;
+    }
+
+    m_uploadJobId = SftpInvalidJob;
+    if (!errorMsg.isEmpty()) {
+        emit error(tr("Could not upload UTFS client: %1").arg(errorMsg));
+        return;
+    }
+
+    mount();
+}
+
+void MaemoSshRunner::mount()
+{
+    const MaemoRemoteMountsModel * const remoteMounts
+        = m_runConfig->remoteMounts();
+    QString remoteCall(QLatin1String(":"));
+    for (int i = 0; i < remoteMounts->mountSpecificationCount(); ++i) {
+        const MaemoRemoteMountsModel::MountSpecification &mountSpec
+            = remoteMounts->mountSpecificationAt(i);
+
+        QProcess * const utfsServerProc = new QProcess(this);
+        const QString port = QString::number(mountSpec.port);
+        const QString localSecretOpt = QLatin1String("-l");
+        const QString remoteSecretOpt = QLatin1String("-r");
+        const QStringList utfsServerArgs = QStringList() << localSecretOpt
+            << port << remoteSecretOpt << port << QLatin1String("-b") << port;
+        utfsServerProc->start(utfsServer(), utfsServerArgs);
+        if (!utfsServerProc->waitForStarted()) {
+            emit error(tr("Could not start UTFS server: %1")
+                .arg(utfsServerProc->errorString()));
+            return;
+        }
+        m_utfsServers << utfsServerProc;
+        const QString mkdir = QString::fromLocal8Bit("%1 mkdir -p %2")
+            .arg(MaemoGlobal::remoteSudo(), mountSpec.remoteMountPoint);
+        const QString utfsClient
+            = QString::fromLocal8Bit("%1 -l %2 -r %2 -c %3:%2 %4")
+                  .arg(utfsClientOnDevice()).arg(port)
+                  .arg(m_runConfig->localHostAddressFromDevice())
+                  .arg(mountSpec.remoteMountPoint);
+        const QLatin1String andOp(" && ");
+        remoteCall += andOp + mkdir + andOp + utfsClient;
+    }
+
+    m_mountProcess = m_connection->createRemoteProcess(remoteCall.toUtf8());
+    connect(m_mountProcess.data(), SIGNAL(started()), this,
+        SLOT(handleMountProcessStarted()));
+    connect(m_mountProcess.data(), SIGNAL(closed(int)), this,
+        SLOT(handleRemoteProcessFinished(int)));
+    m_mountProcess->start();
+}
+
+void MaemoSshRunner::handleMountProcessStarted()
+{
+    // TODO: Do we get "finished" from utfs-client when it goes into background?
+    // If so, remnove this slot; readyForExecution() should be emitted from
+    // handleRemoteProcessFinished() in that case.
+    if (!m_stop)
+        emit readyForExecution();
+}
+
+void MaemoSshRunner::handleMountProcessFinished(int exitStatus)
+{
+    if (m_stop)
+        return;
+
+    if (exitStatus != SshRemoteProcess::ExitedNormally) {
+        emit error(tr("Failure running UTFS client: %1")
+            .arg(m_mountProcess->errorString()));
+    }
+}
+
 void MaemoSshRunner::startExecution(const QByteArray &remoteCall)
 {
     if (m_runConfig->remoteExecutableFilePath().isEmpty()) {
@@ -187,6 +340,20 @@ void MaemoSshRunner::handleRemoteProcessFinished(int exitStatus)
     }
 }
 
+QString MaemoSshRunner::utfsClientOnDevice() const
+{
+    return MaemoGlobal::homeDirOnDevice(QLatin1String("developer"))
+        + QLatin1String("/utfs-client");
+}
+
+QString MaemoSshRunner::utfsServer() const
+{
+    const MaemoToolChain * const toolChain
+        = dynamic_cast<const MaemoToolChain *>(m_runConfig->toolchain());
+    Q_ASSERT_X(toolChain, Q_FUNC_INFO,
+        "Impossible: Maemo run configuration has no Maemo Toolchain.");
+    return toolChain->maddeRoot() + QLatin1String("/madlib/utfs-server");
+}
 
 } // namespace Internal
 } // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.h b/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.h
index 02f80f36ec21227f37bd1de5e13d715fb5d4ea2b..cbfcb36c164dbe33c94e6c1418bf7bf2acdcaa49 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemosshrunner.h
@@ -44,6 +44,8 @@
 
 #include <QtCore/QStringList>
 
+QT_FORWARD_DECLARE_CLASS(QProcess)
+
 namespace Core {
     class SftpChannel;
     class SshConnection;
@@ -84,9 +86,18 @@ private slots:
     void handleConnectionFailure();
     void handleInitialCleanupFinished(int exitStatus);
     void handleRemoteProcessFinished(int exitStatus);
+    void handleUploaderInitialized();
+    void handleUploaderInitializationFailed(const QString &reason);
+    void handleUploadFinished(Core::SftpJobId jobId, const QString &error);
+    void handleMountProcessStarted();
+    void handleMountProcessFinished(int exitStatus);
 
 private:
-    void killRemoteProcs(bool initialCleanup);
+    void cleanup(bool initialCleanup);
+    void deployUtfsClient();
+    void mount();
+    QString utfsClientOnDevice() const;
+    QString utfsServer() const;
 
     MaemoRunConfiguration * const m_runConfig; // TODO this pointer can be invalid
     const MaemoDeviceConfig m_devConfig;
@@ -94,7 +105,12 @@ private:
     QSharedPointer<Core::SshConnection> m_connection;
     QSharedPointer<Core::SshRemoteProcess> m_runner;
     QSharedPointer<Core::SshRemoteProcess> m_initialCleaner;
+    QSharedPointer<Core::SshRemoteProcess> m_mountProcess;
+    QSharedPointer<Core::SftpChannel> m_utfsClientUploader;
     QStringList m_procsToKill;
+    QList<QProcess *> m_utfsServers;
+
+    Core::SftpJobId m_uploadJobId;
     bool m_stop;
 };