/*
 * 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.
 */

#include <config.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>		// Needed on some systems.
#endif

#include <errno.h>
#include <fcntl.h>
#include "proctitle.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>

#include <QtCore/QLibrary>
#include <QtCore/QString>
#include <QtCore/QFile>
#include <QtCore/QDate>
#include <QtCore/QFileInfo>
#include <QtCore/QTextStream>
#include <QtCore/QRegExp>
#include <QtGui/QFont>
#include <QDir>
#include <kcomponentdata.h>
#include <kstandarddirs.h>
#include <kconfiggroup.h>
#include <kglobal.h>
#include <kconfig.h>
#include <klibloader.h>
#include <kapplication.h>
#include <klocale.h>
#include <kdebug.h>
#include <kde_file.h>

#ifdef Q_OS_LINUX
#include <sys/prctl.h>
#ifndef PR_SET_NAME
#define PR_SET_NAME 15
#endif
#endif

#ifdef Q_WS_MACX
#include <kkernel_mac.h>
#endif

#include <kdeversion.h>

#include "klauncher_cmds.h"

#ifdef Q_WS_X11
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <fixx11h.h>
#include <kstartupinfo.h>
#endif

#if KDE_IS_VERSION( 3, 90, 0 )
#ifdef __GNUC__
#warning Check if Linux OOM-killer still sucks and if yes, forwardport revision 579164 and following fixes.
#endif
#endif

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];
//static char sock_file_old[MAX_SOCK_FILE];

#ifdef Q_WS_X11
#define DISPLAY "DISPLAY"
#elif defined(Q_WS_QWS)
#define DISPLAY "QWS_DISPLAY"
#elif defined(Q_WS_MACX)
#define DISPLAY "MAC_DISPLAY"
#elif defined(Q_WS_WIN)
#define DISPLAY "WIN_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 wrapper_old; /* old 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;
  pid_t kded_pid;
  pid_t my_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

/* These are to link libkparts even if 'smart' linker is used */
#include <kparts/plugin.h>
extern "C" KParts::Plugin* _kinit_init_kparts() { return new KParts::Plugin(); }
/* These are to link libkio even if 'smart' linker is used */
#include <kio/authinfo.h>
extern "C" KIO::AuthInfo* _kioslave_init_kio() { return new KIO::AuthInfo(); }

/*
 * 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()
{
    for (int fd = 3; fd < FD_SETSIZE; ++fd)
       close(fd);
}

/*
 * Close fd's which are only useful for the parent process.
 * Restore default signal handlers.
 */
