1/*
2 This file is part of the KDE libraries
3 Copyright (c) 1999 Waldo Bastian <bastian@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19
20#define QT_NO_CAST_FROM_ASCII
21
22#include "klauncher.h"
23#include "klauncher_cmds.h"
24#include "klauncher_adaptor.h"
25
26#include <config.h>
27
28#include <stdio.h>
29#include <unistd.h>
30#include <stdlib.h>
31#include <errno.h>
32#include <signal.h>
33#include <sys/time.h>
34
35#ifdef Q_WS_X11
36#include <kstartupinfo.h>
37#include <X11/Xlib.h>
38#endif
39
40#include <QtCore/QFile>
41#include <qplatformdefs.h>
42
43#include <kconfig.h>
44#include <kdebug.h>
45#include <kde_file.h>
46#include <klibrary.h>
47#include <klocale.h>
48#include <kprotocolmanager.h>
49#include <kprotocolinfo.h>
50#include <krun.h>
51#include <kstandarddirs.h>
52#include <ktemporaryfile.h>
53#include <kdesktopfile.h>
54#include <kurl.h>
55
56#include <kio/global.h>
57#include <kio/connection.h>
58#include <kio/slaveinterface.h>
59
60// Dispose slaves after being idle for SLAVE_MAX_IDLE seconds
61#define SLAVE_MAX_IDLE 30
62
63// #define KLAUNCHER_VERBOSE_OUTPUT
64
65static const char* const s_DBusStartupTypeToString[] =
66 { "DBusNone", "DBusUnique", "DBusMulti", "DBusWait", "ERROR" };
67
68using namespace KIO;
69
70IdleSlave::IdleSlave(QObject *parent)
71 : QObject(parent)
72{
73 QObject::connect(&mConn, SIGNAL(readyRead()), this, SLOT(gotInput()));
74 // Send it a SLAVE_STATUS command.
75 mConn.send( CMD_SLAVE_STATUS );
76 mPid = 0;
77 mBirthDate = time(0);
78 mOnHold = false;
79}
80
81template<int T> struct PIDType { typedef pid_t PID_t; } ;
82template<> struct PIDType<2> { typedef qint16 PID_t; } ;
83template<> struct PIDType<4> { typedef qint32 PID_t; } ;
84
85void
86IdleSlave::gotInput()
87{
88 int cmd;
89 QByteArray data;
90 if (mConn.read( &cmd, data) == -1)
91 {
92 // Communication problem with slave.
93 //kError(7016) << "SlavePool: No communication with slave." << endl;
94 deleteLater();
95 }
96 else if (cmd == MSG_SLAVE_ACK)
97 {
98 deleteLater();
99 }
100 else if (cmd != MSG_SLAVE_STATUS)
101 {
102 kError(7016) << "SlavePool: Unexpected data from slave." << endl;
103 deleteLater();
104 }
105 else
106 {
107 QDataStream stream( data );
108 PIDType<sizeof(pid_t)>::PID_t stream_pid;
109 pid_t pid;
110 QByteArray protocol;
111 QString host;
112 qint8 b;
113 stream >> stream_pid >> protocol >> host >> b;
114 pid = stream_pid;
115// Overload with (bool) onHold, (KUrl) url.
116 if (!stream.atEnd())
117 {
118 KUrl url;
119 stream >> url;
120 mOnHold = true;
121 mUrl = url;
122 }
123
124 mPid = pid;
125 mConnected = (b != 0);
126 mProtocol = QString::fromLatin1(protocol);
127 mHost = host;
128 emit statusUpdate(this);
129 }
130}
131
132void
133IdleSlave::connect(const QString &app_socket)
134{
135 QByteArray data;
136 QDataStream stream( &data, QIODevice::WriteOnly);
137 stream << app_socket;
138 mConn.send( CMD_SLAVE_CONNECT, data );
139 // Timeout!
140}
141
142void
143IdleSlave::reparseConfiguration()
144{
145 mConn.send( CMD_REPARSECONFIGURATION );
146}
147
148bool
149IdleSlave::match(const QString &protocol, const QString &host, bool needConnected) const
150{
151 if (mOnHold || protocol != mProtocol) {
152 return false;
153 }
154 if (host.isEmpty()) {
155 return true;
156 }
157 return (host == mHost) && (!needConnected || mConnected);
158}
159
160bool
161IdleSlave::onHold(const KUrl &url) const
162{
163 if (!mOnHold) return false;
164 return (url == mUrl);
165}
166
167int
168IdleSlave::age(time_t now) const
169{
170 return (int) difftime(now, mBirthDate);
171}
172
173static KLauncher* g_klauncher_self;
174
175
176// From qcore_unix_p.h. We could also port to QLocalSocket :)
177#define K_EINTR_LOOP(var, cmd) \
178 do { \
179 var = cmd; \
180 } while (var == -1 && errno == EINTR)
181
182ssize_t kde_safe_write(int fd, const void *buf, size_t count)
183{
184 ssize_t ret = 0;
185 K_EINTR_LOOP(ret, QT_WRITE(fd, buf, count));
186 if (ret < 0)
187 qWarning() << "write failed:" << strerror(errno);
188 return ret;
189}
190
191#ifndef USE_KPROCESS_FOR_KIOSLAVES
192KLauncher::KLauncher(int _kdeinitSocket)
193 : QObject(0),
194 kdeinitSocket(_kdeinitSocket)
195#else
196KLauncher::KLauncher()
197 : QObject(0)
198#endif
199{
200#ifdef Q_WS_X11
201 mCached_dpy = NULL;
202#endif
203 Q_ASSERT( g_klauncher_self == NULL );
204 g_klauncher_self = this;
205
206 mAutoTimer.setSingleShot(true);
207 new KLauncherAdaptor(this);
208 QDBusConnection::sessionBus().registerObject(QLatin1String("/KLauncher"), this); // same as ktoolinvocation.cpp
209
210 connect(&mAutoTimer, SIGNAL(timeout()), this, SLOT(slotAutoStart()));
211 connect(QDBusConnection::sessionBus().interface(),
212 SIGNAL(serviceOwnerChanged(QString,QString,QString)),
213 SLOT(slotNameOwnerChanged(QString,QString,QString)));
214
215 mConnectionServer.listenForRemote();
216 connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave()));
217 if (!mConnectionServer.isListening())
218 {
219 // Severe error!
220 qDebug("KLauncher: Fatal error, can't create tempfile!");
221 ::_exit(1);
222 }
223
224 connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout()));
225
226#ifndef USE_KPROCESS_FOR_KIOSLAVES
227 kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read);
228 connect(kdeinitNotifier, SIGNAL(activated(int)),
229 this, SLOT(slotKDEInitData(int)));
230 kdeinitNotifier->setEnabled( true );
231#endif
232 lastRequest = 0;
233 bProcessingQueue = false;
234
235 mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT"));
236 if (!mSlaveDebug.isEmpty())
237 {
238 qWarning("Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug));
239 }
240 mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND"));
241 if (!mSlaveValgrind.isEmpty())
242 {
243 mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN"));
244 qWarning("Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind));
245 }
246#ifdef USE_KPROCESS_FOR_KIOSLAVES
247 kDebug(7016) << "LAUNCHER_OK";
248#else
249 klauncher_header request_header;
250 request_header.cmd = LAUNCHER_OK;
251 request_header.arg_length = 0;
252 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
253#endif
254}
255
256KLauncher::~KLauncher()
257{
258 close();
259 g_klauncher_self = NULL;
260}
261
262void KLauncher::close()
263{
264#ifdef Q_WS_X11
265 if( mCached_dpy != NULL )
266 {
267 XCloseDisplay( mCached_dpy );
268 mCached_dpy = NULL;
269 }
270#endif
271}
272
273void
274KLauncher::destruct()
275{
276 if (g_klauncher_self)
277 g_klauncher_self->close();
278 // We don't delete the app here, that's intentional.
279 ::_exit(255);
280}
281
282void KLauncher::setLaunchEnv(const QString &name, const QString &value)
283{
284#ifndef USE_KPROCESS_FOR_KIOSLAVES
285 klauncher_header request_header;
286 QByteArray requestData;
287 requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0');
288 request_header.cmd = LAUNCHER_SETENV;
289 request_header.arg_length = requestData.size();
290 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
291 kde_safe_write(kdeinitSocket, requestData.data(), request_header.arg_length);
292#else
293 Q_UNUSED(name);
294 Q_UNUSED(value);
295#endif
296}
297
298#ifndef USE_KPROCESS_FOR_KIOSLAVES
299/*
300 * Read 'len' bytes from 'sock' into buffer.
301 * returns -1 on failure, 0 on no data.
302 */
303static int
304read_socket(int sock, char *buffer, int len)
305{
306 ssize_t result;
307 int bytes_left = len;
308 while (bytes_left > 0) {
309 // in case we get a request to start an application and data arrive
310 // to kdeinitSocket at the same time, requestStart() will already
311 // call slotKDEInitData(), so we must check there's still something
312 // to read, otherwise this would block
313
314 // Same thing if kdeinit dies without warning.
315
316 fd_set in;
317 timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us
318 FD_ZERO ( &in );
319 FD_SET( sock, &in );
320 select( sock + 1, &in, 0, 0, &tm );
321 if( !FD_ISSET( sock, &in )) {
322 kDebug(7016) << "read_socket" << sock << "nothing to read, kdeinit4 must be dead";
323 return -1;
324 }
325
326 result = read(sock, buffer, bytes_left);
327 if (result > 0)
328 {
329 buffer += result;
330 bytes_left -= result;
331 }
332 else if (result == 0)
333 return -1;
334 else if ((result == -1) && (errno != EINTR))
335 return -1;
336 }
337 return 0;
338}
339#endif
340
341void
342KLauncher::slotKDEInitData(int)
343{
344#ifndef USE_KPROCESS_FOR_KIOSLAVES
345 klauncher_header request_header;
346 QByteArray requestData;
347
348 int result = read_socket(kdeinitSocket, (char *) &request_header,
349 sizeof( request_header));
350 if (result == -1)
351 {
352 kDebug(7016) << "Exiting on read_socket errno:" << errno;
353 KDE_signal( SIGHUP, SIG_IGN);
354 KDE_signal( SIGTERM, SIG_IGN);
355 destruct(); // Exit!
356 }
357 requestData.resize(request_header.arg_length);
358 result = read_socket(kdeinitSocket, (char *) requestData.data(),
359 request_header.arg_length);
360
361 processRequestReturn(request_header.cmd,requestData);
362#endif
363}
364
365void KLauncher::processRequestReturn(int status, const QByteArray &requestData)
366{
367 if (status == LAUNCHER_CHILD_DIED)
368 {
369 long *request_data;
370 request_data = (long *) requestData.data();
371 processDied(request_data[0], request_data[1]);
372 return;
373 }
374 if (lastRequest && (status == LAUNCHER_OK))
375 {
376 long *request_data;
377 request_data = (long *) requestData.data();
378 lastRequest->pid = (pid_t) (*request_data);
379 kDebug(7016).nospace() << lastRequest->name << " (pid " << lastRequest->pid <<
380 ") up and running.";
381 switch(lastRequest->dbus_startup_type)
382 {
383 case KService::DBusNone:
384 lastRequest->status = KLaunchRequest::Running;
385 break;
386 case KService::DBusUnique:
387 case KService::DBusWait:
388 case KService::DBusMulti:
389 lastRequest->status = KLaunchRequest::Launching;
390 break;
391 }
392 lastRequest = 0;
393 return;
394 }
395 if (lastRequest && (status == LAUNCHER_ERROR))
396 {
397 lastRequest->status = KLaunchRequest::Error;
398 kDebug(7016) << lastRequest->name << " failed." << endl;
399 if (!requestData.isEmpty())
400 lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data());
401 lastRequest = 0;
402 return;
403 }
404
405 kWarning(7016)<< "Unexpected request return" << (unsigned int) status;
406}
407
408void
409KLauncher::processDied(pid_t pid, long exitStatus)
410{
411#ifdef KLAUNCHER_VERBOSE_OUTPUT
412 kDebug(7016) << pid << "exitStatus=" << exitStatus;
413#else
414 Q_UNUSED(exitStatus);
415 // We should probably check the exitStatus for the uniqueapp case?
416#endif
417 foreach (KLaunchRequest *request, requestList)
418 {
419#ifdef KLAUNCHER_VERBOSE_OUTPUT
420 kDebug(7016) << " had pending request" << request->pid;
421#endif
422 if (request->pid == pid)
423 {
424 if (request->dbus_startup_type == KService::DBusWait)
425 request->status = KLaunchRequest::Done;
426 else if ((request->dbus_startup_type == KService::DBusUnique)
427 && QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) {
428 request->status = KLaunchRequest::Running;
429#ifdef KLAUNCHER_VERBOSE_OUTPUT
430 kDebug(7016) << pid << "running as a unique app";
431#endif
432 } else {
433 request->status = KLaunchRequest::Error;
434#ifdef KLAUNCHER_VERBOSE_OUTPUT
435 kDebug(7016) << pid << "died, requestDone. status=" << request->status;
436#endif
437 }
438 requestDone(request);
439 return;
440 }
441 }
442#ifdef KLAUNCHER_VERBOSE_OUTPUT
443 kDebug(7016) << "found no pending requests for PID" << pid;
444#endif
445}
446
447static bool matchesPendingRequest(const QString& appId, const QString& pendingAppId)
448{
449 // appId just registered, e.g. org.koffice.kword-12345
450 // Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for.
451
452 const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present.
453
454 //kDebug() << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId;
455
456 if (pendingAppId.startsWith(QLatin1String("*."))) {
457 const QString pendingName = pendingAppId.mid(2);
458 const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.'))+1);
459 //kDebug() << "appName=" << appName;
460 return appName == pendingName;
461 }
462
463 return newAppId == pendingAppId;
464}
465
466void
467KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner,
468 const QString &newOwner)
469{
470 Q_UNUSED(oldOwner);
471 if (appId.isEmpty() || newOwner.isEmpty())
472 return;
473
474#ifdef KLAUNCHER_VERBOSE_OUTPUT
475 kDebug(7016) << "new app" << appId;
476#endif
477 foreach (KLaunchRequest *request, requestList)
478 {
479 if (request->status != KLaunchRequest::Launching)
480 continue;
481
482#ifdef KLAUNCHER_VERBOSE_OUTPUT
483 kDebug(7016) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name;
484#endif
485 // For unique services check the requested service name first
486 if (request->dbus_startup_type == KService::DBusUnique) {
487 if ((appId == request->dbus_name) || // just started
488 QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running
489 request->status = KLaunchRequest::Running;
490#ifdef KLAUNCHER_VERBOSE_OUTPUT
491 kDebug(7016) << "OK, unique app" << request->dbus_name << "is running";
492#endif
493 requestDone(request);
494 continue;
495 } else {
496#ifdef KLAUNCHER_VERBOSE_OUTPUT
497 kDebug(7016) << "unique app" << request->dbus_name << "not running yet";
498#endif
499 }
500 }
501
502 const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name;
503#ifdef KLAUNCHER_VERBOSE_OUTPUT
504 //kDebug(7016) << "using" << rAppId << "for matching";
505#endif
506 if (rAppId.isEmpty())
507 continue;
508
509 if (matchesPendingRequest(appId, rAppId)) {
510#ifdef KLAUNCHER_VERBOSE_OUTPUT
511 kDebug(7016) << "ok, request done";
512#endif
513 request->dbus_name = appId;
514 request->status = KLaunchRequest::Running;
515 requestDone(request);
516 continue;
517 }
518 }
519}
520
521void
522KLauncher::autoStart(int phase)
523{
524 if( mAutoStart.phase() >= phase )
525 return;
526 mAutoStart.setPhase(phase);
527 if (phase == 0)
528 mAutoStart.loadAutoStartList();
529 mAutoTimer.start(0);
530}
531
532void
533KLauncher::slotAutoStart()
534{
535 KService::Ptr s;
536 do
537 {
538 QString service = mAutoStart.startService();
539 if (service.isEmpty())
540 {
541 // Done
542 if( !mAutoStart.phaseDone())
543 {
544 mAutoStart.setPhaseDone();
545 switch( mAutoStart.phase())
546 {
547 case 0:
548 emit autoStart0Done();
549 break;
550 case 1:
551 emit autoStart1Done();
552 break;
553 case 2:
554 emit autoStart2Done();
555 break;
556 }
557 }
558 return;
559 }
560 s = new KService(service);
561 }
562 while (!start_service(s, QStringList(), QStringList(), "0", false, true, QDBusMessage()));
563 // Loop till we find a service that we can start.
564}
565
566void
567KLauncher::requestDone(KLaunchRequest *request)
568{
569 if ((request->status == KLaunchRequest::Running) ||
570 (request->status == KLaunchRequest::Done))
571 {
572 requestResult.result = 0;
573 requestResult.dbusName = request->dbus_name;
574 requestResult.error = QString::fromLatin1(""); // not null, cf assert further down
575 requestResult.pid = request->pid;
576 }
577 else
578 {
579 requestResult.result = 1;
580 requestResult.dbusName.clear();
581 requestResult.error = i18n("KDEInit could not launch '%1'", request->name);
582 if (!request->errorMsg.isEmpty())
583 requestResult.error += QString::fromLatin1(":\n") + request->errorMsg;
584 requestResult.pid = 0;
585
586#ifdef Q_WS_X11
587 if (!request->startup_dpy.isEmpty())
588 {
589 Display* dpy = NULL;
590 if( (mCached_dpy != NULL) &&
591 (request->startup_dpy == XDisplayString( mCached_dpy )))
592 dpy = mCached_dpy;
593 if( dpy == NULL )
594 dpy = XOpenDisplay(request->startup_dpy);
595 if( dpy )
596 {
597 KStartupInfoId id;
598 id.initId(request->startup_id);
599 KStartupInfo::sendFinishX( dpy, id );
600 if( mCached_dpy != dpy && mCached_dpy != NULL )
601 XCloseDisplay( mCached_dpy );
602 mCached_dpy = dpy;
603 }
604 }
605#endif
606 }
607
608 if (request->autoStart)
609 {
610 mAutoTimer.start(0);
611 }
612
613 if (request->transaction.type() != QDBusMessage::InvalidMessage)
614 {
615 if ( requestResult.dbusName.isNull() ) // null strings can't be sent
616 requestResult.dbusName.clear();
617 Q_ASSERT( !requestResult.error.isNull() );
618 PIDType<sizeof(pid_t)>::PID_t stream_pid = requestResult.pid;
619 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result
620 << requestResult.dbusName
621 << requestResult.error
622 << stream_pid));
623 }
624#ifdef KLAUNCHER_VERBOSE_OUTPUT
625 kDebug(7016) << "removing done request" << request->name << "PID" << request->pid;
626#endif
627
628 requestList.removeAll( request );
629 delete request;
630}
631
632static void appendLong(QByteArray &ba, long l)
633{
634 const int sz = ba.size();
635 ba.resize(sz + sizeof(long));
636 memcpy(ba.data() + sz, &l, sizeof(long));
637}
638
639void
640KLauncher::requestStart(KLaunchRequest *request)
641{
642#ifdef USE_KPROCESS_FOR_KIOSLAVES
643 requestList.append( request );
644 lastRequest = request;
645
646 KProcess *process = new KProcess;
647 process->setOutputChannelMode(KProcess::MergedChannels);
648 connect(process ,SIGNAL(readyReadStandardOutput()),this, SLOT(slotGotOutput()) );
649 connect(process ,SIGNAL(finished(int,QProcess::ExitStatus)),this, SLOT(slotFinished(int,QProcess::ExitStatus)) );
650 request->process = process;
651
652// process.setEnvironment(envlist);
653 QStringList args;
654 foreach (const QString &arg, request->arg_list)
655 args << arg;
656
657 QString executable = request->name;
658#ifdef Q_WS_MAC
659 const QString bundlepath = KStandardDirs::findExe(executable);
660 if (!bundlepath.isEmpty())
661 executable = bundlepath;
662#endif
663 process->setProgram(executable,args);
664 process->start();
665
666 if (!process->waitForStarted())
667 {
668 processRequestReturn(LAUNCHER_ERROR,"");
669 }
670 else
671 {
672 request->pid = process->pid();
673 QByteArray data((char *)&request->pid, sizeof(int));
674 processRequestReturn(LAUNCHER_OK,data);
675 }
676 return;
677
678#else
679 requestList.append( request );
680 // Send request to kdeinit.
681 klauncher_header request_header;
682 QByteArray requestData;
683 requestData.reserve(1024);
684
685 appendLong(requestData, request->arg_list.count() + 1);
686 requestData.append(request->name.toLocal8Bit());
687 requestData.append('\0');
688 foreach (const QString &arg, request->arg_list)
689 requestData.append(arg.toLocal8Bit()).append('\0');
690 appendLong(requestData, request->envs.count());
691 foreach (const QString &env, request->envs)
692 requestData.append(env.toLocal8Bit()).append('\0');
693 appendLong(requestData, 0); // avoid_loops, always false here
694#ifdef Q_WS_X11
695 bool startup_notify = !request->startup_id.isNull() && request->startup_id != "0";
696 if( startup_notify )
697 requestData.append(request->startup_id).append('\0');
698#endif
699 if (!request->cwd.isEmpty())
700 requestData.append(QFile::encodeName(request->cwd)).append('\0');
701
702#ifdef Q_WS_X11
703 request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW;
704#else
705 request_header.cmd = LAUNCHER_EXEC_NEW;
706#endif
707 request_header.arg_length = requestData.length();
708
709#ifdef KLAUNCHER_VERBOSE_OUTPUT
710 kDebug(7016) << "Asking kdeinit to start" << request->name << request->arg_list
711 << "cmd=" << commandToString(request_header.cmd);
712#endif
713
714 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
715 kde_safe_write(kdeinitSocket, requestData.data(), requestData.length());
716
717 // Wait for pid to return.
718 lastRequest = request;
719 do {
720 slotKDEInitData( kdeinitSocket );
721 }
722 while (lastRequest != 0);
723#endif
724}
725
726void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id)
727{
728 KLaunchRequest *request = new KLaunchRequest;
729 request->autoStart = false;
730 request->name = name;
731 request->arg_list = arg_list;
732 request->dbus_startup_type = KService::DBusNone;
733 request->pid = 0;
734 request->status = KLaunchRequest::Launching;
735 request->envs = envs;
736 // Find service, if any - strip path if needed
737 KService::Ptr service = KService::serviceByDesktopName( name.mid( name.lastIndexOf(QLatin1Char('/')) + 1 ));
738 if (service)
739 send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList());
740 else // no .desktop file, no startup info
741 cancel_service_startup_info( request, startup_id.toLocal8Bit(), envs );
742
743 requestStart(request);
744 // We don't care about this request any longer....
745 requestDone(request);
746}
747
748
749// KDE5: remove
750bool
751KLauncher::start_service_by_name(const QString &serviceName, const QStringList &urls,
752 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
753{
754 KService::Ptr service;
755 // Find service
756#ifndef KDE_NO_DEPRECATED
757 service = KService::serviceByName(serviceName);
758#endif
759 if (!service)
760 {
761 requestResult.result = ENOENT;
762 requestResult.error = i18n("Could not find service '%1'.", serviceName);
763 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
764 return false;
765 }
766 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
767}
768
769bool
770KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls,
771 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
772{
773 KService::Ptr service;
774 // Find service
775 const QFileInfo fi(serviceName);
776 if (fi.isAbsolute() && fi.exists())
777 {
778 // Full path
779 service = new KService(serviceName);
780 }
781 else
782 {
783 service = KService::serviceByDesktopPath(serviceName);
784 // TODO?
785 //if (!service)
786 // service = KService::serviceByStorageId(serviceName); // This method should be named start_service_by_storage_id ideally...
787 }
788 if (!service)
789 {
790 requestResult.result = ENOENT;
791 requestResult.error = i18n("Could not find service '%1'.", serviceName);
792 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
793 return false;
794 }
795 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
796}
797
798bool
799KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls,
800 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
801{
802 KService::Ptr service = KService::serviceByDesktopName(serviceName);
803 if (!service)
804 {
805 requestResult.result = ENOENT;
806 requestResult.error = i18n("Could not find service '%1'.", serviceName);
807 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
808 return false;
809 }
810 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
811}
812
813bool
814KLauncher::start_service(KService::Ptr service, const QStringList &_urls,
815 const QStringList &envs, const QByteArray &startup_id,
816 bool blind, bool autoStart, const QDBusMessage &msg)
817{
818 QStringList urls = _urls;
819 bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath());
820
821 if (!service->isValid() || !runPermitted)
822 {
823 requestResult.result = ENOEXEC;
824 if (service->isValid())
825 requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath());
826 else
827 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
828 cancel_service_startup_info( NULL, startup_id, envs ); // cancel it if any
829 return false;
830 }
831 KLaunchRequest *request = new KLaunchRequest;
832 request->autoStart = autoStart;
833
834 if ((urls.count() > 1) && !service->allowMultipleFiles())
835 {
836 // We need to launch the application N times. That sucks.
837 // We ignore the result for application 2 to N.
838 // For the first file we launch the application in the
839 // usual way. The reported result is based on this
840 // application.
841 QStringList::ConstIterator it = urls.constBegin();
842 for(++it;
843 it != urls.constEnd();
844 ++it)
845 {
846 QStringList singleUrl;
847 singleUrl.append(*it);
848 QByteArray startup_id2 = startup_id;
849 if( !startup_id2.isEmpty() && startup_id2 != "0" )
850 startup_id2 = "0"; // can't use the same startup_id several times // krazy:exclude=doublequote_chars
851 start_service( service, singleUrl, envs, startup_id2, true, false, msg);
852 }
853 QString firstURL = *(urls.begin());
854 urls.clear();
855 urls.append(firstURL);
856 }
857 createArgs(request, service, urls);
858
859 // We must have one argument at least!
860 if (!request->arg_list.count())
861 {
862 requestResult.result = ENOEXEC;
863 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
864 delete request;
865 cancel_service_startup_info( NULL, startup_id, envs );
866 return false;
867 }
868
869 request->name = request->arg_list.takeFirst();
870
871 if (request->name.endsWith(QLatin1String("/kioexec"))) {
872 // Special case for kioexec; if createArgs said we were going to use it,
873 // then we have to expect a kioexec-PID, not a org.kde.finalapp...
874 // Testcase: konqueror www.kde.org, RMB on link, open with, kruler.
875
876 request->dbus_startup_type = KService::DBusMulti;
877 request->dbus_name = QString::fromLatin1("org.kde.kioexec");
878 } else {
879 request->dbus_startup_type = service->dbusStartupType();
880
881 if ((request->dbus_startup_type == KService::DBusUnique) ||
882 (request->dbus_startup_type == KService::DBusMulti)) {
883 const QVariant v = service->property(QLatin1String("X-DBUS-ServiceName"));
884 if (v.isValid()) {
885 request->dbus_name = v.toString();
886 }
887 if (request->dbus_name.isEmpty()) {
888 const QString binName = KRun::binaryName(service->exec(), true);
889 request->dbus_name = QString::fromLatin1("org.kde.") + binName;
890 request->tolerant_dbus_name = QString::fromLatin1("*.") + binName;
891 }
892 }
893 }
894
895#ifdef KLAUNCHER_VERBOSE_OUTPUT
896 kDebug(7016) << "name=" << request->name << "dbus_name=" << request->dbus_name
897 << "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type];
898#endif
899
900 request->pid = 0;
901 request->envs = envs;
902 send_service_startup_info( request, service, startup_id, envs );
903
904 // Request will be handled later.
905 if (!blind && !autoStart)
906 {
907 msg.setDelayedReply(true);
908 request->transaction = msg;
909 }
910 queueRequest(request);
911 return true;
912}
913
914void
915KLauncher::send_service_startup_info( KLaunchRequest *request, KService::Ptr service, const QByteArray& startup_id,
916 const QStringList &envs )
917{
918#ifdef Q_WS_X11
919 request->startup_id = "0";// krazy:exclude=doublequote_chars
920 if (startup_id == "0")
921 return;
922 bool silent;
923 QByteArray wmclass;
924 if( !KRun::checkStartupNotify( QString(), service.data(), &silent, &wmclass ))
925 return;
926 KStartupInfoId id;
927 id.initId(startup_id);
928 QByteArray dpy_str;
929 foreach (const QString &env, envs) {
930 if (env.startsWith(QLatin1String("DISPLAY=")))
931 dpy_str = env.mid(8).toLocal8Bit();
932 }
933 Display* dpy = NULL;
934 if (!dpy_str.isEmpty() && mCached_dpy != NULL && dpy_str != XDisplayString(mCached_dpy))
935 dpy = mCached_dpy;
936 if (dpy == NULL)
937 dpy = XOpenDisplay(dpy_str);
938 request->startup_id = id.id();
939 if (dpy == NULL) {
940 cancel_service_startup_info( request, startup_id, envs );
941 return;
942 }
943
944 request->startup_dpy = dpy_str;
945
946 KStartupInfoData data;
947 data.setName( service->name());
948 data.setIcon( service->icon());
949 data.setDescription( i18n( "Launching %1" , service->name()));
950 if( !wmclass.isEmpty())
951 data.setWMClass( wmclass );
952 if( silent )
953 data.setSilent( KStartupInfoData::Yes );
954 data.setApplicationId( service->entryPath());
955 // the rest will be sent by kdeinit
956 KStartupInfo::sendStartupX( dpy, id, data );
957 if( mCached_dpy != dpy && mCached_dpy != NULL )
958 XCloseDisplay( mCached_dpy );
959 mCached_dpy = dpy;
960 return;
961#else
962 return;
963#endif
964}
965
966void
967KLauncher::cancel_service_startup_info( KLaunchRequest* request, const QByteArray& startup_id,
968 const QStringList &envs )
969{
970#ifdef Q_WS_X11
971 if( request != NULL )
972 request->startup_id = "0"; // krazy:exclude=doublequote_chars
973 if( !startup_id.isEmpty() && startup_id != "0" )
974 {
975 QString dpy_str;
976 foreach (const QString &env, envs) {
977 if (env.startsWith(QLatin1String("DISPLAY=")))
978 dpy_str = env.mid(8);
979 }
980 Display* dpy = NULL;
981 if( !dpy_str.isEmpty() && mCached_dpy != NULL
982 && dpy_str != QLatin1String(XDisplayString( mCached_dpy )) )
983 dpy = mCached_dpy;
984 if( dpy == NULL )
985 dpy = XOpenDisplay( dpy_str.toLatin1().constData() );
986 if( dpy == NULL )
987 return;
988 KStartupInfoId id;
989 id.initId(startup_id);
990 KStartupInfo::sendFinishX( dpy, id );
991 if( mCached_dpy != dpy && mCached_dpy != NULL )
992 XCloseDisplay( mCached_dpy );
993 mCached_dpy = dpy;
994 }
995#endif
996}
997
998bool
999KLauncher::kdeinit_exec(const QString &app, const QStringList &args,
1000 const QString& workdir, const QStringList &envs,
1001 const QString &startup_id, bool wait, const QDBusMessage &msg)
1002{
1003 KLaunchRequest *request = new KLaunchRequest;
1004 request->autoStart = false;
1005 request->arg_list = args;
1006 request->name = app;
1007 if (wait)
1008 request->dbus_startup_type = KService::DBusWait;
1009 else
1010 request->dbus_startup_type = KService::DBusNone;
1011 request->pid = 0;
1012#ifdef Q_WS_X11
1013 request->startup_id = startup_id.toLocal8Bit();
1014#endif
1015 request->envs = envs;
1016 request->cwd = workdir;
1017#ifdef Q_WS_X11
1018 if (!app.endsWith(QLatin1String("kbuildsycoca4"))) { // avoid stupid loop
1019 // Find service, if any - strip path if needed
1020 const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1);
1021 KService::Ptr service = KService::serviceByDesktopName(desktopName);
1022 if (service)
1023 send_service_startup_info(request, service,
1024 request->startup_id, envs);
1025 else // no .desktop file, no startup info
1026 cancel_service_startup_info(request, request->startup_id, envs);
1027 }
1028#endif
1029 msg.setDelayedReply(true);
1030 request->transaction = msg;
1031 queueRequest(request);
1032 return true;
1033}
1034
1035void
1036KLauncher::queueRequest(KLaunchRequest *request)
1037{
1038 requestQueue.append( request );
1039 if (!bProcessingQueue)
1040 {
1041 bProcessingQueue = true;
1042 QTimer::singleShot(0, this, SLOT(slotDequeue()));
1043 }
1044}
1045
1046void
1047KLauncher::slotDequeue()
1048{
1049 do {
1050 KLaunchRequest *request = requestQueue.takeFirst();
1051 // process request
1052 request->status = KLaunchRequest::Launching;
1053 requestStart(request);
1054 if (request->status != KLaunchRequest::Launching)
1055 {
1056 // Request handled.
1057#ifdef KLAUNCHER_VERBOSE_OUTPUT
1058 kDebug(7016) << "Request handled already";
1059#endif
1060 requestDone( request );
1061 continue;
1062 }
1063 } while(requestQueue.count());
1064 bProcessingQueue = false;
1065}
1066
1067void
1068KLauncher::createArgs( KLaunchRequest *request, const KService::Ptr service ,
1069 const QStringList &urls)
1070{
1071 const QStringList params = KRun::processDesktopExec(*service, urls);
1072
1073 for(QStringList::ConstIterator it = params.begin();
1074 it != params.end(); ++it)
1075 {
1076 request->arg_list.append(*it);
1077 }
1078
1079 const QString& path = service->path();
1080 if (!path.isEmpty()) {
1081 request->cwd = path;
1082 } else if (!urls.isEmpty()) {
1083 const KUrl url(urls.first());
1084 if (url.isLocalFile()) {
1085 request->cwd = url.directory();
1086 }
1087 }
1088}
1089
1090///// IO-Slave functions
1091
1092pid_t
1093KLauncher::requestHoldSlave(const KUrl &url, const QString &app_socket)
1094{
1095 IdleSlave *slave = 0;
1096 foreach (IdleSlave *p, mSlaveList)
1097 {
1098 if (p->onHold(url))
1099 {
1100 slave = p;
1101 break;
1102 }
1103 }
1104 if (slave)
1105 {
1106 mSlaveList.removeAll(slave);
1107 slave->connect(app_socket);
1108 return slave->pid();
1109 }
1110 return 0;
1111}
1112
1113pid_t
1114KLauncher::requestSlave(const QString &protocol,
1115 const QString &host,
1116 const QString &app_socket,
1117 QString &error)
1118{
1119 IdleSlave *slave = 0;
1120 foreach (IdleSlave *p, mSlaveList)
1121 {
1122 if (p->match(protocol, host, true))
1123 {
1124 slave = p;
1125 break;
1126 }
1127 }
1128 if (!slave)
1129 {
1130 foreach (IdleSlave *p, mSlaveList)
1131 {
1132 if (p->match(protocol, host, false))
1133 {
1134 slave = p;
1135 break;
1136 }
1137 }
1138 }
1139 if (!slave)
1140 {
1141 foreach (IdleSlave *p, mSlaveList)
1142 {
1143 if (p->match(protocol, QString(), false))
1144 {
1145 slave = p;
1146 break;
1147 }
1148 }
1149 }
1150 if (slave)
1151 {
1152 mSlaveList.removeAll(slave);
1153 slave->connect(app_socket);
1154 return slave->pid();
1155 }
1156
1157 QString name = KProtocolInfo::exec(protocol);
1158 if (name.isEmpty())
1159 {
1160 error = i18n("Unknown protocol '%1'.\n", protocol);
1161 return 0;
1162 }
1163
1164 QStringList arg_list;
1165#ifdef USE_KPROCESS_FOR_KIOSLAVES
1166 arg_list << name;
1167 arg_list << protocol;
1168 arg_list << mConnectionServer.address();
1169 arg_list << app_socket;
1170 name = KStandardDirs::findExe(QLatin1String("kioslave"));
1171#else
1172 QString arg1 = protocol;
1173 QString arg2 = mConnectionServer.address();
1174 QString arg3 = app_socket;
1175 arg_list.append(arg1);
1176 arg_list.append(arg2);
1177 arg_list.append(arg3);
1178#endif
1179
1180 kDebug(7016) << "KLauncher: launching new slave " << name << " with protocol=" << protocol
1181 << " args=" << arg_list << endl;
1182
1183#ifdef Q_OS_UNIX
1184 if (mSlaveDebug == protocol)
1185 {
1186#ifndef USE_KPROCESS_FOR_KIOSLAVES
1187 klauncher_header request_header;
1188 request_header.cmd = LAUNCHER_DEBUG_WAIT;
1189 request_header.arg_length = 0;
1190 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
1191#else
1192 name = QString::fromLatin1("gdb");
1193#endif
1194 }
1195 if (mSlaveValgrind == protocol) {
1196#ifndef USE_KPROCESS_FOR_KIOSLAVES // otherwise we've already done this
1197 KLibrary lib(name, KGlobal::mainComponent());
1198 arg_list.prepend(lib.fileName());
1199 arg_list.prepend(KStandardDirs::locate("exe", QString::fromLatin1("kioslave")));
1200#endif
1201 name = QString::fromLatin1("valgrind");
1202
1203 if (!mSlaveValgrindSkin.isEmpty()) {
1204 arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin);
1205 } else
1206 arg_list.prepend(QLatin1String("--tool=memcheck"));
1207 }
1208#endif
1209 KLaunchRequest *request = new KLaunchRequest;
1210 request->autoStart = false;
1211 request->name = name;
1212 request->arg_list = arg_list;
1213 request->dbus_startup_type = KService::DBusNone;
1214 request->pid = 0;
1215#ifdef Q_WS_X11
1216 request->startup_id = "0"; // krazy:exclude=doublequote_chars
1217#endif
1218 request->status = KLaunchRequest::Launching;
1219 requestStart(request);
1220 pid_t pid = request->pid;
1221
1222// kDebug(7016) << "Slave launched, pid = " << pid;
1223
1224 // We don't care about this request any longer....
1225 requestDone(request);
1226 if (!pid)
1227 {
1228 error = i18n("Error loading '%1'.\n", name);
1229 }
1230 return pid;
1231}
1232
1233bool KLauncher::checkForHeldSlave(const QString &url)
1234{
1235 Q_FOREACH (const IdleSlave *p, mSlaveList) {
1236 if (p->onHold(url)) {
1237 return true;
1238 }
1239 }
1240 return false;
1241}
1242
1243void
1244KLauncher::waitForSlave(int pid, const QDBusMessage &msg)
1245{
1246 foreach (IdleSlave *slave, mSlaveList)
1247 {
1248 if (slave->pid() == static_cast<pid_t>(pid))
1249 return; // Already here.
1250 }
1251 SlaveWaitRequest *waitRequest = new SlaveWaitRequest;
1252 msg.setDelayedReply(true);
1253 waitRequest->transaction = msg;
1254 waitRequest->pid = static_cast<pid_t>(pid);
1255 mSlaveWaitRequest.append(waitRequest);
1256}
1257
1258void
1259KLauncher::acceptSlave()
1260{
1261 IdleSlave *slave = new IdleSlave(this);
1262 mConnectionServer.setNextPendingConnection(&slave->mConn);
1263 mSlaveList.append(slave);
1264 connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone()));
1265 connect(slave, SIGNAL(statusUpdate(IdleSlave*)),
1266 this, SLOT(slotSlaveStatus(IdleSlave*)));
1267 if (!mTimer.isActive())
1268 {
1269 mTimer.start(1000*10);
1270 }
1271}
1272
1273void
1274KLauncher::slotSlaveStatus(IdleSlave *slave)
1275{
1276 QMutableListIterator<SlaveWaitRequest *> it(mSlaveWaitRequest);
1277 while(it.hasNext())
1278 {
1279 SlaveWaitRequest *waitRequest = it.next();
1280 if (waitRequest->pid == slave->pid())
1281 {
1282 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply());
1283 it.remove();
1284 delete waitRequest;
1285 }
1286 }
1287}
1288
1289void
1290KLauncher::slotSlaveGone()
1291{
1292 IdleSlave *slave = (IdleSlave *) sender();
1293 mSlaveList.removeAll(slave);
1294 if ((mSlaveList.count() == 0) && (mTimer.isActive()))
1295 {
1296 mTimer.stop();
1297 }
1298}
1299
1300void
1301KLauncher::idleTimeout()
1302{
1303 bool keepOneFileSlave=true;
1304 time_t now = time(0);
1305 foreach (IdleSlave *slave, mSlaveList)
1306 {
1307 if ((slave->protocol()==QLatin1String("file")) && (keepOneFileSlave))
1308 keepOneFileSlave=false;
1309 else if (slave->age(now) > SLAVE_MAX_IDLE)
1310 {
1311 // killing idle slave
1312 delete slave;
1313 }
1314 }
1315}
1316
1317void KLauncher::reparseConfiguration()
1318{
1319 KProtocolManager::reparseConfiguration();
1320 foreach (IdleSlave *slave, mSlaveList)
1321 slave->reparseConfiguration();
1322}
1323
1324
1325void
1326KLauncher::slotGotOutput()
1327{
1328#ifdef USE_KPROCESS_FOR_KIOSLAVES
1329 KProcess *p = static_cast<KProcess *>(sender());
1330 QByteArray _stdout = p->readAllStandardOutput();
1331 kDebug(7016) << _stdout.data();
1332#endif
1333}
1334
1335void
1336KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus )
1337{
1338#ifdef USE_KPROCESS_FOR_KIOSLAVES
1339 KProcess *p = static_cast<KProcess *>(sender());
1340 kDebug(7016) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus;
1341
1342 foreach (KLaunchRequest *request, requestList)
1343 {
1344 if (request->process == p)
1345 {
1346#ifdef KLAUNCHER_VERBOSE_OUTPUT
1347 kDebug(7016) << "found KProcess, request done";
1348#endif
1349 if (exitCode == 0 && exitStatus == QProcess::NormalExit)
1350 request->status = KLaunchRequest::Done;
1351 else
1352 request->status = KLaunchRequest::Error;
1353 requestDone(request);
1354 request->process = 0;
1355 }
1356 }
1357 delete p;
1358#else
1359 Q_UNUSED(exitCode);
1360 Q_UNUSED(exitStatus);
1361#endif
1362}
1363
1364
1365void KLauncher::terminate_kdeinit()
1366{
1367 kDebug(7016);
1368#ifndef USE_KPROCESS_FOR_KIOSLAVES
1369 klauncher_header request_header;
1370 request_header.cmd = LAUNCHER_TERMINATE_KDEINIT;
1371 request_header.arg_length = 0;
1372 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
1373#endif
1374}
1375
1376#include "klauncher.moc"
1377