mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
1623 lines
44 KiB
C++
1623 lines
44 KiB
C++
/*
|
|
* This file is part of the KDE libraries
|
|
* Copyright (c) 1999-2000 Waldo Bastian <bastian@kde.org>
|
|
* (c) 1999 Mario Weilguni <mweilguni@sime.com>
|
|
* (c) 2001 Lubos Lunak <l.lunak@kde.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#define QT_NO_CAST_FROM_ASCII
|
|
|
|
#include <config.h>
|
|
#include <config-kdeinit.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/select.h> // Needed on some systems.
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <locale.h>
|
|
|
|
#include <qplatformdefs.h>
|
|
#include <QtCore/QLibrary>
|
|
#include <QtCore/QString>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QRegExp>
|
|
#include <kcomponentdata.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kglobal.h>
|
|
#include <kconfig.h>
|
|
#include <kapplication.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kde_file.h>
|
|
#include <ksavefile.h>
|
|
#include <kpluginloader.h>
|
|
#include <kdeversion.h>
|
|
#include "klauncher_cmds.h"
|
|
#include "proctitle.h"
|
|
|
|
#ifdef Q_OS_LINUX
|
|
#include <sys/prctl.h>
|
|
#ifndef PR_SET_NAME
|
|
#define PR_SET_NAME 15
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef Q_WS_X11
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <fixx11h.h>
|
|
#include <kstartupinfo.h>
|
|
#endif
|
|
|
|
// #define SKIP_PROCTITLE 1
|
|
|
|
extern char **environ;
|
|
|
|
#ifdef Q_WS_X11
|
|
static int X11fd = -1;
|
|
static Display *X11display = 0;
|
|
static int X11_startup_notify_fd = -1;
|
|
static Display *X11_startup_notify_display = 0;
|
|
#endif
|
|
static KComponentData *s_instance = 0;
|
|
#define MAX_SOCK_FILE 255
|
|
static char sock_file[MAX_SOCK_FILE];
|
|
|
|
#ifdef Q_WS_X11
|
|
#define DISPLAY "DISPLAY"
|
|
#elif defined(Q_WS_QWS)
|
|
#define DISPLAY "QWS_DISPLAY"
|
|
#else
|
|
#error Use QT/X11 or QT/Embedded
|
|
#endif
|
|
|
|
/* Group data */
|
|
static struct {
|
|
int maxname;
|
|
int fd[2];
|
|
int launcher[2]; /* socket pair for launcher communication */
|
|
int deadpipe[2]; /* pipe used to detect dead children */
|
|
int initpipe[2];
|
|
int wrapper; /* socket for wrapper communication */
|
|
int accepted_fd; /* socket accepted and that must be closed in the child process */
|
|
char result;
|
|
int exit_status;
|
|
pid_t fork;
|
|
pid_t launcher_pid;
|
|
int n;
|
|
char **argv;
|
|
int (*func)(int, char *[]);
|
|
int (*launcher_func)(int);
|
|
bool debug_wait;
|
|
QByteArray errorMsg;
|
|
bool launcher_ok;
|
|
bool suicide;
|
|
} d;
|
|
|
|
struct child
|
|
{
|
|
pid_t pid;
|
|
int sock; /* fd to write message when child is dead*/
|
|
struct child *next;
|
|
};
|
|
|
|
static struct child *children;
|
|
|
|
#ifdef Q_WS_X11
|
|
extern "C" {
|
|
int kdeinit_xio_errhandler( Display * );
|
|
int kdeinit_x_errhandler( Display *, XErrorEvent *err );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Clean up the file descriptor table by closing all file descriptors
|
|
* that are still open.
|
|
*
|
|
* This function is called very early in the main() function, so that
|
|
* we don't leak anything that was leaked to us.
|
|
*/
|
|
static void cleanup_fds()
|
|
{
|
|
int maxfd = FD_SETSIZE;
|
|
struct rlimit rl;
|
|
if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
|
|
maxfd = rl.rlim_max;
|
|
for (int fd = 3; fd < maxfd; ++fd)
|
|
{
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Close fd's which are only useful for the parent process.
|
|
* Restore default signal handlers.
|
|
*/
|
|
static void close_fds()
|
|
{
|
|
while (struct child *child = children) {
|
|
close(child->sock);
|
|
children = child->next;
|
|
free(child);
|
|
}
|
|
|
|
if (d.deadpipe[0] != -1)
|
|
{
|
|
close(d.deadpipe[0]);
|
|
d.deadpipe[0] = -1;
|
|
}
|
|
|
|
if (d.deadpipe[1] != -1)
|
|
{
|
|
close(d.deadpipe[1]);
|
|
d.deadpipe[1] = -1;
|
|
}
|
|
|
|
if (d.initpipe[0] != -1)
|
|
{
|
|
close(d.initpipe[0]);
|
|
d.initpipe[0] = -1;
|
|
}
|
|
|
|
if (d.initpipe[1] != -1)
|
|
{
|
|
close(d.initpipe[1]);
|
|
d.initpipe[1] = -1;
|
|
}
|
|
|
|
if (d.launcher[0] != -1)
|
|
{
|
|
close(d.launcher[0]);
|
|
d.launcher[0] = -1;
|
|
}
|
|
if (d.wrapper != -1)
|
|
{
|
|
close(d.wrapper);
|
|
d.wrapper = -1;
|
|
}
|
|
if (d.accepted_fd != -1)
|
|
{
|
|
close(d.accepted_fd);
|
|
d.accepted_fd = -1;
|
|
}
|
|
#ifdef Q_WS_X11
|
|
if (X11fd >= 0)
|
|
{
|
|
close(X11fd);
|
|
X11fd = -1;
|
|
}
|
|
if (X11_startup_notify_fd >= 0 && X11_startup_notify_fd != X11fd )
|
|
{
|
|
close(X11_startup_notify_fd);
|
|
X11_startup_notify_fd = -1;
|
|
}
|
|
#endif
|
|
|
|
KDE_signal(SIGCHLD, SIG_DFL);
|
|
KDE_signal(SIGPIPE, SIG_DFL);
|
|
}
|
|
|
|
/* Notify wrapper program that the child it started has finished. */
|
|
static void child_died(pid_t exit_pid, int exit_status)
|
|
{
|
|
struct child *child, **childptr = &children;
|
|
|
|
while ((child = *childptr))
|
|
{
|
|
if (child->pid == exit_pid)
|
|
{
|
|
/* Send a message with the return value of the child on the control socket */
|
|
klauncher_header request_header;
|
|
long request_data[2];
|
|
request_header.cmd = LAUNCHER_CHILD_DIED;
|
|
request_header.arg_length = sizeof(long) * 2;
|
|
request_data[0] = exit_pid;
|
|
request_data[1] = exit_status;
|
|
write(child->sock, &request_header, sizeof(request_header));
|
|
write(child->sock, request_data, request_header.arg_length);
|
|
close(child->sock);
|
|
|
|
*childptr = child->next;
|
|
free(child);
|
|
return;
|
|
}
|
|
|
|
childptr = &child->next;
|
|
}
|
|
}
|
|
|
|
|
|
static void exitWithErrorMsg(const QString &errorMsg)
|
|
{
|
|
fprintf( stderr, "%s\n", errorMsg.toLocal8Bit().data() );
|
|
QByteArray utf8ErrorMsg = errorMsg.toUtf8();
|
|
d.result = 3; // Error with msg
|
|
write(d.fd[1], &d.result, 1);
|
|
int l = utf8ErrorMsg.length();
|
|
write(d.fd[1], &l, sizeof(int));
|
|
write(d.fd[1], utf8ErrorMsg.data(), l);
|
|
close(d.fd[1]);
|
|
exit(255);
|
|
}
|
|
|
|
static void setup_tty( const char* tty )
|
|
{
|
|
if( tty == NULL || *tty == '\0' )
|
|
return;
|
|
int fd = KDE_open( tty, O_WRONLY );
|
|
if( fd < 0 )
|
|
{
|
|
perror( "kdeinit4: could not open() tty" );
|
|
return;
|
|
}
|
|
if( dup2( fd, STDOUT_FILENO ) < 0 )
|
|
{
|
|
perror( "kdeinit4: could not dup2() stdout tty" );
|
|
}
|
|
if( dup2( fd, STDERR_FILENO ) < 0 )
|
|
{
|
|
perror( "kdeinit4: could not dup2() stderr tty" );
|
|
}
|
|
close( fd );
|
|
}
|
|
|
|
// from kdecore/netwm.cpp
|
|
static int get_current_desktop( Display* disp )
|
|
{
|
|
int desktop = 0; // no desktop by default
|
|
#ifdef Q_WS_X11 // Only X11 supports multiple desktops
|
|
Atom net_current_desktop = XInternAtom( disp, "_NET_CURRENT_DESKTOP", False );
|
|
Atom type_ret;
|
|
int format_ret;
|
|
unsigned char *data_ret;
|
|
unsigned long nitems_ret, unused;
|
|
if( XGetWindowProperty( disp, DefaultRootWindow( disp ), net_current_desktop,
|
|
0l, 1l, False, XA_CARDINAL, &type_ret, &format_ret, &nitems_ret, &unused, &data_ret )
|
|
== Success)
|
|
{
|
|
if (type_ret == XA_CARDINAL && format_ret == 32 && nitems_ret == 1)
|
|
desktop = *((long *) data_ret) + 1;
|
|
if (data_ret)
|
|
XFree ((char*) data_ret);
|
|
}
|
|
#endif
|
|
return desktop;
|
|
}
|
|
|
|
// var has to be e.g. "DISPLAY=", i.e. with =
|
|
const char* get_env_var( const char* var, int envc, const char* envs )
|
|
{
|
|
if( envc > 0 )
|
|
{ // get the var from envs
|
|
const char* env_l = envs;
|
|
int ln = strlen( var );
|
|
for (int i = 0; i < envc; i++)
|
|
{
|
|
if( strncmp( env_l, var, ln ) == 0 )
|
|
return env_l + ln;
|
|
while(*env_l != 0) env_l++;
|
|
env_l++;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
static void init_startup_info( KStartupInfoId& id, const char* bin,
|
|
int envc, const char* envs )
|
|
{
|
|
const char* dpy = get_env_var( DISPLAY"=", envc, envs );
|
|
// this may be called in a child, so it can't use display open using X11display
|
|
// also needed for multihead
|
|
X11_startup_notify_display = XOpenDisplay( dpy );
|
|
if( X11_startup_notify_display == NULL )
|
|
return;
|
|
X11_startup_notify_fd = XConnectionNumber( X11_startup_notify_display );
|
|
KStartupInfoData data;
|
|
int desktop = get_current_desktop( X11_startup_notify_display );
|
|
data.setDesktop( desktop );
|
|
data.setBin(QFile::decodeName(bin));
|
|
KStartupInfo::sendChangeX( X11_startup_notify_display, id, data );
|
|
XFlush( X11_startup_notify_display );
|
|
}
|
|
|
|
static void complete_startup_info( KStartupInfoId& id, pid_t pid )
|
|
{
|
|
if( X11_startup_notify_display == NULL )
|
|
return;
|
|
if( pid == 0 ) // failure
|
|
KStartupInfo::sendFinishX( X11_startup_notify_display, id );
|
|
else
|
|
{
|
|
KStartupInfoData data;
|
|
data.addPid( pid );
|
|
data.setHostname();
|
|
KStartupInfo::sendChangeX( X11_startup_notify_display, id, data );
|
|
}
|
|
XCloseDisplay( X11_startup_notify_display );
|
|
X11_startup_notify_display = NULL;
|
|
X11_startup_notify_fd = -1;
|
|
}
|
|
#endif
|
|
|
|
QByteArray execpath_avoid_loops( const QByteArray& exec, int envc, const char* envs, bool avoid_loops )
|
|
{
|
|
QStringList paths;
|
|
const QRegExp pathSepRegExp(QString::fromLatin1("[:\b]"));
|
|
if( envc > 0 ) /* use the passed environment */
|
|
{
|
|
const char* path = get_env_var( "PATH=", envc, envs );
|
|
if( path != NULL )
|
|
paths = QFile::decodeName(path).split(pathSepRegExp);
|
|
} else {
|
|
paths = QString::fromLocal8Bit(qgetenv("PATH")).split(pathSepRegExp, QString::KeepEmptyParts);
|
|
}
|
|
QString execpath = s_instance->dirs()->findExe(QFile::decodeName(exec), paths.join(QLatin1String(":")));
|
|
if (avoid_loops && !execpath.isEmpty()) {
|
|
const int pos = execpath.lastIndexOf(QLatin1Char('/'));
|
|
const QString bin_path = execpath.left(pos);
|
|
for( QStringList::Iterator it = paths.begin();
|
|
it != paths.end();
|
|
++it ) {
|
|
if( *it == bin_path || *it == bin_path + QLatin1Char('/')) {
|
|
paths.erase( it );
|
|
break; // -->
|
|
}
|
|
}
|
|
execpath = s_instance->dirs()->findExe(QFile::decodeName(exec), paths.join(QLatin1String(":")));
|
|
}
|
|
return QFile::encodeName(execpath);
|
|
}
|
|
|
|
static pid_t launch(int argc, const char *_name, const char *args,
|
|
const char *cwd=0, int envc=0, const char *envs=0,
|
|
bool reset_env = false,
|
|
const char *tty=0, bool avoid_loops = false,
|
|
const char* startup_id_str = "0" ) // krazy:exclude=doublequote_chars
|
|
{
|
|
QString lib;
|
|
QByteArray name;
|
|
QByteArray exec;
|
|
QString libpath;
|
|
QByteArray execpath;
|
|
|
|
if (_name[0] != '/') {
|
|
name = _name;
|
|
lib = QFile::decodeName(name);
|
|
exec = name;
|
|
KPluginLoader klib(lib, *s_instance );
|
|
libpath = klib.fileName();
|
|
execpath = execpath_avoid_loops(exec, envc, envs, avoid_loops);
|
|
} else {
|
|
name = _name;
|
|
lib = QFile::decodeName(name);
|
|
name = name.mid(name.lastIndexOf('/') + 1);
|
|
exec = _name;
|
|
if (lib.endsWith(QLatin1String(".so")))
|
|
libpath = lib;
|
|
else {
|
|
execpath = exec;
|
|
}
|
|
}
|
|
#ifndef NDEBUG
|
|
fprintf(stderr,"kdeinit4: preparing to launch %s\n", libpath.isEmpty()
|
|
? execpath.constData() : libpath.toUtf8().constData());
|
|
#endif
|
|
if (!args) {
|
|
argc = 1;
|
|
}
|
|
|
|
if (0 > pipe(d.fd))
|
|
{
|
|
perror("kdeinit4: pipe() failed");
|
|
d.result = 3;
|
|
d.errorMsg = i18n("Unable to start new process.\n"
|
|
"The system may have reached the maximum number of open files possible or the maximum number of open files that you are allowed to use has been reached.").toUtf8();
|
|
d.fork = 0;
|
|
return d.fork;
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
KStartupInfoId startup_id;
|
|
startup_id.initId( startup_id_str );
|
|
if( !startup_id.none())
|
|
init_startup_info( startup_id, name, envc, envs );
|
|
#endif
|
|
// find out this path before forking, doing it afterwards
|
|
// crashes on some platforms, notably OSX
|
|
|
|
d.errorMsg = 0;
|
|
d.fork = fork();
|
|
switch(d.fork) {
|
|
case -1:
|
|
perror("kdeinit4: fork() failed");
|
|
d.result = 3;
|
|
d.errorMsg = i18n("Unable to create new process.\n"
|
|
"The system may have reached the maximum number of processes possible or the maximum number of processes that you are allowed to use has been reached.").toUtf8();
|
|
close(d.fd[0]);
|
|
close(d.fd[1]);
|
|
d.fork = 0;
|
|
break;
|
|
case 0:
|
|
{
|
|
// Child
|
|
close(d.fd[0]);
|
|
close_fds();
|
|
|
|
// Try to chdir, either to the requested directory or to the user's document path by default.
|
|
// We ignore errors - if you write a desktop file with Exec=foo and Path=/doesnotexist,
|
|
// we still want to execute `foo` even if the chdir() failed.
|
|
if (cwd && *cwd) {
|
|
(void)chdir(cwd);
|
|
}
|
|
|
|
if( reset_env ) // KWRAPPER/SHELL
|
|
{
|
|
|
|
QList<QByteArray> unset_envs;
|
|
for( int tmp_env_count = 0;
|
|
environ[tmp_env_count];
|
|
tmp_env_count++)
|
|
unset_envs.append( environ[ tmp_env_count ] );
|
|
foreach(const QByteArray &tmp, unset_envs)
|
|
{
|
|
int pos = tmp.indexOf( '=' );
|
|
if( pos >= 0 )
|
|
unsetenv( tmp.left( pos ));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < envc; i++)
|
|
{
|
|
putenv((char *)envs);
|
|
while(*envs != 0) envs++;
|
|
envs++;
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
if( startup_id.none())
|
|
KStartupInfo::resetStartupEnv();
|
|
else
|
|
startup_id.setupStartupEnv();
|
|
#endif
|
|
{
|
|
int r;
|
|
QByteArray procTitle;
|
|
d.argv = (char **) malloc(sizeof(char *) * (argc+1));
|
|
d.argv[0] = (char *) _name;
|
|
for (int i = 1; i < argc; i++)
|
|
{
|
|
d.argv[i] = (char *) args;
|
|
procTitle += ' ';
|
|
procTitle += (char *) args;
|
|
while(*args != 0) args++;
|
|
args++;
|
|
}
|
|
d.argv[argc] = 0;
|
|
|
|
#ifndef SKIP_PROCTITLE
|
|
// Give the process a new name
|
|
#ifdef Q_OS_LINUX
|
|
// Set the process name so that killall works like intended
|
|
r = prctl(PR_SET_NAME, (unsigned long) name.data(), 0, 0, 0);
|
|
if ( r == 0 )
|
|
proctitle_set( "%s [kdeinit]%s", name.data(), procTitle.data() ? procTitle.data() : "" );
|
|
else
|
|
proctitle_set( "kdeinit4: %s%s", name.data(), procTitle.data() ? procTitle.data() : "" );
|
|
#else
|
|
proctitle_set( "kdeinit4: %s%s", name.data(), procTitle.data() ? procTitle.data() : "" );
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
if (libpath.isEmpty() && execpath.isEmpty())
|
|
{
|
|
QString errorMsg = i18n("Could not find '%1' executable.", QFile::decodeName(_name));
|
|
exitWithErrorMsg(errorMsg);
|
|
}
|
|
|
|
QLibrary l(libpath);
|
|
|
|
if ( !libpath.isEmpty() )
|
|
{
|
|
if (!l.load() || !l.isLoaded() )
|
|
{
|
|
QString ltdlError (l.errorString());
|
|
if (execpath.isEmpty())
|
|
{
|
|
// Error
|
|
QString errorMsg = i18n("Could not open library '%1'.\n%2", libpath, ltdlError);
|
|
exitWithErrorMsg(errorMsg);
|
|
}
|
|
else
|
|
{
|
|
// Print warning
|
|
fprintf(stderr, "Could not open library %s: %s\n", qPrintable(lib),
|
|
qPrintable(ltdlError) );
|
|
}
|
|
}
|
|
}
|
|
if (!l.isLoaded())
|
|
{
|
|
d.result = 2; // Try execing
|
|
write(d.fd[1], &d.result, 1);
|
|
|
|
// We set the close on exec flag.
|
|
// Closing of d.fd[1] indicates that the execvp succeeded!
|
|
fcntl(d.fd[1], F_SETFD, FD_CLOEXEC);
|
|
|
|
setup_tty( tty );
|
|
|
|
if (!execpath.isEmpty())
|
|
execvp(execpath.constData(), d.argv);
|
|
|
|
d.result = 1; // Error
|
|
write(d.fd[1], &d.result, 1);
|
|
close(d.fd[1]);
|
|
exit(255);
|
|
}
|
|
|
|
void * sym = l.resolve( "kdemain" );
|
|
if ( !sym )
|
|
{
|
|
QString ltdlError = l.errorString();
|
|
fprintf(stderr, "Could not find kdemain: %s\n", qPrintable(ltdlError) );
|
|
QString errorMsg = i18n("Could not find 'kdemain' in '%1'.\n%2",
|
|
libpath, ltdlError);
|
|
exitWithErrorMsg(errorMsg);
|
|
}
|
|
|
|
d.result = 0; // Success
|
|
write(d.fd[1], &d.result, 1);
|
|
close(d.fd[1]);
|
|
|
|
d.func = (int (*)(int, char *[])) sym;
|
|
if (d.debug_wait)
|
|
{
|
|
fprintf(stderr, "kdeinit4: Suspending process\n"
|
|
"kdeinit4: 'gdb kdeinit4 %d' to debug\n"
|
|
"kdeinit4: 'kill -SIGCONT %d' to continue\n",
|
|
getpid(), getpid());
|
|
kill(getpid(), SIGSTOP);
|
|
}
|
|
else
|
|
{
|
|
setup_tty( tty );
|
|
}
|
|
|
|
exit( d.func(argc, d.argv)); /* Launch! */
|
|
|
|
break;
|
|
}
|
|
default:
|
|
// Parent
|
|
close(d.fd[1]);
|
|
bool exec = false;
|
|
for(;;)
|
|
{
|
|
d.n = read(d.fd[0], &d.result, 1);
|
|
if (d.n == 1)
|
|
{
|
|
if (d.result == 2)
|
|
{
|
|
#ifndef NDEBUG
|
|
//fprintf(stderr, "kdeinit4: no kdeinit module, trying exec....\n");
|
|
#endif
|
|
exec = true;
|
|
continue;
|
|
}
|
|
if (d.result == 3)
|
|
{
|
|
int l = 0;
|
|
d.n = read(d.fd[0], &l, sizeof(int));
|
|
if (d.n == sizeof(int))
|
|
{
|
|
QByteArray tmp;
|
|
tmp.resize(l+1);
|
|
d.n = read(d.fd[0], tmp.data(), l);
|
|
tmp[l] = 0;
|
|
if (d.n == l)
|
|
d.errorMsg = tmp;
|
|
}
|
|
}
|
|
// Finished
|
|
break;
|
|
}
|
|
if (d.n == -1)
|
|
{
|
|
if (errno == ECHILD) { // a child died.
|
|
continue;
|
|
}
|
|
if (errno == EINTR || errno == EAGAIN) { // interrupted or more to read
|
|
continue;
|
|
}
|
|
}
|
|
if (d.n == 0)
|
|
{
|
|
if (exec) {
|
|
d.result = 0;
|
|
} else {
|
|
fprintf(stderr,"kdeinit4: (%s %s) Pipe closed unexpectedly", name.constData(), execpath.constData());
|
|
perror("kdeinit4: Pipe closed unexpectedly");
|
|
d.result = 1; // Error
|
|
}
|
|
break;
|
|
}
|
|
perror("kdeinit4: Error reading from pipe");
|
|
d.result = 1; // Error
|
|
break;
|
|
}
|
|
close(d.fd[0]);
|
|
}
|
|
#ifdef Q_WS_X11
|
|
if( !startup_id.none())
|
|
{
|
|
if( d.fork && d.result == 0 ) // launched successfully
|
|
complete_startup_info( startup_id, d.fork );
|
|
else // failure, cancel ASN
|
|
complete_startup_info( startup_id, 0 );
|
|
}
|
|
#endif
|
|
return d.fork;
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
static void sig_child_handler(int)
|
|
{
|
|
/*
|
|
* Write into the pipe of death.
|
|
* This way we are sure that we return from the select()
|
|
*
|
|
* A signal itself causes select to return as well, but
|
|
* this creates a race-condition in case the signal arrives
|
|
* just before we enter the select.
|
|
*/
|
|
char c = 0;
|
|
write(d.deadpipe[1], &c, 1);
|
|
}
|
|
|
|
}
|
|
|
|
static void init_signals()
|
|
{
|
|
struct sigaction act;
|
|
long options;
|
|
|
|
if (pipe(d.deadpipe) != 0)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not create pipe");
|
|
exit(255);
|
|
}
|
|
|
|
options = fcntl(d.deadpipe[0], F_GETFL);
|
|
if (options == -1)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not make pipe non-blocking");
|
|
exit(255);
|
|
}
|
|
|
|
if (fcntl(d.deadpipe[0], F_SETFL, options | O_NONBLOCK) == -1)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not make pipe non-blocking");
|
|
exit(255);
|
|
}
|
|
|
|
/*
|
|
* A SIGCHLD handler is installed which sends a byte into the
|
|
* pipe of death. This is to ensure that a dying child causes
|
|
* an exit from select().
|
|
*/
|
|
act.sa_handler=sig_child_handler;
|
|
sigemptyset(&(act.sa_mask));
|
|
sigaddset(&(act.sa_mask), SIGCHLD);
|
|
sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0L);
|
|
act.sa_flags = SA_NOCLDSTOP;
|
|
|
|
// CC: take care of SunOS which automatically restarts interrupted system
|
|
// calls (and thus does not have SA_RESTART)
|
|
|
|
#ifdef SA_RESTART
|
|
act.sa_flags |= SA_RESTART;
|
|
#endif
|
|
sigaction( SIGCHLD, &act, 0L);
|
|
|
|
act.sa_handler=SIG_IGN;
|
|
sigemptyset(&(act.sa_mask));
|
|
sigaddset(&(act.sa_mask), SIGPIPE);
|
|
sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0L);
|
|
act.sa_flags = 0;
|
|
sigaction( SIGPIPE, &act, 0L);
|
|
}
|
|
|
|
static void shutdown_kdeinit()
|
|
{
|
|
QT_SOCKLEN_T socklen;
|
|
|
|
/** Test if socket file is already present
|
|
* note that access() resolves symlinks, and so we check the actual
|
|
* socket file if it exists
|
|
*/
|
|
if (access(sock_file, W_OK) == 0)
|
|
{
|
|
int s;
|
|
struct sockaddr_un server;
|
|
|
|
// fprintf(stderr, "kdeinit4: Warning, socket_file already exists!\n");
|
|
// Create the socket stream
|
|
s = socket(PF_UNIX, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
{
|
|
perror("socket() failed");
|
|
exit(255);
|
|
}
|
|
server.sun_family = AF_UNIX;
|
|
strcpy(server.sun_path, sock_file);
|
|
socklen = sizeof(server);
|
|
|
|
if(connect(s, (struct sockaddr *)&server, socklen) == 0)
|
|
{
|
|
fprintf(stderr, "kdeinit4: Shutting down running client.\n");
|
|
klauncher_header request_header;
|
|
request_header.cmd = LAUNCHER_TERMINATE_KDEINIT;
|
|
request_header.arg_length = 0;
|
|
write(s, &request_header, sizeof(request_header));
|
|
sleep(1); // Give it some time
|
|
}
|
|
close(s);
|
|
}
|
|
|
|
// Delete any stale socket file (and symlink)
|
|
unlink(sock_file);
|
|
}
|
|
|
|
static void init_kdeinit_socket()
|
|
{
|
|
struct sockaddr_un sa;
|
|
QT_SOCKLEN_T socklen;
|
|
long options;
|
|
const QByteArray home_dir = qgetenv("HOME");
|
|
int max_tries = 10;
|
|
if (home_dir.isEmpty())
|
|
{
|
|
fprintf(stderr, "kdeinit4: Aborting. $HOME not set!");
|
|
exit(255);
|
|
}
|
|
if (chdir(home_dir) != 0) {
|
|
fprintf(stderr, "kdeinit4: Aborting. Couldn't enter '%s'!", home_dir.constData());
|
|
exit(255);
|
|
}
|
|
|
|
{
|
|
QByteArray path = home_dir;
|
|
QByteArray readOnly = qgetenv("KDE_HOME_READONLY");
|
|
if (access(path.data(), R_OK|W_OK))
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
fprintf(stderr, "kdeinit4: Aborting. $HOME directory (%s) does not exist.\n", path.data());
|
|
exit(255);
|
|
}
|
|
else if (readOnly.isEmpty())
|
|
{
|
|
fprintf(stderr, "kdeinit4: Aborting. No write access to $HOME directory (%s).\n", path.data());
|
|
exit(255);
|
|
}
|
|
}
|
|
#if 0 // obsolete in kde4. Should we check writing to another file instead?
|
|
path = qgetenv("ICEAUTHORITY");
|
|
if (path.isEmpty())
|
|
{
|
|
path = home_dir;
|
|
path += "/.ICEauthority";
|
|
}
|
|
if (access(path.data(), R_OK|W_OK) && (errno != ENOENT))
|
|
{
|
|
fprintf(stderr, "kdeinit4: Aborting. No write access to '%s'.\n", path.data());
|
|
exit(255);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
shutdown_kdeinit();
|
|
|
|
// Create socket
|
|
d.wrapper = socket(PF_UNIX, SOCK_STREAM, 0);
|
|
if (d.wrapper < 0)
|
|
{
|
|
perror("kdeinit4: Aborting. socket() failed");
|
|
exit(255);
|
|
}
|
|
|
|
options = fcntl(d.wrapper, F_GETFL);
|
|
if (options == -1)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not make socket non-blocking");
|
|
close(d.wrapper);
|
|
exit(255);
|
|
}
|
|
|
|
if (fcntl(d.wrapper, F_SETFL, options | O_NONBLOCK) == -1)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not make socket non-blocking");
|
|
close(d.wrapper);
|
|
exit(255);
|
|
}
|
|
|
|
while (1) {
|
|
// Bind it
|
|
socklen = sizeof(sa);
|
|
memset(&sa, 0, socklen);
|
|
sa.sun_family = AF_UNIX;
|
|
strcpy(sa.sun_path, sock_file);
|
|
if(bind(d.wrapper, (struct sockaddr *)&sa, socklen) != 0)
|
|
{
|
|
if (max_tries == 0) {
|
|
perror("kdeinit4: Aborting. bind() failed");
|
|
fprintf(stderr, "Could not bind to socket '%s'\n", sock_file);
|
|
close(d.wrapper);
|
|
exit(255);
|
|
}
|
|
max_tries--;
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// Set permissions
|
|
if (chmod(sock_file, 0600) != 0)
|
|
{
|
|
perror("kdeinit4: Aborting. Can not set permissions on socket");
|
|
fprintf(stderr, "Wrong permissions of socket '%s'\n", sock_file);
|
|
unlink(sock_file);
|
|
close(d.wrapper);
|
|
exit(255);
|
|
}
|
|
|
|
if(listen(d.wrapper, SOMAXCONN) < 0)
|
|
{
|
|
perror("kdeinit4: Aborting. listen() failed");
|
|
unlink(sock_file);
|
|
close(d.wrapper);
|
|
exit(255);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read 'len' bytes from 'sock' into buffer.
|
|
* returns 0 on success, -1 on failure.
|
|
*/
|
|
static int read_socket(int sock, char *buffer, int len)
|
|
{
|
|
ssize_t result;
|
|
int bytes_left = len;
|
|
while ( bytes_left > 0)
|
|
{
|
|
result = read(sock, buffer, bytes_left);
|
|
if (result > 0)
|
|
{
|
|
buffer += result;
|
|
bytes_left -= result;
|
|
}
|
|
else if (result == 0)
|
|
return -1;
|
|
else if ((result == -1) && (errno != EINTR) && (errno != EAGAIN))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void start_klauncher()
|
|
{
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, d.launcher) < 0) {
|
|
perror("kdeinit4: socketpair() failed");
|
|
exit(255);
|
|
}
|
|
char args[32];
|
|
strcpy(args, "--fd=");
|
|
sprintf(args + 5, "%d", d.launcher[1]);
|
|
d.launcher_pid = launch( 2, "klauncher", args );
|
|
close(d.launcher[1]);
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Launched KLauncher, pid = %ld, result = %d\n",
|
|
(long) d.launcher_pid, d.result);
|
|
#endif
|
|
}
|
|
|
|
static void launcher_died()
|
|
{
|
|
if (!d.launcher_ok)
|
|
{
|
|
/* This is bad. */
|
|
fprintf(stderr, "kdeinit4: Communication error with launcher. Exiting!\n");
|
|
::exit(255);
|
|
return;
|
|
}
|
|
|
|
// KLauncher died... restart
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: KLauncher died unexpectedly.\n");
|
|
#endif
|
|
// Make sure it's really dead.
|
|
if (d.launcher_pid)
|
|
{
|
|
kill(d.launcher_pid, SIGKILL);
|
|
sleep(1); // Give it some time
|
|
}
|
|
|
|
d.launcher_ok = false;
|
|
d.launcher_pid = 0;
|
|
close(d.launcher[0]);
|
|
d.launcher[0] = -1;
|
|
|
|
start_klauncher();
|
|
}
|
|
|
|
static bool handle_launcher_request(int sock, const char *who)
|
|
{
|
|
(void)who; // for NDEBUG
|
|
|
|
klauncher_header request_header;
|
|
char *request_data = 0L;
|
|
int result = read_socket(sock, (char *) &request_header, sizeof(request_header));
|
|
if (result != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( request_header.arg_length != 0 )
|
|
{
|
|
request_data = (char *) malloc(request_header.arg_length);
|
|
|
|
result = read_socket(sock, request_data, request_header.arg_length);
|
|
if (result != 0)
|
|
{
|
|
free(request_data);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//kDebug() << "Got cmd" << request_header.cmd << commandToString(request_header.cmd);
|
|
if (request_header.cmd == LAUNCHER_OK)
|
|
{
|
|
d.launcher_ok = true;
|
|
}
|
|
else if (request_header.arg_length &&
|
|
((request_header.cmd == LAUNCHER_EXEC) ||
|
|
(request_header.cmd == LAUNCHER_EXT_EXEC) ||
|
|
(request_header.cmd == LAUNCHER_EXEC_NEW)))
|
|
{
|
|
pid_t pid;
|
|
klauncher_header response_header;
|
|
long response_data;
|
|
long l;
|
|
memcpy( &l, request_data, sizeof( long ));
|
|
int argc = l;
|
|
const char *name = request_data + sizeof(long);
|
|
const char *args = name + strlen(name) + 1;
|
|
const char *cwd = 0;
|
|
int envc = 0;
|
|
const char *envs = 0;
|
|
const char *tty = 0;
|
|
int avoid_loops = 0;
|
|
const char *startup_id_str = "0"; // krazy:exclude=doublequote_chars
|
|
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Got %s '%s' from %s.\n",
|
|
commandToString(request_header.cmd),
|
|
name, who);
|
|
#endif
|
|
|
|
const char *arg_n = args;
|
|
for(int i = 1; i < argc; i++)
|
|
{
|
|
arg_n = arg_n + strlen(arg_n) + 1;
|
|
}
|
|
|
|
if( request_header.cmd == LAUNCHER_EXT_EXEC || request_header.cmd == LAUNCHER_EXEC_NEW )
|
|
{
|
|
memcpy( &l, arg_n, sizeof( long ));
|
|
envc = l;
|
|
arg_n += sizeof(long);
|
|
envs = arg_n;
|
|
for(int i = 0; i < envc; i++)
|
|
{
|
|
arg_n = arg_n + strlen(arg_n) + 1;
|
|
}
|
|
memcpy( &l, arg_n, sizeof( long ));
|
|
avoid_loops = l;
|
|
arg_n += sizeof( long );
|
|
}
|
|
|
|
if( request_header.cmd == LAUNCHER_EXT_EXEC )
|
|
{
|
|
startup_id_str = arg_n;
|
|
arg_n += strlen( startup_id_str ) + 1;
|
|
}
|
|
|
|
if ((request_header.arg_length > (arg_n - request_data)) &&
|
|
(request_header.cmd == LAUNCHER_EXT_EXEC || request_header.cmd == LAUNCHER_EXEC_NEW ))
|
|
{
|
|
// Optional cwd
|
|
cwd = arg_n; arg_n += strlen(cwd) + 1;
|
|
}
|
|
|
|
if ((arg_n - request_data) != request_header.arg_length)
|
|
{
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: EXEC request has invalid format.\n");
|
|
#endif
|
|
free(request_data);
|
|
d.debug_wait = false;
|
|
return true; // sure?
|
|
}
|
|
|
|
pid = launch( argc, name, args, cwd, envc, envs,
|
|
false,
|
|
tty, avoid_loops, startup_id_str );
|
|
|
|
if (pid && (d.result == 0))
|
|
{
|
|
response_header.cmd = LAUNCHER_OK;
|
|
response_header.arg_length = sizeof(response_data);
|
|
response_data = pid;
|
|
write(sock, &response_header, sizeof(response_header));
|
|
write(sock, &response_data, response_header.arg_length);
|
|
|
|
/* add new child to list */
|
|
struct child *child = (struct child *) malloc(sizeof(struct child));
|
|
child->pid = pid;
|
|
child->sock = dup(sock);
|
|
child->next = children;
|
|
children = child;
|
|
}
|
|
else
|
|
{
|
|
int l = d.errorMsg.length();
|
|
if (l) l++; // Include trailing null.
|
|
response_header.cmd = LAUNCHER_ERROR;
|
|
response_header.arg_length = l;
|
|
write(sock, &response_header, sizeof(response_header));
|
|
if (l)
|
|
write(sock, d.errorMsg.data(), l);
|
|
}
|
|
d.debug_wait = false;
|
|
}
|
|
else if (request_header.arg_length && request_header.cmd == LAUNCHER_SETENV)
|
|
{
|
|
const char *env_name;
|
|
const char *env_value;
|
|
env_name = request_data;
|
|
env_value = env_name + strlen(env_name) + 1;
|
|
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Got SETENV '%s=%s' from %s.\n", env_name, env_value, who);
|
|
#endif
|
|
|
|
if ( request_header.arg_length !=
|
|
(int) (strlen(env_name) + strlen(env_value) + 2))
|
|
{
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: SETENV request has invalid format.\n");
|
|
#endif
|
|
free(request_data);
|
|
return true; // sure?
|
|
}
|
|
setenv( env_name, env_value, 1);
|
|
}
|
|
else if (request_header.cmd == LAUNCHER_TERMINATE_KDEINIT)
|
|
{
|
|
#ifndef NDEBUG
|
|
fprintf(stderr,"kdeinit4: Got termination request (PID %ld).\n", (long) getpid());
|
|
#endif
|
|
if (d.launcher_pid) {
|
|
kill(d.launcher_pid, SIGTERM);
|
|
d.launcher_pid = 0;
|
|
close(d.launcher[0]);
|
|
d.launcher[0] = -1;
|
|
}
|
|
unlink(sock_file);
|
|
if (children) {
|
|
close(d.wrapper);
|
|
d.wrapper = -1;
|
|
#ifndef NDEBUG
|
|
fprintf(stderr,"kdeinit4: Closed sockets, but not exiting until all children terminate.\n");
|
|
#endif
|
|
} else {
|
|
raise(SIGTERM);
|
|
}
|
|
}
|
|
else if (request_header.cmd == LAUNCHER_DEBUG_WAIT)
|
|
{
|
|
#ifndef NDEBUG
|
|
fprintf(stderr,"kdeinit4: Debug wait activated.\n");
|
|
#endif
|
|
d.debug_wait = true;
|
|
}
|
|
if (request_data)
|
|
free(request_data);
|
|
return true;
|
|
}
|
|
|
|
static void handle_requests(pid_t waitForPid)
|
|
{
|
|
int max_sock = d.deadpipe[0];
|
|
if (d.wrapper > max_sock)
|
|
max_sock = d.wrapper;
|
|
if (d.launcher[0] > max_sock)
|
|
max_sock = d.launcher[0];
|
|
#ifdef Q_WS_X11
|
|
if (X11fd > max_sock)
|
|
max_sock = X11fd;
|
|
#endif
|
|
max_sock++;
|
|
|
|
while(1)
|
|
{
|
|
fd_set rd_set;
|
|
fd_set wr_set;
|
|
fd_set e_set;
|
|
int result;
|
|
pid_t exit_pid;
|
|
int exit_status;
|
|
char c;
|
|
|
|
/* Flush the pipe of death */
|
|
while( read(d.deadpipe[0], &c, 1) == 1)
|
|
{}
|
|
|
|
/* Handle dying children */
|
|
do {
|
|
exit_pid = waitpid(-1, &exit_status, WNOHANG);
|
|
if (exit_pid > 0)
|
|
{
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: PID %ld terminated.\n", (long) exit_pid);
|
|
#endif
|
|
if (waitForPid && (exit_pid == waitForPid))
|
|
return;
|
|
|
|
if( WIFEXITED( exit_status )) // fix process return value
|
|
exit_status = WEXITSTATUS(exit_status);
|
|
else if( WIFSIGNALED(exit_status))
|
|
exit_status = 128 + WTERMSIG( exit_status );
|
|
child_died(exit_pid, exit_status);
|
|
|
|
if (d.wrapper < 0 && !children) {
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Last child terminated, exiting (PID %ld).\n",
|
|
(long) getpid());
|
|
#endif
|
|
raise(SIGTERM);
|
|
}
|
|
}
|
|
}
|
|
while( exit_pid > 0);
|
|
|
|
FD_ZERO(&rd_set);
|
|
FD_ZERO(&wr_set);
|
|
FD_ZERO(&e_set);
|
|
|
|
if (d.launcher[0] >= 0)
|
|
FD_SET(d.launcher[0], &rd_set);
|
|
if (d.wrapper >= 0)
|
|
FD_SET(d.wrapper, &rd_set);
|
|
FD_SET(d.deadpipe[0], &rd_set);
|
|
#ifdef Q_WS_X11
|
|
if(X11fd >= 0) FD_SET(X11fd, &rd_set);
|
|
#endif
|
|
|
|
result = select(max_sock, &rd_set, &wr_set, &e_set, 0);
|
|
if (result < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
perror("kdeinit4: Aborting. select() failed");
|
|
return;
|
|
}
|
|
|
|
/* Handle wrapper request */
|
|
if (d.wrapper >= 0 && FD_ISSET(d.wrapper, &rd_set))
|
|
{
|
|
struct sockaddr_un client;
|
|
QT_SOCKLEN_T sClient = sizeof(client);
|
|
int sock = accept(d.wrapper, (struct sockaddr *)&client, &sClient);
|
|
if (sock >= 0)
|
|
{
|
|
d.accepted_fd = sock;
|
|
handle_launcher_request(sock, "wrapper");
|
|
close(sock);
|
|
d.accepted_fd = -1;
|
|
}
|
|
}
|
|
|
|
/* Handle launcher request */
|
|
if (d.launcher[0] >= 0 && FD_ISSET(d.launcher[0], &rd_set))
|
|
{
|
|
if (!handle_launcher_request(d.launcher[0], "launcher"))
|
|
launcher_died();
|
|
if (waitForPid == d.launcher_pid)
|
|
return;
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
/* Look for incoming X11 events */
|
|
if(X11fd >= 0 && FD_ISSET(X11fd,&rd_set)) {
|
|
if (X11display != 0) {
|
|
XEvent event_return;
|
|
while (XPending(X11display))
|
|
XNextEvent(X11display, &event_return);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void kdeinit_library_path()
|
|
{
|
|
QByteArray display = qgetenv(DISPLAY);
|
|
if (display.isEmpty())
|
|
{
|
|
#if defined(Q_WS_X11) || defined(Q_WS_QWS)
|
|
fprintf(stderr, "kdeinit4: Aborting. $" DISPLAY " is not set.\n");
|
|
exit(255);
|
|
#endif
|
|
}
|
|
int i;
|
|
if((i = display.lastIndexOf('.')) > display.lastIndexOf(':') && i >= 0)
|
|
display.truncate(i);
|
|
|
|
display.replace(':','_');
|
|
// WARNING, if you change the socket name, adjust kwrapper too
|
|
const QString socketFileName = QString::fromLatin1("kdeinit4_%1").arg(QLatin1String(display));
|
|
QByteArray socketName = QFile::encodeName(KStandardDirs::locateLocal("tmp", socketFileName, *s_instance));
|
|
if (socketName.length() >= MAX_SOCK_FILE)
|
|
{
|
|
fprintf(stderr, "kdeinit4: Aborting. Socket name will be too long:\n");
|
|
fprintf(stderr, " '%s'\n", socketName.data());
|
|
exit(255);
|
|
}
|
|
strcpy(sock_file, socketName.data());
|
|
}
|
|
|
|
int kdeinit_xio_errhandler( Display *disp )
|
|
{
|
|
// disp is 0L when KDE shuts down. We don't want those warnings then.
|
|
if ( disp )
|
|
kWarning() << "Fatal IO error: client killed";
|
|
|
|
if (sock_file[0])
|
|
{
|
|
// Delete any stale socket file
|
|
unlink(sock_file);
|
|
}
|
|
|
|
// Don't kill our children in suicide mode, they may still be in use
|
|
if (d.suicide)
|
|
{
|
|
if (d.launcher_pid)
|
|
kill(d.launcher_pid, SIGTERM);
|
|
exit( 0 );
|
|
}
|
|
|
|
if ( disp )
|
|
kWarning() << "Sending SIGHUP to children.";
|
|
|
|
// this should remove all children we started
|
|
KDE_signal(SIGHUP, SIG_IGN);
|
|
kill(0, SIGHUP);
|
|
|
|
sleep(2);
|
|
|
|
if ( disp )
|
|
kWarning() << "Sending SIGTERM to children.";
|
|
|
|
// And if they don't listen to us, this should work
|
|
KDE_signal(SIGTERM, SIG_IGN);
|
|
kill(0, SIGTERM);
|
|
|
|
if ( disp )
|
|
kWarning() << "Exit.";
|
|
|
|
exit( 0 );
|
|
return 0;
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
int kdeinit_x_errhandler( Display *dpy, XErrorEvent *err )
|
|
{
|
|
#ifndef NDEBUG
|
|
char errstr[256];
|
|
// kdeinit almost doesn't use X, and therefore there shouldn't be any X error
|
|
XGetErrorText( dpy, err->error_code, errstr, 256 );
|
|
fprintf(stderr, "kdeinit4(%d) : KDE detected X Error: %s %d\n"
|
|
" Major opcode: %d\n"
|
|
" Minor opcode: %d\n"
|
|
" Resource id: 0x%lx\n",
|
|
getpid(), errstr, err->error_code, err->request_code, err->minor_code, err->resourceid );
|
|
|
|
//kDebug() << kBacktrace();
|
|
|
|
#else
|
|
Q_UNUSED(dpy);
|
|
Q_UNUSED(err);
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef Q_WS_X11
|
|
// needs to be done sooner than initXconnection() because of also opening
|
|
// another X connection for startup notification purposes
|
|
static void setupX()
|
|
{
|
|
XSetIOErrorHandler(kdeinit_xio_errhandler);
|
|
XSetErrorHandler(kdeinit_x_errhandler);
|
|
/*
|
|
Handle the tricky case of running via kdesudo/su/sudo/etc. There the usual case
|
|
is that kdesudo (etc.) creates a file with xauth information, sets XAUTHORITY,
|
|
runs the command and removes the xauth file after the command finishes. However,
|
|
dbus and kdeinit daemon currently don't clean up properly and keeping running.
|
|
Which means that running a KDE app via kdesudo the second time talks to kdeinit
|
|
with obsolete xauth information, which makes it unable to connect to X or launch
|
|
any X11 applications.
|
|
Even fixing the cleanup probably wouldn't be sufficient, since it'd be possible to
|
|
launch one kdesudo session, another one, exit the first one and the app from the second
|
|
session would be using kdeinit from the first one.
|
|
So the trick here is to duplicate the xauth file to another file in KDE's tmp
|
|
location, make the file have a consistent name so that future sessions will use it
|
|
as well, point XAUTHORITY there and never remove the file (except for possible
|
|
tmp cleanup).
|
|
*/
|
|
if( !qgetenv( "XAUTHORITY" ).isEmpty()) {
|
|
QByteArray display = qgetenv( DISPLAY );
|
|
int i;
|
|
if((i = display.lastIndexOf('.')) > display.lastIndexOf(':') && i >= 0)
|
|
display.truncate(i);
|
|
display.replace(':','_');
|
|
QString xauth = s_instance->dirs()->saveLocation( "tmp" ) + QLatin1String( "xauth-" )
|
|
+ QString::number( getuid()) + QLatin1String( "-" ) + QString::fromLocal8Bit( display );
|
|
KSaveFile xauthfile( xauth );
|
|
QFile xauthfrom( QFile::decodeName( qgetenv( "XAUTHORITY" )));
|
|
if( !xauthfrom.open( QFile::ReadOnly ) || !xauthfile.open( QFile::WriteOnly )
|
|
|| xauthfile.write( xauthfrom.readAll()) != xauthfrom.size() || !xauthfile.finalize()) {
|
|
xauthfile.abort();
|
|
} else {
|
|
setenv( "XAUTHORITY", QFile::encodeName( xauth ), true );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Borrowed from kdebase/kaudio/kaudioserver.cpp
|
|
static int initXconnection()
|
|
{
|
|
X11display = XOpenDisplay(NULL);
|
|
if ( X11display != 0 ) {
|
|
XCreateSimpleWindow(X11display, DefaultRootWindow(X11display), 0,0,1,1, \
|
|
0,
|
|
BlackPixelOfScreen(DefaultScreenOfDisplay(X11display)),
|
|
BlackPixelOfScreen(DefaultScreenOfDisplay(X11display)) );
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: opened connection to %s\n", DisplayString(X11display));
|
|
#endif
|
|
int fd = XConnectionNumber( X11display );
|
|
int on = 1;
|
|
(void) setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, (int) sizeof(on));
|
|
return fd;
|
|
} else
|
|
fprintf(stderr, "kdeinit4: Can not connect to the X Server.\n" \
|
|
"kdeinit4: Might not terminate at end of session.\n");
|
|
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
extern "C" {
|
|
|
|
static void secondary_child_handler(int)
|
|
{
|
|
waitpid(-1, 0, WNOHANG);
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
setlocale (LC_ALL, "");
|
|
setlocale (LC_NUMERIC, "C");
|
|
|
|
pid_t pid;
|
|
bool do_fork = true;
|
|
int launch_klauncher = 1;
|
|
int do_shutdown = 0;
|
|
d.suicide = false;
|
|
|
|
// Save arguments first...
|
|
char **safe_argv = (char **) malloc( sizeof(char *) * argc);
|
|
for(int i = 0; i < argc; i++)
|
|
{
|
|
safe_argv[i] = strcpy((char*)malloc(strlen(argv[i])+1), argv[i]);
|
|
if (strcmp(safe_argv[i], "--no-klauncher") == 0)
|
|
launch_klauncher = 0;
|
|
if (strcmp(safe_argv[i], "--no-fork") == 0)
|
|
do_fork = false;
|
|
if (strcmp(safe_argv[i], "--suicide") == 0)
|
|
d.suicide = true;
|
|
if (strcmp(safe_argv[i], "--shutdown") == 0)
|
|
do_shutdown = 1;
|
|
if (strcmp(safe_argv[i], "--version") == 0)
|
|
{
|
|
printf("Katie: %s\n", qVersion());
|
|
printf("KDE: %s\n", KDE_VERSION_STRING);
|
|
exit(0);
|
|
}
|
|
if (strcmp(safe_argv[i], "--help") == 0)
|
|
{
|
|
printf("Usage: kdeinit4 [options]\n");
|
|
printf(" --no-fork Do not fork\n");
|
|
printf(" --no-klauncher Do not start klauncher\n");
|
|
printf(" --suicide Terminate when no KDE applications are left running\n");
|
|
printf(" --version Show version information\n");
|
|
printf(" --shutdown Shutdown running kdeinit4 instance\n");
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
if (do_shutdown) {
|
|
KDE_signal(SIGPIPE, SIG_IGN);
|
|
s_instance = new KComponentData("kdeinit4", QByteArray(), KComponentData::SkipMainComponentRegistration);
|
|
kdeinit_library_path();
|
|
shutdown_kdeinit();
|
|
return 0;
|
|
}
|
|
|
|
cleanup_fds();
|
|
|
|
// Redirect stdout to stderr. We have no reason to use stdout anyway.
|
|
// This minimizes our impact on commands used in pipes.
|
|
(void)dup2(2, 1);
|
|
|
|
if (do_fork) {
|
|
if (pipe(d.initpipe) != 0) {
|
|
perror("kdeinit4: pipe failed");
|
|
return 1;
|
|
}
|
|
|
|
// Fork here and let parent process exit.
|
|
// Parent process may only exit after all required services have been
|
|
// launched. (dcopserver/klauncher and services which start with '+')
|
|
KDE_signal( SIGCHLD, secondary_child_handler);
|
|
if (fork() > 0) // Go into background
|
|
{
|
|
close(d.initpipe[1]);
|
|
d.initpipe[1] = -1;
|
|
// wait till init is complete
|
|
char c;
|
|
while( read(d.initpipe[0], &c, 1) < 0)
|
|
;
|
|
// then exit;
|
|
close(d.initpipe[0]);
|
|
d.initpipe[0] = -1;
|
|
return 0;
|
|
}
|
|
close(d.initpipe[0]);
|
|
d.initpipe[0] = -1;
|
|
}
|
|
|
|
// Make process group leader (for shutting down children later)
|
|
setsid();
|
|
|
|
// Create our instance
|
|
s_instance = new KComponentData("kdeinit4", QByteArray(), KComponentData::SkipMainComponentRegistration);
|
|
|
|
// Prepare to change process name
|
|
#ifndef SKIP_PROCTITLE
|
|
proctitle_init(argc, argv);
|
|
#endif
|
|
|
|
kdeinit_library_path();
|
|
// Don't make our instance the global instance
|
|
// (do it only after kdeinit_library_path, that one indirectly uses KConfig,
|
|
// which seems to be buggy and always use KGlobal instead of the matching KComponentData)
|
|
Q_ASSERT(!KGlobal::hasMainComponent());
|
|
// don't change envvars before proctitle_init()
|
|
unsetenv("LD_BIND_NOW");
|
|
KApplication::loadedByKdeinit = true;
|
|
|
|
d.maxname = strlen(argv[0]);
|
|
d.launcher_pid = 0;
|
|
d.wrapper = -1;
|
|
d.accepted_fd = -1;
|
|
d.debug_wait = false;
|
|
d.launcher_ok = false;
|
|
children = NULL;
|
|
init_signals();
|
|
#ifdef Q_WS_X11
|
|
setupX();
|
|
#endif
|
|
|
|
// Create socket for incoming wrapper requests
|
|
init_kdeinit_socket();
|
|
|
|
if (launch_klauncher)
|
|
{
|
|
start_klauncher();
|
|
handle_requests(d.launcher_pid); // Wait for klauncher to be ready
|
|
}
|
|
|
|
#ifdef Q_WS_X11
|
|
X11fd = initXconnection();
|
|
#endif
|
|
|
|
for(int i = 1; i < argc; i++)
|
|
{
|
|
if (safe_argv[i][0] == '+')
|
|
{
|
|
pid = launch( 1, safe_argv[i]+1, 0);
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Launched '%s', pid = %ld result = %d\n", safe_argv[i]+1, (long) pid, d.result);
|
|
#endif
|
|
handle_requests(pid);
|
|
}
|
|
else if (safe_argv[i][0] == '-')
|
|
{
|
|
// Ignore
|
|
}
|
|
else
|
|
{
|
|
pid = launch( 1, safe_argv[i], 0 );
|
|
#ifndef NDEBUG
|
|
fprintf(stderr, "kdeinit4: Launched '%s', pid = %ld result = %d\n", safe_argv[i], (long) pid, d.result);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Free argumentsf
|
|
for(int i = 0; i < argc; i++)
|
|
{
|
|
free(safe_argv[i]);
|
|
}
|
|
free (safe_argv);
|
|
|
|
#ifndef SKIP_PROCTITLE
|
|
proctitle_set("kdeinit4 Running...");
|
|
#endif
|
|
|
|
if (d.initpipe[1] != -1)
|
|
{
|
|
char c = 0;
|
|
write(d.initpipe[1], &c, 1); // Kdeinit is started.
|
|
close(d.initpipe[1]);
|
|
d.initpipe[1] = -1;
|
|
}
|
|
|
|
handle_requests(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|