static void close_fds()
{
   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_pid)
   {
      close(d.launcher[0]);
      d.launcher_pid = 0;
   }
   if (d.wrapper != -1)
   {
      close(d.wrapper);
      d.wrapper = -1;
   }
   if (d.wrapper_old != -1)
   {
      close(d.wrapper_old);
      d.wrapper_old = -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 = children;
   struct child *prev = NULL;

   while (child)
   {
      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_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);

         if (prev)
         {
            prev->next = child->next;
         }
         else
         {
            child = NULL;
         }
         free(child);
         return;
      }

      prev = child;
      child = 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() tty" );
        close( fd );
        return;
    }
    if( dup2( fd, STDERR_FILENO ) < 0 )
    {
        perror( "kdeinit4: could not dup2() tty" );
        close( fd );
        return;
    }
    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( 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;
     if( envc > 0 ) /* use the passed environment */
     {
         const char* path = get_env_var( "PATH=", envc, envs );
         if( path != NULL )
             paths = QString(path).split( QRegExp( "[:\b]" ));
     }
     else
         paths = QString::fromLocal8Bit( qgetenv("PATH") ).split( QRegExp( "[:\b]" ), QString::KeepEmptyParts );
     QByteArray execpath = QFile::encodeName(
         s_instance->dirs()->findExe( exec, paths.join( QLatin1String( ":" ))));
     if( avoid_loops && !execpath.isEmpty())
     {
         int pos = execpath.lastIndexOf( '/' );
         QString bin_path = execpath.left( pos );
         for( QStringList::Iterator it = paths.begin();
              it != paths.end();
              ++it )
             if( ( *it ) == bin_path || ( *it ) == bin_path + '/' )
             {
                 paths.erase( it );
                 break; // -->
             }
         execpath = QFile::encodeName(
             s_instance->dirs()->findExe( exec, paths.join( QString( ":" ))));
     }
     return 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" )
{
  int launcher = 0;
  QByteArray lib;
  QByteArray name;
  QByteArray exec;

  if (strcmp(_name, "klauncher") == 0) {
     /* klauncher is launched in a special way:
      * It has a communication socket on LAUNCHER_FD
      */
     if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, d.launcher))
     {
        perror("kdeinit4: socketpair() failed!\n");
        exit(255);
     }
     launcher = 1;
  }

  QByteArray libpath;
  QByteArray execpath;
  if (_name[0] != '/')
  {
     lib = name = _name;
     exec = name;
     libpath = QFile::encodeName(KLibLoader::findLibrary(lib.constData(), *s_instance));
     execpath = execpath_avoid_loops( exec, envc, envs, avoid_loops );
  }
  else
  {
     lib = _name;
     name = _name;
     name = name.mid( name.lastIndexOf('/') + 1);
     exec = _name;
     if (lib.endsWith(".la"))
        libpath = lib;
     else
        execpath = exec;
  }
  fprintf(stderr,"kdeinit4: preparing to launch %s\n", execpath.constData());
  if (!args)
  {
    argc = 1;
  }

  if (0 > pipe(d.fd))
  {
     perror("kdeinit4: pipe() failed!\n");
     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

  d.errorMsg = 0;
  d.fork = fork();
  switch(d.fork) {
  case -1:
     perror("kdeinit4: fork() failed!\n");
     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();
     if (launcher)
     {
        if (d.fd[1] == LAUNCHER_FD)
        {
          d.fd[1] = dup(d.fd[1]); // Evacuate from LAUNCHER_FD
        }
        if (d.launcher[1] != LAUNCHER_FD)
        {
          dup2( d.launcher[1], LAUNCHER_FD); // Make sure the socket has fd LAUNCHER_FD
          close( d.launcher[1] );
        }
        close( d.launcher[0] );
     }

     if (cwd && *cwd)
        chdir(cwd);
     else {
         // Can't use KGlobalSettings::documentPath() here, we don't have a main component set,
         // and we don't want to set one; e.g. kioslaves will do that.
         // So we have to duplicate this stuff from KGlobalSettings.
         KConfigGroup g(s_instance->config(), "Paths");
         const QString documentPath = g.readPathEntry("Documents",
#ifdef Q_WS_WIN
                                                       getWin32ShellFoldersPath("Personal")
#else
                                                       QDir::homePath());
#endif
         const QByteArray docPath = QFile::encodeName(documentPath);
         chdir(docPath.constData());
     }

     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;
#ifdef Q_WS_MAC
       QString argvexe = s_instance->dirs()->findExe(QString::fromLatin1(d.argv[0]));
       if (!argvexe.isEmpty()) {
          QByteArray cstr = argvexe.toLocal8Bit();
          kDebug(7016) << "kdeinit4: launch() setting argv: " << cstr.data();
          d.argv[0] = strdup(cstr.data());
       }
#endif
       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;

       /** 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
     }

     if (libpath.isEmpty() && execpath.isEmpty())
     {
        QString errorMsg = i18n("Could not find '%1' executable.", QFile::decodeName(_name));
        exitWithErrorMsg(errorMsg);
     }


     if ( !qgetenv("KDE_IS_PRELINKED").isEmpty() && !execpath.isEmpty() && !launcher)
         libpath.truncate(0);

     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", QFile::decodeName(libpath), ltdlError);
             exitWithErrorMsg(errorMsg);
          }
          else
          {
             // Print warning
             fprintf(stderr, "Could not open library %s: %s\n", lib.data(),
                     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 );

        QByteArray executable = execpath.data();
#ifdef Q_WS_MAC
        QString bundlepath = s_instance->dirs()->findExe( execpath.data() );
        if (!bundlepath.isEmpty())
           executable = QFile::encodeName(bundlepath);
#endif

        if (!executable.isEmpty())
           execvp(executable, d.argv);

        d.result = 1; // Error
        write(d.fd[1], &d.result, 1);
        close(d.fd[1]);
        exit(255);
     }

     void * sym = l.resolve( "kdeinitmain");
     if (!sym )
        {
        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",
                    QLatin1String(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]);
     if (launcher)
     {
        close(d.launcher[1]);
        d.launcher_pid = d.fork;
     }
     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 (exec)
       {
          d.result = 0;
          break;
       }
       if (d.n == 0)
       {
          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]);
     if (launcher && (d.result == 0))
     {
        // Trader launched successful
        d.launcher_pid = d.fork;
     }
  }
#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;
}

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 init_kdeinit_socket()
{
  struct sockaddr_un sa;
  //struct sockaddr_un sa_old;
  kde_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);
  }
  chdir(home_dir);

  {
     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
  }

  /** 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);
//  unlink(sock_file_old);

  /** 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);
  }

  if (fcntl(d.wrapper, F_SETFD, FD_CLOEXEC) == -1)
  {
     perror("kdeinit4: Aborting. Can not make socket close-on-execute: ");
     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);
  }

#if 0
  /** create compatibility socket **/
  d.wrapper_old = socket(PF_UNIX, SOCK_STREAM, 0);
  if (d.wrapper_old < 0)
  {
     // perror("kdeinit4: Aborting. socket() failed: ");
     return;
  }

  options = fcntl(d.wrapper_old, F_GETFL);
  if (options == -1)
  {
     // perror("kdeinit4: Aborting. Can't make socket non-blocking: ");
     close(d.wrapper_old);
     d.wrapper_old = -1;
     return;
  }

  if (fcntl(d.wrapper_old, F_SETFL, options | O_NONBLOCK) == -1)
  {
     // perror("kdeinit4: Aborting. Can't make socket non-blocking: ");
     close(d.wrapper_old);
     d.wrapper_old = -1;
     return;
  }

  if (fcntl(d.wrapper, F_SETFD, FD_CLOEXEC) == -1)
  {
     //perror("kdeinit4: Aborting. Can't make socket close-on-execute: ");
     close(d.wrapper);
     d.wrapper_old = -1;
     return;
  }

  max_tries = 10;
  while (1) {
      /** bind it **/
      socklen = sizeof(sa_old);
      memset(&sa_old, 0, socklen);
      sa_old.sun_family = AF_UNIX;
      strcpy(sa_old.sun_path, sock_file_old);
      if(bind(d.wrapper_old, (struct sockaddr *)&sa_old, socklen) != 0)
      {
          if (max_tries == 0) {
	      // perror("kdeinit4: Aborting. bind() failed: ");
	      fprintf(stderr, "Could not bind to socket '%s'\n", sock_file_old);
	      close(d.wrapper_old);
	      d.wrapper_old = -1;
	      return;
	  }
	  max_tries--;
      } else
          break;
  }

  /** set permissions **/
  if (chmod(sock_file_old, 0600) != 0)
  {
     fprintf(stderr, "Wrong permissions of socket '%s'\n", sock_file);
     unlink(sock_file_old);
     close(d.wrapper_old);
     d.wrapper_old = -1;
     return;
  }

  if(listen(d.wrapper_old, SOMAXCONN) < 0)
  {
     // perror("kdeinit4: Aborting. listen() failed: ");
     unlink(sock_file_old);
     close(d.wrapper_old);
     d.wrapper_old = -1;
  }
#endif
}

/*
 * 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 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;

   pid_t pid = launch( 1, "klauncher", 0 );
#ifndef NDEBUG
   fprintf(stderr, "kdeinit4: Relaunching KLauncher, pid = %ld result = %d\n", (long) pid, d.result);
#endif
}

static void handle_launcher_request(int sock = -1)
{
   bool launcher = false;
   if (sock < 0)
   {
       sock = d.launcher[0];
       launcher = true;
   }
   else
   {
       d.accepted_fd = sock;
   }

   klauncher_header request_header;
   char *request_data = 0L;
   int result = read_socket(sock, (char *) &request_header, sizeof(request_header));
   if (result != 0)
   {
      if (launcher)
         launcher_died();
      return;
   }

   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)
       {
           if (launcher)
               launcher_died();
           free(request_data);
           return;
       }
   }

   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_SHELL ) ||
       (request_header.cmd == LAUNCHER_KWRAPPER) ||
       (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";

#ifndef NDEBUG
     fprintf(stderr, "kdeinit4: Got %s '%s' from %s.\n",
        (request_header.cmd == LAUNCHER_EXEC ? "EXEC" :
        (request_header.cmd == LAUNCHER_EXT_EXEC ? "EXT_EXEC" :
        (request_header.cmd == LAUNCHER_EXEC_NEW ? "EXEC_NEW" :
        (request_header.cmd == LAUNCHER_SHELL ? "SHELL" : "KWRAPPER" )))),
         name, launcher ? "launcher" : "socket" );
#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_SHELL || request_header.cmd == LAUNCHER_KWRAPPER )
      {
         // Shell or kwrapper
         cwd = arg_n; arg_n += strlen(cwd) + 1;
      }
      if( request_header.cmd == LAUNCHER_SHELL || request_header.cmd == LAUNCHER_KWRAPPER
          || 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;
         }
         if( request_header.cmd == LAUNCHER_KWRAPPER )
         {
             tty = arg_n;
             arg_n += strlen( tty ) + 1;
         }
      }

     if( request_header.cmd == LAUNCHER_SHELL || request_header.cmd == LAUNCHER_KWRAPPER
         || request_header.cmd == LAUNCHER_EXT_EXEC || request_header.cmd == LAUNCHER_EXEC_NEW )
     {
         memcpy( &l, arg_n, sizeof( long ));
         avoid_loops = l;
         arg_n += sizeof( long );
     }

     if( request_header.cmd == LAUNCHER_SHELL || request_header.cmd == LAUNCHER_KWRAPPER
         || 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;
     }

      // support for the old a bit broken way of setting DISPLAY for multihead
      QByteArray olddisplay = qgetenv(DISPLAY);
      QByteArray kdedisplay = qgetenv("KDE_DISPLAY");
      bool reset_display = (! olddisplay.isEmpty() &&
                            ! kdedisplay.isEmpty() &&
                            olddisplay != kdedisplay);

      if (reset_display)
          setenv(DISPLAY, kdedisplay, true);

      pid = launch( argc, name, args, cwd, envc, envs,
          request_header.cmd == LAUNCHER_SHELL || request_header.cmd == LAUNCHER_KWRAPPER,
          tty, avoid_loops, startup_id_str );

      if (reset_display) {
          unsetenv("KDE_DISPLAY");
          setenv(DISPLAY, olddisplay, true);
      }

      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;

         /* check child hasn't terminated already */
         if (kill(pid, 0))
         {
            children = child->next;
            free(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
      if (launcher)
         fprintf(stderr, "kdeinit4: Got SETENV '%s=%s' from klauncher.\n", env_name, env_value);
      else
         fprintf(stderr, "kdeinit4: Got SETENV '%s=%s' from socket.\n", env_name, env_value);
#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;
      }
      setenv( env_name, env_value, 1);
   }
   else if (request_header.cmd == LAUNCHER_TERMINATE_KDE)
   {
#ifndef NDEBUG
       fprintf(stderr,"kdeinit4: terminate KDE.\n");
#endif
#ifdef Q_WS_X11
       kdeinit_xio_errhandler( 0L );
#endif
   }
   else if (request_header.cmd == LAUNCHER_TERMINATE_KDEINIT)
   {
#ifndef NDEBUG
       fprintf(stderr,"kdeinit4: Killing kdeinit/klauncher.\n");
#endif
       if (d.launcher_pid)
          kill(d.launcher_pid, SIGTERM);
       if (d.my_pid)
          kill(d.my_pid, 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);
}

static void handle_requests(pid_t waitForPid)
{
   int max_sock = d.wrapper;
   if (d.wrapper_old > max_sock)
      max_sock = d.wrapper_old;
   if (d.launcher_pid && (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);
        }
      }
      while( exit_pid > 0);

      /* Set up the next loop */
      d.accepted_fd = -1;

      FD_ZERO(&rd_set);
      FD_ZERO(&wr_set);
      FD_ZERO(&e_set);

      if (d.launcher_pid)
      {
         FD_SET(d.launcher[0], &rd_set);
      }
      FD_SET(d.wrapper, &rd_set);
      if (d.wrapper_old != -1)
      {
         FD_SET(d.wrapper_old, &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);

      /* Handle wrapper request */
      if ((result > 0) && (FD_ISSET(d.wrapper, &rd_set)))
      {
         struct sockaddr_un client;
         kde_socklen_t sClient = sizeof(client);
         int sock = accept(d.wrapper, (struct sockaddr *)&client, &sClient);
         if (sock >= 0)
         {
            handle_launcher_request(sock);
            close(sock);
         }
      }
      if ((result > 0) && d.wrapper_old != -1 && (FD_ISSET(d.wrapper_old, &rd_set)))
      {
         struct sockaddr_un client;
         kde_socklen_t sClient = sizeof(client);
         int sock = accept(d.wrapper_old, (struct sockaddr *)&client, &sClient);
         if (sock >= 0)
         {
            handle_launcher_request(sock);
            close(sock);
         }
      }

      /* Handle launcher request */
      if ((result > 0) && (d.launcher_pid) && (FD_ISSET(d.launcher[0], &rd_set)))
      {
         handle_launcher_request();
         if (waitForPid == d.launcher_pid)
            return;
      }

#ifdef Q_WS_X11
      /* Look for incoming X11 events */
      if((result > 0) && (X11fd >= 0))
      {
        if(FD_ISSET(X11fd,&rd_set))
        {
          if (X11display != 0) {
	    XEvent event_return;
	    while (XPending(X11display))
	      XNextEvent(X11display, &event_return);
	  }
        }
      }
#endif
   }
}

static void kdeinit_library_path()
{
   QStringList ltdl_library_path =
     QFile::decodeName(qgetenv("LTDL_LIBRARY_PATH")).split(':',QString::SkipEmptyParts);
#ifdef Q_OS_DARWIN
   QStringList ld_library_path =
     QFile::decodeName(qgetenv("DYLD_LIBRARY_PATH")).split(':',QString::SkipEmptyParts);
#else
   QStringList ld_library_path =
     QFile::decodeName(qgetenv("LD_LIBRARY_PATH")).split(':',QString::SkipEmptyParts);
#endif

   QByteArray extra_path;
   QStringList candidates = s_instance->dirs()->resourceDirs("lib");
   for (QStringList::ConstIterator it = candidates.begin();
        it != candidates.end();
        ++it)
   {
      QString d = *it;
      if (ltdl_library_path.contains(d))
          continue;
      if (ld_library_path.contains(d))
          continue;
      if (d[d.length()-1] == '/')
      {
         d.truncate(d.length()-1);
         if (ltdl_library_path.contains(d))
            continue;
         if (ld_library_path.contains(d))
            continue;
      }
      if ((d == "/lib") || (d == "/usr/lib"))
         continue;

      QByteArray dir = QFile::encodeName(d);

      if (access(dir, R_OK))
          continue;

      if ( !extra_path.isEmpty())
         extra_path += ':';
      extra_path += dir;
   }

//   if (!extra_path.isEmpty())
//      lt_dlsetsearchpath(extra_path.data());

   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);

   // Was kdeinit-display initially,
   // then at some point in KDE-3.x it became kdeinit_display_with_underscores
   // And for KDE4 it became kdeinit4_display_with_underscores, to avoid messing up the kde3 kdeinit.
   // Compat code needed just in case we need it later for something else.
#if 0
   QByteArray socketName = QFile::encodeName(locateLocal("socket", QString("kdeinit-%1").arg(QLatin1String(display)), *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_old, socketName.data());
#endif

   display.replace(':','_');
   // WARNING, if you change the socket name, adjust kwrapper too
   QByteArray socketName = QFile::encodeName(KStandardDirs::locateLocal("socket", QString("kdeinit4_%1").arg(QLatin1String(display)), *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 )
    qWarning( "kdeinit4: Fatal IO error: client killed" );

    if (sock_file[0])
    {
      /** Delete any stale socket file **/
      unlink(sock_file);
    }
#if 0
    if (sock_file_old[0])
    {
      /** Delete any stale socket file **/
      unlink(sock_file_old);
    }
#endif

    // 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);
       if (d.kded_pid)
          kill(d.kded_pid, SIGTERM);
       exit( 0 );
    }

    if ( disp )
    qWarning( "kdeinit4: sending SIGHUP to children." );

    /* this should remove all children we started */
    KDE_signal(SIGHUP, SIG_IGN);
    kill(0, SIGHUP);

    sleep(2);

    if ( disp )
    qWarning( "kdeinit4: 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 )
    qWarning( "kdeinit4: 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: KDE detected X Error: %s %d\n"
                    "         Major opcode: %d\n"
                    "         Minor opcode: %d\n"
                    "         Resource id:  0x%lx\n",
            errstr, err->error_code, err->request_code, err->minor_code, err->resourceid );
#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);
}

// 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

#ifdef __KCC
/* One of my horrible hacks.  KCC includes in each "main" function a call
   to _main(), which is provided by the C++ runtime system.  It is
   responsible for calling constructors for some static objects.  That must
   be done only once, so _main() is guarded against multiple calls.
   For unknown reasons the designers of KAI's libKCC decided it would be
   a good idea to actually abort() when it's called multiple times, instead
   of ignoring further calls.  This breaks our mechanism of KLM's, because
   most KLM's have a main() function which is called from us.
   The "solution" is to simply define our own _main(), which ignores multiple
   calls, which is easy, and which does the same work as KAI'c _main(),
   which is difficult.  Currently (KAI 4.0f) it only calls __call_ctors(void)
   (a C++ function), but if that changes we need to change our's too.
   (matz) */
/*
 Those 'unknown reasons' are C++ standard forbidding recursive calls to main()
 or any means that would possibly allow that (e.g. taking address of main()).
 The correct solution is not using main() as entry point for kdeinit modules,
 but only kdemain().
*/
extern "C" void _main(void);
extern "C" void __call_ctors__Fv(void);
static int main_called = 0;
void _main(void)
{
  if (main_called)
    return;
  main_called = 1;
  __call_ctors__Fv ();
}
#endif

static void secondary_child_handler(int)
{
   waitpid(-1, 0, WNOHANG);
}

int main(int argc, char **argv, char **envp)
{
   int i;
   pid_t pid;
   bool do_fork = true;
   int launch_klauncher = 1;
   int launch_kded = 1;
   int keep_running = 1;
   d.suicide = false;

   /** Save arguments first... **/
   char **safe_argv = (char **) malloc( sizeof(char *) * argc);
   for(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-kded") == 0)
         launch_kded = 0;
#ifdef Q_WS_MACX
      // make it nofork to match KUniqueApplication, technically command-line incompatible
      if (strcmp(safe_argv[i], "--nofork") == 0)
#else
      if (strcmp(safe_argv[i], "--no-fork") == 0)
#endif
         do_fork = false;
      if (strcmp(safe_argv[i], "--suicide") == 0)
         d.suicide = true;
      if (strcmp(safe_argv[i], "--exit") == 0)
         keep_running = 0;
      if (strcmp(safe_argv[i], "--help") == 0)
      {
        printf("Usage: kdeinit4 [options]\n");
     // printf("    --no-dcop         Do not start dcopserver\n");
#ifdef Q_WS_MACX
        printf("    --nofork          Do not fork\n");
#else
        printf("    --no-fork         Do not fork\n");
#endif
     // printf("    --no-klauncher    Do not start klauncher\n");
        printf("    --no-kded         Do not start kded\n");
        printf("    --suicide         Terminate when no KDE applications are left running\n");
     // printf("    --exit            Terminate when kded has run\n");
        exit(0);
      }
   }

   cleanup_fds();

   if (do_fork) {
#ifdef Q_WS_MACX
      mac_fork_and_reexec_self();
#else
      pipe(d.initpipe);

      // 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;
#endif
   }

   /** We start a few processes (including klauncher and kded) before
    * we close our pipe, so make sure it doesn't leak */
   if (d.initpipe[1] != -1)
      fcntl(d.initpipe[1], F_SETFD, FD_CLOEXEC);

   d.my_pid = getpid();

   /** Make process group leader (for shutting down children later) **/
   if(keep_running)
      setsid();

   /** Create our instance **/
   s_instance = new KComponentData("kdeinit4", QByteArray(), KComponentData::SkipMainComponentRegistration);

   /** Prepare to change process name **/
   proctitle_init(argc, argv, envp);
   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");
   unsetenv("DYLD_BIND_AT_LAUNCH");
   KApplication::loadedByKdeinit = true;

   d.maxname = strlen(argv[0]);
   d.launcher_pid = 0;
   d.kded_pid = 0;
   d.wrapper = -1;
   d.wrapper_old = -1;
   d.accepted_fd = -1;
   d.debug_wait = false;
   d.launcher_ok = false;
   children = NULL;
   init_signals();
#ifdef Q_WS_X11
   setupX();
#endif

   if (keep_running)
   {
      /*
       * Create ~/.kde/tmp-<hostname>/kdeinit4-<display> socket for incoming wrapper
       * requests.
       */
      init_kdeinit_socket();
   }
#ifdef Q_WS_X11
   if (!d.suicide && qgetenv("KDE_IS_PRELINKED").isEmpty())
   {
       QString konq = KStandardDirs::locate("lib", "libkonq.so.5", *s_instance);
       // can't use KLibLoader here as it would unload the library
       // again
       if (!konq.isEmpty()) {
           QLibrary l(konq);
           l.setLoadHints(QLibrary::ExportExternalSymbolsHint);
           l.load();
       }
   }
#endif
   if (launch_klauncher)
   {
      pid = launch( 1, "klauncher", 0 );
#ifndef NDEBUG
      fprintf(stderr, "kdeinit4: Launched KLauncher, pid = %ld result = %d\n", (long) pid, d.result);
#endif
      handle_requests(pid); // Wait for klauncher to be ready
   }

#ifdef Q_WS_X11
   X11fd = initXconnection();
#endif

   {
      QFont::initialize();
      setlocale (LC_ALL, "");
      setlocale (LC_NUMERIC, "C");
#ifdef Q_WS_X11
      if (XSupportsLocale ())
      {
         // Similar to QApplication::create_xim()
	 // but we need to use our own display
	 XOpenIM (X11display, 0, 0, 0);
      }
#endif
   }

   if (launch_kded)
   {
      pid = launch( 1, KDED_EXENAME, 0 );
#ifndef NDEBUG
      fprintf(stderr, "kdeinit4: Launched KDED, pid = %ld result = %d\n", (long) pid, d.result);
#endif
      d.kded_pid = pid;
      handle_requests(pid);
   }

   for(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 arguments **/
   for(i = 0; i < argc; i++)
   {
      free(safe_argv[i]);
   }
   free (safe_argv);

   proctitle_set("kdeinit4 Running...");

   if (!keep_running)
      return 0;

   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;
}


