diff --git a/src/kdeinit/kinit.cpp b/src/kdeinit/kinit.cpp index 523b715..64a6ec4 100644 --- a/src/kdeinit/kinit.cpp +++ b/src/kdeinit/kinit.cpp @@ -1,1788 +1,1788 @@ /* * This file is part of the KDE libraries * Copyright (c) 1999-2000 Waldo Bastian * (c) 1999 Mario Weilguni * (c) 2001 Lubos Lunak * * 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 #include #include #include #include #include #include #include #if HAVE_SYS_SELECT_H #include // Needed on some systems. #endif #include #include #include #include "proctitle.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #ifndef PR_SET_NAME #define PR_SET_NAME 15 #endif #endif #include #include "klauncher_cmds.h" #if HAVE_X11 #include #include #include #endif #if HAVE_XCB #include #include #endif #include #include #include "kinit.h" #ifdef Q_OS_OSX #include "kinit_mac.h" #endif #ifdef Q_OS_UNIX //TODO: make sure what libraries we want here... static const char *extra_libs[] = { #ifdef Q_OS_OSX "libKF5KIOCore.5.dylib", "libKF5Parts.5.dylib", "libKF5Plasma.5.dylib" #else "libKF5KIOCore.so.5", "libKF5Parts.so.5", //#ifdef __KDE_HAVE_GCC_VISIBILITY // Removed for KF5, we'll see. "libKF5Plasma.so.5" //#endif #endif }; #endif // #define SKIP_PROCTITLE 1 namespace KCrash { extern KCRASH_EXPORT bool loadedByKdeinit; } extern char **environ; #if HAVE_X11 static int X11fd = -1; -static Display *X11display = 0; +static Display *X11display = nullptr; static int X11_startup_notify_fd = -1; #endif #if HAVE_XCB static xcb_connection_t *s_startup_notify_connection = Q_NULLPTR; static int s_startup_notify_screen = 0; #endif // Finds size of sun_path without allocating a sockaddr_un to do it. // Assumes sun_path is at end of sockaddr_un though #define MAX_SOCK_FILE (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un,sun_path)) static char sock_file[MAX_SOCK_FILE]; #if HAVE_X11 || HAVE_XCB static const char* displayEnvVarName_c() { // Can't use QGuiApplication::platformName() here, there is no app instance. return "DISPLAY"; } static inline QByteArray displayEnvVarName() { return QByteArray::fromRawData(displayEnvVarName_c(), strlen(displayEnvVarName_c())); } #endif static struct child *children; #if HAVE_X11 extern "C" { int kdeinit_xio_errhandler(Display *); int kdeinit_x_errhandler(Display *, XErrorEvent *err); } #endif #if KDEINIT_OOM_PROTECT static int oom_pipe = -1; #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) { #if KDEINIT_OOM_PROTECT if (fd != oom_pipe) #endif close(fd); } } /* * Close fd's which are only useful for the parent process. * Restore default signal handlers. */ 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; } #if HAVE_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 signal(SIGCHLD, SIG_DFL); 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; } } void setup_tty(const char *tty) { - if (tty == NULL || *tty == '\0') { + if (tty == nullptr || *tty == '\0') { return; } QFile f(QFile::decodeName(tty)); f.open(QFileDevice::WriteOnly); int fd = f.handle(); if (fd < 0) { perror("kdeinit5: could not open() tty"); return; } if (dup2(fd, STDOUT_FILENO) < 0) { perror("kdeinit5: could not dup2() stdout tty"); } if (dup2(fd, STDERR_FILENO) < 0) { perror("kdeinit5: could not dup2() stderr tty"); } close(fd); } // 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; + return nullptr; } #if HAVE_XCB static void init_startup_info(KStartupInfoId &id, const QByteArray &bin, int envc, const char *envs) { QByteArray envname = displayEnvVarName() + "="; const char *dpy = get_env_var(envname.constData(), envc, envs); // this may be called in a child, so it can't use display open using X11display // also needed for multihead s_startup_notify_connection = xcb_connect(dpy, &s_startup_notify_screen); if (!s_startup_notify_connection) { return; } if (xcb_connection_has_error(s_startup_notify_connection)) { xcb_disconnect(s_startup_notify_connection); s_startup_notify_connection = Q_NULLPTR; return; } X11_startup_notify_fd = xcb_get_file_descriptor(s_startup_notify_connection); NETRootInfo rootInfo(s_startup_notify_connection, NET::CurrentDesktop); KStartupInfoData data; data.setDesktop(rootInfo.currentDesktop(true)); data.setBin(QFile::decodeName(bin)); KStartupInfo::sendChangeXcb(s_startup_notify_connection, s_startup_notify_screen, id, data); xcb_flush(s_startup_notify_connection); } static void complete_startup_info(KStartupInfoId &id, pid_t pid) { if (!s_startup_notify_connection) { return; } if (pid == 0) { // failure KStartupInfo::sendFinishXcb(s_startup_notify_connection, s_startup_notify_screen, id); } else { KStartupInfoData data; data.addPid(pid); data.setHostname(); KStartupInfo::sendChangeXcb(s_startup_notify_connection, s_startup_notify_screen, id, data); } xcb_disconnect(s_startup_notify_connection); s_startup_notify_connection = Q_NULLPTR; s_startup_notify_screen = 0; 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(QStringLiteral("[:\b]")); if (envc > 0) { /* use the passed environment */ const char *path = get_env_var("PATH=", envc, envs); - if (path != NULL) { + if (path != nullptr) { paths = QFile::decodeName(path).split(pathSepRegExp); } } else { paths = QString::fromLocal8Bit(qgetenv("PATH")).split(pathSepRegExp, QString::KeepEmptyParts); paths.prepend(QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5)); } QString execpath = QStandardPaths::findExecutable(QFile::decodeName(exec), paths); 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 = QStandardPaths::findExecutable(QFile::decodeName(exec), paths); } return QFile::encodeName(execpath); } #if KDEINIT_OOM_PROTECT static void oom_protect_sighandler(int) { } void reset_oom_protect() { if (oom_pipe <= 0) { return; } struct sigaction act, oldact; act.sa_handler = oom_protect_sighandler; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGUSR1, &act, &oldact); sigset_t sigs, oldsigs; sigemptyset(&sigs); sigaddset(&sigs, SIGUSR1); sigprocmask(SIG_BLOCK, &sigs, &oldsigs); pid_t pid = getpid(); if (write(oom_pipe, &pid, sizeof(pid_t)) > 0) { struct timespec timeout; timeout.tv_sec = 1; timeout.tv_nsec = 0; do { - if (sigtimedwait(&sigs, NULL, &timeout) < 0) { // wait for the signal to come + if (sigtimedwait(&sigs, nullptr, &timeout) < 0) { // wait for the signal to come if (errno == EINTR) { // other signal continue; } else if (errno == EAGAIN) { fprintf(stderr, "Timed out during reset OOM protection: %d\n", pid); } else { fprintf(stderr, "Error during reset OOM protection: %d\n", pid); } } break; } while (true); } else { #ifndef NDEBUG fprintf(stderr, "Failed to reset OOM protection: %d\n", pid); #endif } - sigprocmask(SIG_SETMASK, &oldsigs, NULL); - sigaction(SIGUSR1, &oldact, NULL); + sigprocmask(SIG_SETMASK, &oldsigs, nullptr); + sigaction(SIGUSR1, &oldact, nullptr); close(oom_pipe); oom_pipe = -1; } #else void reset_oom_protect() { } #endif #ifndef Q_OS_OSX 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 pid_t launch(int argc, const char *_name, const char *args, - const char *cwd = 0, int envc = 0, const char *envs = 0, + const char *cwd = nullptr, int envc = 0, const char *envs = nullptr, bool reset_env = false, - const char *tty = 0, bool avoid_loops = false, + const char *tty = nullptr, bool avoid_loops = false, const char *startup_id_str = "0") // krazy:exclude=doublequote_chars { QString lib; QByteArray name; QString libpath; QByteArray execpath; bool libpath_relative = false; if (_name[0] != '/') { name = _name; lib = QFile::decodeName(name); libpath = QLatin1String("libkdeinit5_") + lib; libpath_relative = true; execpath = execpath_avoid_loops(name, envc, envs, avoid_loops); } else { name = _name; lib = QFile::decodeName(name); name = name.mid(name.lastIndexOf('/') + 1); // FIXME: this .so extension stuff is very Linux-specific if (lib.endsWith(QLatin1String(".so"))) { libpath = lib; } else { execpath = _name; // Try to match an absolute path to an executable binary (either in // bin/ or in libexec/) to a kdeinit module in the same prefix. // // Note that these *_INSTALL_DIR values should normally relative to // the install prefix, although this may not be the case if the user // has overridden them, and so this search is inherently fragile in // the face of unusual installation layouts. if (lib.contains(QLatin1String(KF5_LIBEXEC_INSTALL_DIR))) { libpath = QString(lib).replace(QLatin1String(KF5_LIBEXEC_INSTALL_DIR), QLatin1String(LIB_INSTALL_DIR "/libkdeinit5_")) + QLatin1String(".so"); } else if (lib.contains(QLatin1String("/bin/"))) { libpath = QString(lib).replace(QLatin1String("/bin/"), QLatin1String(LIB_INSTALL_DIR "/libkdeinit5_")) + QLatin1String(".so"); } // Don't confuse the user with "Could not load libkdeinit5_foo.so" if it doesn't exist if (!QFile::exists(libpath)) { libpath.clear(); } } } #ifndef NDEBUG fprintf(stderr, "kdeinit5: preparing to launch '%s'\n", libpath.isEmpty() ? execpath.constData() : libpath.toUtf8().constData()); #endif if (!args) { argc = 1; } if (0 > pipe(d.fd)) { perror("kdeinit5: 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; } #if HAVE_XCB 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.errorMsg = nullptr; d.fork = fork(); switch (d.fork) { case -1: perror("kdeinit5: 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(); reset_oom_protect(); // 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 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).constData()); } } } for (int i = 0; i < envc; i++) { putenv((char *)envs); while (*envs != 0) { envs++; } envs++; } #if HAVE_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; + d.argv[argc] = nullptr; #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 [kdeinit5]%s", name.data(), procTitle.data() ? procTitle.data() : ""); } else { proctitle_set("%s%s", name.data(), procTitle.data() ? procTitle.data() : ""); } #else proctitle_set("%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); } if (!qEnvironmentVariableIsEmpty("KDE_IS_PRELINKED") && !execpath.isEmpty()) { libpath.truncate(0); } QLibrary l(libpath); if (!libpath.isEmpty()) { if (libpath_relative) { // NB: Because Qt makes the actual dlopen() call, the // RUNPATH of kdeinit is *not* respected - see // https://sourceware.org/bugzilla/show_bug.cgi?id=13945 // - so we try hacking it in ourselves QString install_lib_dir = QFile::decodeName( CMAKE_INSTALL_PREFIX "/" LIB_INSTALL_DIR "/"); QString orig_libpath = libpath; libpath = install_lib_dir + libpath; l.setFileName(libpath); if (!l.load()) { libpath = orig_libpath; l.setFileName(libpath); l.load(); } } else { l.load(); } if (!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 %s using a library: %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); QByteArray executable = execpath; if (!executable.isEmpty()) { execvp(executable.constData(), d.argv); } d.result = 1; // Error write(d.fd[1], &d.result, 1); close(d.fd[1]); exit(255); } QFunctionPointer 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", 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, "kdeinit5: Suspending process\n" "kdeinit5: 'gdb kdeinit5 %d' to debug\n" "kdeinit5: '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, "kdeinit5: 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, "kdeinit5: (%s %s) Pipe closed unexpectedly", name.constData(), execpath.constData()); perror("kdeinit5: Pipe closed unexpectedly"); d.result = 1; // Error } break; } perror("kdeinit5: Error reading from pipe"); d.result = 1; // Error break; } close(d.fd[0]); } #if HAVE_XCB 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; } #endif // !Q_OS_OSX 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("kdeinit5: Aborting. Can not create pipe"); exit(255); } options = fcntl(d.deadpipe[0], F_GETFL); if (options == -1) { perror("kdeinit5: Aborting. Can not make pipe non-blocking"); exit(255); } if (fcntl(d.deadpipe[0], F_SETFL, options | O_NONBLOCK) == -1) { perror("kdeinit5: 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); + sigprocmask(SIG_UNBLOCK, &(act.sa_mask), nullptr); 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); + sigaction(SIGCHLD, &act, nullptr); act.sa_handler = SIG_IGN; sigemptyset(&(act.sa_mask)); sigaddset(&(act.sa_mask), SIGPIPE); - sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0L); + sigprocmask(SIG_UNBLOCK, &(act.sa_mask), nullptr); act.sa_flags = 0; - sigaction(SIGPIPE, &act, 0L); + sigaction(SIGPIPE, &act, nullptr); } static void init_kdeinit_socket() { struct sockaddr_un sa; kde_socklen_t socklen; long options; const QByteArray home_dir = qgetenv("HOME"); int max_tries = 10; if (home_dir.isEmpty()) { fprintf(stderr, "kdeinit5: Aborting. $HOME not set!"); exit(255); } if (chdir(home_dir.constData()) != 0) { fprintf(stderr, "kdeinit5: 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, "kdeinit5: Aborting. $HOME directory (%s) does not exist.\n", path.data()); exit(255); } else if (readOnly.isEmpty()) { fprintf(stderr, "kdeinit5: Aborting. No write access to $HOME directory (%s).\n", path.data()); exit(255); } } } /** 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, "kdeinit5: 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; qstrncpy(server.sun_path, sock_file, sizeof(server.sun_path)); socklen = sizeof(server); if (connect(s, (struct sockaddr *)&server, socklen) == 0) { fprintf(stderr, "kdeinit5: 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); /** create socket **/ d.wrapper = socket(PF_UNIX, SOCK_STREAM, 0); if (d.wrapper < 0) { perror("kdeinit5: Aborting. socket() failed"); exit(255); } options = fcntl(d.wrapper, F_GETFL); if (options == -1) { perror("kdeinit5: Aborting. Can not make socket non-blocking"); close(d.wrapper); exit(255); } if (fcntl(d.wrapper, F_SETFL, options | O_NONBLOCK) == -1) { perror("kdeinit5: 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; qstrncpy(sa.sun_path, sock_file, sizeof(sa.sun_path)); if (bind(d.wrapper, (struct sockaddr *)&sa, socklen) != 0) { if (max_tries == 0) { perror("kdeinit5: 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("kdeinit5: 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("kdeinit5: 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("kdeinit5: socketpair() failed"); exit(255); } char args[32]; strcpy(args, "--fd="); sprintf(args + 5, "%d", d.launcher[1]); d.launcher_pid = launch(2, CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/klauncher", args); close(d.launcher[1]); #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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, "kdeinit5: Communication error with launcher. Exiting!\n"); ::exit(255); return; } // KLauncher died... restart #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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; + char *request_data = nullptr; 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 + 1); request_data[request_header.arg_length] = '\0'; result = read_socket(sock, request_data, request_header.arg_length); if (result != 0) { free(request_data); return false; } } //qDebug() << "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_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; + const char *cwd = nullptr; int envc = 0; - const char *envs = 0; - const char *tty = 0; + const char *envs = nullptr; + const char *tty = nullptr; int avoid_loops = 0; const char *startup_id_str = "0"; // krazy:exclude=doublequote_chars #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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_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, "kdeinit5: EXEC request has invalid format.\n"); #endif free(request_data); d.debug_wait = false; return true; // sure? } #if HAVE_X11 || HAVE_XCB // support for the old a bit broken way of setting DISPLAY for multihead QByteArray olddisplay = qgetenv(displayEnvVarName_c()); QByteArray kdedisplay = qgetenv("KDE_DISPLAY"); bool reset_display = (! olddisplay.isEmpty() && ! kdedisplay.isEmpty() && olddisplay != kdedisplay); if (reset_display) { qputenv(displayEnvVarName_c(), kdedisplay); } #endif 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 HAVE_X11 || HAVE_XCB if (reset_display) { unsetenv("KDE_DISPLAY"); qputenv(displayEnvVarName_c(), olddisplay); } #endif 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, "kdeinit5: 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, "kdeinit5: SETENV request has invalid format.\n"); #endif free(request_data); return true; // sure? } qputenv(env_name, env_value); } else if (request_header.cmd == LAUNCHER_TERMINATE_KDE) { #ifndef NDEBUG fprintf(stderr, "kdeinit5: terminate KDE.\n"); #endif #if HAVE_X11 - kdeinit_xio_errhandler(0L); + kdeinit_xio_errhandler(nullptr); #endif } else if (request_header.cmd == LAUNCHER_TERMINATE_KDEINIT) { #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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, "kdeinit5: 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, "kdeinit5: 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]; } #if HAVE_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, "kdeinit5: 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, "kdeinit5: 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); #if HAVE_X11 if (X11fd >= 0) { FD_SET(X11fd, &rd_set); } #endif - result = select(max_sock, &rd_set, &wr_set, &e_set, 0); + result = select(max_sock, &rd_set, &wr_set, &e_set, nullptr); if (result < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } perror("kdeinit5: Aborting. select() failed"); return; } /* Handle wrapper request */ if (d.wrapper >= 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) { 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; } } #if HAVE_X11 /* Look for incoming X11 events */ if (X11fd >= 0 && FD_ISSET(X11fd, &rd_set)) { - if (X11display != 0) { + if (X11display != nullptr) { XEvent event_return; while (XPending(X11display)) { XNextEvent(X11display, &event_return); } } } #endif } } static void generate_socket_name() { #if HAVE_X11 || HAVE_XCB // qt5: see displayEnvVarName_c() QByteArray display = qgetenv(displayEnvVarName_c()); if (display.isEmpty()) { fprintf(stderr, "kdeinit5: Aborting. $%s is not set. \n", displayEnvVarName_c()); exit(255); } int i; if ((i = display.lastIndexOf('.')) > display.lastIndexOf(':') && i >= 0) { display.truncate(i); } display.replace(':', '_'); #ifdef __APPLE__ // not entirely impossible, so let's leave it display.replace('/', '_'); #endif #else // not using a DISPLAY variable; use an empty string instead QByteArray display = ""; #endif // WARNING, if you change the socket name, adjust kwrapper too const QString socketFileName = QStringLiteral("kdeinit5_%1").arg(QLatin1String(display)); const QByteArray socketName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + socketFileName); if (static_cast(socketName.length()) >= MAX_SOCK_FILE) { fprintf(stderr, "kdeinit5: Aborting. Socket name will be too long:\n"); fprintf(stderr, " '%s'\n", socketName.data()); exit(255); } strcpy(sock_file, socketName.data()); } #if HAVE_X11 int kdeinit_xio_errhandler(Display *disp) { // disp is 0L when KDE shuts down. We don't want those warnings then. if (disp) { qWarning("kdeinit5: 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); } if (d.kded_pid) { kill(d.kded_pid, SIGTERM); } exit(0); } if (disp) { qWarning("kdeinit5: sending SIGHUP to children."); } /* this should remove all children we started */ signal(SIGHUP, SIG_IGN); kill(0, SIGHUP); sleep(2); if (disp) { qWarning("kdeinit5: sending SIGTERM to children."); } /* and if they don't listen to us, this should work */ signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); if (disp) { qWarning("kdeinit5: Exit."); } exit(0); return 0; } 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, "kdeinit5(%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); #else Q_UNUSED(dpy); Q_UNUSED(err); #endif return 0; } // 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 kdesu/su/sudo/etc. There the usual case is that kdesu (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 kdesu 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 kdesu 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 (!qEnvironmentVariableIsEmpty("XAUTHORITY")) { QByteArray display = qgetenv(displayEnvVarName_c()); int i; if ((i = display.lastIndexOf('.')) > display.lastIndexOf(':') && i >= 0) { display.truncate(i); } display.replace(':', '_'); #ifdef __APPLE__ display.replace('/', '_'); #endif QString xauth = QDir::tempPath() + QLatin1String("/xauth-") + QString::number(getuid()) + QLatin1Char('-') + QString::fromLocal8Bit(display); QSaveFile xauthfile(xauth); QFile xauthfrom(QFile::decodeName(qgetenv("XAUTHORITY"))); // Set umask to make sure the file permissions of xauthfile are correct mode_t oldMask = umask(S_IRGRP | S_IROTH | S_IWGRP | S_IWOTH); if (!xauthfrom.open(QFile::ReadOnly) || !xauthfile.open(QFile::WriteOnly) || xauthfile.write(xauthfrom.readAll()) != xauthfrom.size() || !xauthfile.commit()) { // error } else { qputenv("XAUTHORITY", QFile::encodeName(xauth)); } umask(oldMask); } } // Borrowed from kdebase/kaudio/kaudioserver.cpp static int initXconnection() { - X11display = XOpenDisplay(NULL); - if (X11display != 0) { + X11display = XOpenDisplay(nullptr); + if (X11display != nullptr) { XCreateSimpleWindow(X11display, DefaultRootWindow(X11display), 0, 0, 1, 1, \ 0, BlackPixelOfScreen(DefaultScreenOfDisplay(X11display)), BlackPixelOfScreen(DefaultScreenOfDisplay(X11display))); #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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, "kdeinit5: Can not connect to the X Server.\n" \ "kdeinit5: Might not terminate at end of session.\n"); return -1; } #endif #ifndef Q_OS_OSX // Find a shared lib in the lib dir, e.g. libkio.so. // Completely unrelated to plugins. static QString findSharedLib(const QString &lib) { QString path = QFile::decodeName(CMAKE_INSTALL_PREFIX "/" LIB_INSTALL_DIR "/") + lib; if (QFile::exists(path)) { return path; } // We could also look in LD_LIBRARY_PATH, but really, who installs the main libs in different prefixes? return QString(); } #endif extern "C" { static void secondary_child_handler(int) { - waitpid(-1, 0, WNOHANG); + waitpid(-1, nullptr, WNOHANG); } } int main(int argc, char **argv) { #ifndef _WIN32_WCE setlocale(LC_ALL, ""); setlocale(LC_NUMERIC, "C"); #endif pid_t pid; bool do_fork = true; int launch_klauncher = 1; int launch_kded = 0; int keep_running = 1; 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], "--kded") == 0) { launch_kded = 1; } // allow both nofork and no-fork for compatibility with // old versions (both of this and of KUniqueApplication) if (strcmp(safe_argv[i], "--nofork") == 0) { do_fork = false; } 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], "--exit") == 0) { keep_running = 0; } if (strcmp(safe_argv[i], "--version") == 0) { printf("Qt: %s\n", qVersion()); printf("KDE: %s\n", KINIT_VERSION_STRING); exit(0); } #if KDEINIT_OOM_PROTECT if (strcmp(safe_argv[i], "--oom-pipe") == 0 && i + 1 < argc) { oom_pipe = atol(argv[i + 1]); } #endif if (strcmp(safe_argv[i], "--help") == 0) { printf("Usage: kdeinit5 [options]\n"); printf(" --no-fork Do not fork\n"); // printf(" --no-klauncher Do not start klauncher\n"); printf(" --kded Start kded\n"); printf(" --suicide Terminate when no KDE applications are left running\n"); printf(" --version Show version information\n"); // printf(" --exit Terminate when kded has run\n"); exit(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) { #ifdef Q_OS_OSX mac_fork_and_reexec_self(); #else if (pipe(d.initpipe) != 0) { perror("kdeinit5: 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 '+') 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 } /** Make process group leader (for shutting down children later) **/ if (keep_running) { setsid(); } /** Prepare to change process name **/ #ifndef SKIP_PROCTITLE proctitle_init(argc, argv); #endif // don't change envvars before proctitle_init() unsetenv("LD_BIND_NOW"); unsetenv("DYLD_BIND_AT_LAUNCH"); KCrash::loadedByKdeinit = true; d.maxname = strlen(argv[0]); d.launcher_pid = 0; d.kded_pid = 0; d.wrapper = -1; d.accepted_fd = -1; d.debug_wait = false; d.launcher_ok = false; - children = NULL; + children = nullptr; init_signals(); #if HAVE_X11 setupX(); #endif generate_socket_name(); if (keep_running) { /* * Create ~/.kde/tmp-/kdeinit5- socket for incoming wrapper * requests. */ init_kdeinit_socket(); } #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) if (!d.suicide && qEnvironmentVariableIsEmpty("KDE_IS_PRELINKED")) { const int extrasCount = sizeof(extra_libs) / sizeof(extra_libs[0]); for (int i = 0; i < extrasCount; i++) { const QString extra = findSharedLib(QString::fromLatin1(extra_libs[i])); if (!extra.isEmpty()) { QLibrary l(extra); l.setLoadHints(QLibrary::ExportExternalSymbolsHint); (void)l.load(); } #ifndef NDEBUG else { fprintf(stderr, "%s was not found.\n", extra_libs[i]); } #endif } } #endif if (launch_klauncher) { start_klauncher(); handle_requests(d.launcher_pid); // Wait for klauncher to be ready } #if HAVE_X11 X11fd = initXconnection(); #endif { QFont::initialize(); #if HAVE_X11 if (XSupportsLocale()) { // Similar to QApplication::create_xim() // but we need to use our own display - XOpenIM(X11display, 0, 0, 0); + XOpenIM(X11display, nullptr, nullptr, nullptr); } #endif } if (launch_kded) { qputenv("KDED_STARTED_BY_KDEINIT", "1"); - pid = launch(1, KDED_EXENAME, 0); + pid = launch(1, KDED_EXENAME, nullptr); unsetenv("KDED_STARTED_BY_KDEINIT"); #ifndef NDEBUG fprintf(stderr, "kdeinit5: Launched KDED, pid = %ld result = %d\n", (long) pid, d.result); #endif d.kded_pid = pid; // kded5 doesn't fork on startup anymore, so just move on. //handle_requests(pid); } for (int i = 1; i < argc; i++) { if (safe_argv[i][0] == '+') { - pid = launch(1, safe_argv[i] + 1, 0); + pid = launch(1, safe_argv[i] + 1, nullptr); #ifndef NDEBUG fprintf(stderr, "kdeinit5: 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] == '-' #if KDEINIT_OOM_PROTECT || isdigit(safe_argv[i][0]) #endif ) { // Ignore } else { - pid = launch(1, safe_argv[i], 0); + pid = launch(1, safe_argv[i], nullptr); #ifndef NDEBUG fprintf(stderr, "kdeinit5: Launched '%s', pid = %ld result = %d\n", safe_argv[i], (long) pid, d.result); #endif } } /** Free arguments **/ for (int i = 0; i < argc; i++) { free(safe_argv[i]); } free(safe_argv); #ifndef SKIP_PROCTITLE proctitle_set("Running..."); #endif 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; } diff --git a/src/kdeinit/proctitle.cpp b/src/kdeinit/proctitle.cpp index 4568d16..88af2af 100644 --- a/src/kdeinit/proctitle.cpp +++ b/src/kdeinit/proctitle.cpp @@ -1,211 +1,211 @@ /* Based on setproctitle.c from OpenSSH 6.6p1 */ /* * Copyright 2014 Alex Merry * Copyright 2003 Damien Miller * Copyright (c) 1983, 1995-1997 Eric P. Allman * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "proctitle.h" #include #define PT_NONE 0 /* don't use it at all */ #define PT_PSTAT 1 /* use pstat(PSTAT_SETCMD, ...) */ #define PT_REUSEARGV 2 /* cover argv with title information */ #define PT_SETPROCTITLE 3 /* forward onto the native setproctitle */ #include #include #include #include #include #include #if HAVE_SETPROCTITLE # define PT_TYPE PT_SETPROCTITLE // process title should get prepended automagically # define ADD_PROCTITLE 0 #elif HAVE_SYS_PSTAT_H && HAVE_PSTAT # include # define PT_TYPE PT_PSTAT #elif CAN_CLOBBER_ARGV # define PT_TYPE PT_REUSEARGV #endif #ifndef PT_TYPE # define PT_TYPE PT_NONE #endif #ifndef ADD_PROCTITLE # define ADD_PROCTITLE 1 #endif #if PT_TYPE == PT_REUSEARGV -static char *argv_start = NULL; +static char *argv_start = nullptr; static size_t argv_env_len = 0; #endif #if HAVE___PROGNAME extern char *__progname; #else char *__progname; #endif void proctitle_init(int argc, char *argv[]) { #if HAVE___PROGNAME // progname may be a reference to argv[0] __progname = strdup(__progname); #else if (argc == 0 || argv[0] == NULL) { __progname = "unknown"; /* XXX */ } else { char *p = strrchr(argv[0], '/'); if (p == NULL) p = argv[0]; else p++; __progname = strdup(p); } #endif #if PT_TYPE == PT_REUSEARGV - if (argc == 0 || argv[0] == NULL) + if (argc == 0 || argv[0] == nullptr) return; extern char **environ; - char *lastargv = NULL; + char *lastargv = nullptr; char **envp = environ; int i; /* * NB: This assumes that argv has already been copied out of the * way. This is true for kdeinit, but may not be true for other * programs. Beware. */ /* Fail if we can't allocate room for the new environment */ - for (i = 0; envp[i] != NULL; i++) + for (i = 0; envp[i] != nullptr; i++) ; - if ((environ = (char**)calloc(i + 1, sizeof(*environ))) == NULL) { + if ((environ = (char**)calloc(i + 1, sizeof(*environ))) == nullptr) { environ = envp; /* put it back */ return; } /* * Find the last argv string or environment variable within * our process memory area. */ for (i = 0; i < argc; i++) { - if (lastargv == NULL || lastargv + 1 == argv[i]) + if (lastargv == nullptr || lastargv + 1 == argv[i]) lastargv = argv[i] + strlen(argv[i]); } - for (i = 0; envp[i] != NULL; i++) { + for (i = 0; envp[i] != nullptr; i++) { if (lastargv + 1 == envp[i]) lastargv = envp[i] + strlen(envp[i]); } - argv[1] = NULL; + argv[1] = nullptr; argv_start = argv[0]; argv_env_len = lastargv - argv[0] - 1; /* * Copy environment * XXX - will truncate env on strdup fail */ - for (i = 0; envp[i] != NULL; i++) + for (i = 0; envp[i] != nullptr; i++) environ[i] = strdup(envp[i]); - environ[i] = NULL; + environ[i] = nullptr; #endif /* PT_REUSEARGV */ } void proctitle_set(const char *fmt, ...) { #if PT_TYPE != PT_NONE #if PT_TYPE == PT_REUSEARGV if (argv_env_len <= 0) return; #endif bool skip_proctitle = false; - if (fmt != NULL && fmt[0] == '-') { + if (fmt != nullptr && fmt[0] == '-') { skip_proctitle = true; ++fmt; } char ptitle[1024]; memset(ptitle, '\0', sizeof(ptitle)); size_t len = 0; #if ADD_PROCTITLE if (!skip_proctitle) { strncpy(ptitle, __progname, sizeof(ptitle)-1); len = strlen(ptitle); - if (fmt != NULL && sizeof(ptitle) - len > 2) { + if (fmt != nullptr && sizeof(ptitle) - len > 2) { strcpy(ptitle + len, ": "); len += 2; } } #endif - if (fmt != NULL) { + if (fmt != nullptr) { int r = -1; if (len < sizeof(ptitle) - 1) { va_list ap; va_start(ap, fmt); r = vsnprintf(ptitle + len, sizeof(ptitle) - len , fmt, ap); va_end(ap); } if (r == -1 || (size_t)r >= sizeof(ptitle) - len) return; } #if PT_TYPE == PT_PSTAT union pstun pst; pst.pst_command = ptitle; pstat(PSTAT_SETCMD, pst, strlen(ptitle), 0, 0); #elif PT_TYPE == PT_REUSEARGV strncpy(argv_start, ptitle, argv_env_len); argv_start[argv_env_len-1] = '\0'; #elif PT_TYPE == PT_SETPROCTITLE if (fmt == NULL) { setproctitle(NULL); #if defined(__FreeBSD__) } else if (skip_proctitle) { // setproctitle on FreeBSD allows skipping the process title setproctitle("-%s", ptitle); #endif } else { setproctitle("%s", ptitle); } #endif #endif /* !PT_NONE */ } diff --git a/src/klauncher/klauncher.cpp b/src/klauncher/klauncher.cpp index 02c129c..c02bd90 100644 --- a/src/klauncher/klauncher.cpp +++ b/src/klauncher/klauncher.cpp @@ -1,1302 +1,1302 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Waldo Bastian 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. */ #ifndef QT_NO_CAST_FROM_ASCII #define QT_NO_CAST_FROM_ASCII #endif #include "klauncher.h" #include "klauncher_cmds.h" #include "klauncher_adaptor.h" #include "kslavelauncheradaptor.h" #include #include #include #include #if HAVE_X11 #include #include #endif #if HAVE_XCB #include #endif #include #include #include #include #include #include #include #include #include #include #include // to find kioslave modules #include #include #include // TODO port away from kiofilewidgets #include #include #include #ifdef Q_OS_WIN #include //windows.h feels like defining this... #undef interface #endif // Dispose slaves after being idle for SLAVE_MAX_IDLE seconds #define SLAVE_MAX_IDLE 30 // #define KLAUNCHER_VERBOSE_OUTPUT #include Q_DECLARE_LOGGING_CATEGORY(KLAUNCHER) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(KLAUNCHER, "kf5.kinit.klauncher", QtWarningMsg) static const char *const s_DBusStartupTypeToString[] = { "DBusNone", "DBusUnique", "DBusMulti", "ERROR" }; using namespace KIO; static KLauncher *g_klauncher_self; // From qcore_unix_p.h. We could also port to QLocalSocket :) #define K_EINTR_LOOP(var, cmd) \ do { \ var = cmd; \ } while (var == -1 && errno == EINTR) ssize_t kde_safe_write(int fd, const void *buf, size_t count) { ssize_t ret = 0; K_EINTR_LOOP(ret, QT_WRITE(fd, buf, count)); if (ret < 0) { qWarning() << "write failed:" << strerror(errno); } return ret; } #ifndef USE_KPROCESS_FOR_KIOSLAVES KLauncher::KLauncher(int _kdeinitSocket) - : QObject(0), + : QObject(nullptr), kdeinitSocket(_kdeinitSocket) #else KLauncher::KLauncher() : QObject(0) #endif { #if HAVE_X11 mIsX11 = QGuiApplication::platformName() == QStringLiteral("xcb"); #endif - Q_ASSERT(g_klauncher_self == NULL); + Q_ASSERT(g_klauncher_self == nullptr); g_klauncher_self = this; mAutoTimer.setSingleShot(true); new KLauncherAdaptor(this); mSlaveLauncherAdaptor = new KSlaveLauncherAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KLauncher"), this); // same as ktoolinvocation.cpp connect(&mAutoTimer, SIGNAL(timeout()), this, SLOT(slotAutoStart())); connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), SLOT(slotNameOwnerChanged(QString,QString,QString))); mConnectionServer.listenForRemote(); connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave())); if (!mConnectionServer.isListening()) { // Severe error! qWarning("KLauncher: Fatal error, can't create tempfile!"); ::_exit(1); } connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout())); #ifndef USE_KPROCESS_FOR_KIOSLAVES kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read); connect(kdeinitNotifier, SIGNAL(activated(int)), this, SLOT(slotKDEInitData(int))); kdeinitNotifier->setEnabled(true); #endif - lastRequest = 0; + lastRequest = nullptr; bProcessingQueue = false; mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT")); if (!mSlaveDebug.isEmpty()) { #ifndef USE_KPROCESS_FOR_KIOSLAVES qWarning("Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug)); #else // Slave debug mode causes kdeinit to suspend the process waiting // for the developer to attach gdb to the process; we do not have // a good way of doing a similar thing if we are using QProcess. mSlaveDebug.clear(); qWarning("slave-debug mode is not available as Klauncher is not using kdeinit"); #endif } mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND")); if (!mSlaveValgrind.isEmpty()) { mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN")); qWarning("Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind)); } #ifdef USE_KPROCESS_FOR_KIOSLAVES qCDebug(KLAUNCHER) << "LAUNCHER_OK"; #else klauncher_header request_header; request_header.cmd = LAUNCHER_OK; request_header.arg_length = 0; kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); #endif } KLauncher::~KLauncher() { close(); - g_klauncher_self = NULL; + g_klauncher_self = nullptr; } void KLauncher::close() { #if HAVE_XCB if (mCached) { xcb_disconnect(mCached.conn); mCached = XCBConnection(); } #endif } void KLauncher::destruct() { if (g_klauncher_self) { g_klauncher_self->close(); } // We don't delete the app here, that's intentional. ::_exit(255); } void KLauncher::setLaunchEnv(const QString &name, const QString &value) { #ifndef USE_KPROCESS_FOR_KIOSLAVES klauncher_header request_header; QByteArray requestData; requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0'); request_header.cmd = LAUNCHER_SETENV; request_header.arg_length = requestData.size(); kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); kde_safe_write(kdeinitSocket, requestData.data(), request_header.arg_length); #else Q_UNUSED(name); Q_UNUSED(value); #endif } #ifndef USE_KPROCESS_FOR_KIOSLAVES /* * Read 'len' bytes from 'sock' into buffer. * returns -1 on failure, 0 on no data. */ static int read_socket(int sock, char *buffer, int len) { ssize_t result; int bytes_left = len; while (bytes_left > 0) { // in case we get a request to start an application and data arrive // to kdeinitSocket at the same time, requestStart() will already // call slotKDEInitData(), so we must check there's still something // to read, otherwise this would block // Same thing if kdeinit dies without warning. fd_set in; timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us FD_ZERO(&in); FD_SET(sock, &in); - select(sock + 1, &in, 0, 0, &tm); + select(sock + 1, &in, nullptr, nullptr, &tm); if (!FD_ISSET(sock, &in)) { qCDebug(KLAUNCHER) << "read_socket" << sock << "nothing to read, kdeinit5 must be dead"; return -1; } 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)) { return -1; } } return 0; } #endif void KLauncher::slotKDEInitData(int) { #ifndef USE_KPROCESS_FOR_KIOSLAVES klauncher_header request_header; QByteArray requestData; int result = read_socket(kdeinitSocket, (char *) &request_header, sizeof(request_header)); if (result == -1) { qCDebug(KLAUNCHER) << "Exiting on read_socket errno:" << errno; signal(SIGHUP, SIG_IGN); signal(SIGTERM, SIG_IGN); destruct(); // Exit! } requestData.resize(request_header.arg_length); result = read_socket(kdeinitSocket, (char *) requestData.data(), request_header.arg_length); processRequestReturn(request_header.cmd, requestData); #endif } void KLauncher::processRequestReturn(int status, const QByteArray &requestData) { if (status == LAUNCHER_CHILD_DIED) { long *request_data; request_data = (long *) requestData.data(); processDied(request_data[0], request_data[1]); return; } if (lastRequest && (status == LAUNCHER_OK)) { long *request_data; request_data = (long *) requestData.data(); lastRequest->pid = (pid_t)(*request_data); qCDebug(KLAUNCHER).nospace() << lastRequest->name << " (pid " << lastRequest->pid << ") up and running."; switch (lastRequest->dbus_startup_type) { case KService::DBusNone: if (lastRequest->wait) { lastRequest->status = KLaunchRequest::Launching; } else { lastRequest->status = KLaunchRequest::Running; } break; case KService::DBusUnique: case KService::DBusMulti: lastRequest->status = KLaunchRequest::Launching; break; } - lastRequest = 0; + lastRequest = nullptr; return; } if (lastRequest && (status == LAUNCHER_ERROR)) { lastRequest->status = KLaunchRequest::Error; qCDebug(KLAUNCHER) << lastRequest->name << " failed."; if (!requestData.isEmpty()) { lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data()); } - lastRequest = 0; + lastRequest = nullptr; return; } qWarning() << "Unexpected request return" << (unsigned int) status; } void KLauncher::processDied(pid_t pid, long exitStatus) { #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << pid << "exitStatus=" << exitStatus; #else Q_UNUSED(exitStatus); // We should probably check the exitStatus for the uniqueapp case? #endif foreach (KLaunchRequest *request, requestList) { #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << " had pending request" << request->pid; #endif if (request->pid == pid) { if ((request->dbus_startup_type == KService::DBusUnique) && QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { request->status = KLaunchRequest::Running; #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << pid << "running as a unique app"; #endif } else if(request->dbus_startup_type == KService::DBusNone && request->wait) { request->status = KLaunchRequest::Running; #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << pid << "running as DBusNone with wait to true"; #endif } else { request->status = KLaunchRequest::Error; #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << pid << "died, requestDone. status=" << request->status; #endif } requestDone(request); return; } } #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "found no pending requests for PID" << pid; #endif } static bool matchesPendingRequest(const QString &appId, const QString &pendingAppId) { // appId just registered, e.g. org.koffice.kword-12345 // Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for. const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present. qCDebug(KLAUNCHER) << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId; if (pendingAppId.startsWith(QLatin1String("*."))) { const QString pendingName = pendingAppId.mid(2); const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.')) + 1); qCDebug(KLAUNCHER) << "appName=" << appName; return appName == pendingName; } return newAppId == pendingAppId; } void KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); if (appId.isEmpty() || newOwner.isEmpty()) { return; } #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "new app" << appId; #endif foreach (KLaunchRequest *request, requestList) { if (request->status != KLaunchRequest::Launching) { continue; } #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name; #endif // For unique services check the requested service name first if (request->dbus_startup_type == KService::DBusUnique) { if ((appId == request->dbus_name) || // just started QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running request->status = KLaunchRequest::Running; #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "OK, unique app" << request->dbus_name << "is running"; #endif requestDone(request); continue; } else { #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "unique app" << request->dbus_name << "not running yet"; #endif } } const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name; #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "using" << rAppId << "for matching"; #endif if (rAppId.isEmpty()) { continue; } if (matchesPendingRequest(appId, rAppId)) { #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "ok, request done"; #endif request->dbus_name = appId; request->status = KLaunchRequest::Running; requestDone(request); continue; } } } void KLauncher::autoStart(int phase) { if (mAutoStart.phase() >= phase) { return; } mAutoStart.setPhase(phase); if (phase == 0) { mAutoStart.loadAutoStartList(); } mAutoTimer.start(0); } void KLauncher::slotAutoStart() { KService::Ptr s; do { QString service = mAutoStart.startService(); qCDebug(KLAUNCHER) << "Service: " << mAutoStart.phase() << service; if (service.isEmpty()) { // Done if (!mAutoStart.phaseDone()) { mAutoStart.setPhaseDone(); switch (mAutoStart.phase()) { case 0: emit autoStart0Done(); break; case 1: emit autoStart1Done(); break; case 2: emit autoStart2Done(); break; } } return; } s = new KService(service); } while (!start_service(s, QStringList(), QStringList(), "0", false, true, QDBusMessage())); // Loop till we find a service that we can start. } void KLauncher::requestDone(KLaunchRequest *request) { if ((request->status == KLaunchRequest::Running) || (request->status == KLaunchRequest::Done)) { requestResult.result = 0; requestResult.dbusName = request->dbus_name; requestResult.error = QStringLiteral(""); // not null, cf assert further down requestResult.pid = request->pid; } else { requestResult.result = 1; requestResult.dbusName.clear(); requestResult.error = i18n("KDEInit could not launch '%1'", request->name); if (!request->errorMsg.isEmpty()) { requestResult.error += QStringLiteral(":\n") + request->errorMsg; } requestResult.pid = 0; #if HAVE_XCB if (!request->startup_dpy.isEmpty() && mIsX11) { XCBConnection conn = getXCBConnection(request->startup_dpy); if (conn) { KStartupInfoId id; id.initId(request->startup_id); KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id); } } #endif } if (request->autoStart) { mAutoTimer.start(0); } if (request->transaction.type() != QDBusMessage::InvalidMessage) { if (requestResult.dbusName.isNull()) { // null strings can't be sent requestResult.dbusName.clear(); } Q_ASSERT(!requestResult.error.isNull()); quintptr stream_pid = requestResult.pid; QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result << requestResult.dbusName << requestResult.error << stream_pid)); } #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "removing done request" << request->name << "PID" << request->pid; #endif requestList.removeAll(request); delete request; } static void appendLong(QByteArray &ba, long l) { const int sz = ba.size(); ba.resize(sz + sizeof(long)); memcpy(ba.data() + sz, &l, sizeof(long)); } void KLauncher::requestStart(KLaunchRequest *request) { #ifdef USE_KPROCESS_FOR_KIOSLAVES requestList.append(request); lastRequest = request; QProcess *process = new QProcess; process->setProcessChannelMode(QProcess::MergedChannels); connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGotOutput())); connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotFinished(int,QProcess::ExitStatus))); request->process = process; QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); foreach (const QString &env, request->envs) { const int pos = env.indexOf(QLatin1Char('=')); const QString envVariable = env.left(pos); const QString envValue = env.mid(pos + 1); environment.insert(envVariable, envValue); } process->setProcessEnvironment(environment); QStringList args; foreach (const QString &arg, request->arg_list) { args << arg; } QString executable = request->name; #ifdef Q_OS_OSX const QString bundlepath = QStandardPaths::findExecutable(executable); if (!bundlepath.isEmpty()) { executable = bundlepath; } #endif process->start(executable, args); if (!process->waitForStarted()) { processRequestReturn(LAUNCHER_ERROR, ""); } else { #ifndef Q_OS_WIN request->pid = process->pid(); #else request->pid = process->pid()->dwProcessId; #endif QByteArray data((char *)&request->pid, sizeof(int)); processRequestReturn(LAUNCHER_OK, data); } return; #else requestList.append(request); // Send request to kdeinit. klauncher_header request_header; QByteArray requestData; requestData.reserve(1024); appendLong(requestData, request->arg_list.count() + 1); requestData.append(request->name.toLocal8Bit()); requestData.append('\0'); foreach (const QString &arg, request->arg_list) { requestData.append(arg.toLocal8Bit()).append('\0'); } appendLong(requestData, request->envs.count()); foreach (const QString &env, request->envs) { requestData.append(env.toLocal8Bit()).append('\0'); } appendLong(requestData, 0); // avoid_loops, always false here #if HAVE_X11 bool startup_notify = mIsX11 && !request->startup_id.isNull() && request->startup_id != "0"; if (startup_notify) { requestData.append(request->startup_id).append('\0'); } #endif if (!request->cwd.isEmpty()) { requestData.append(QFile::encodeName(request->cwd)).append('\0'); } #if HAVE_X11 request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW; #else request_header.cmd = LAUNCHER_EXEC_NEW; #endif request_header.arg_length = requestData.length(); #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "Asking kdeinit to start" << request->name << request->arg_list << "cmd=" << commandToString(request_header.cmd); #endif kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); kde_safe_write(kdeinitSocket, requestData.data(), requestData.length()); // Wait for pid to return. lastRequest = request; do { slotKDEInitData(kdeinitSocket); - } while (lastRequest != 0); + } while (lastRequest != nullptr); #endif } void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id) { KLaunchRequest *request = new KLaunchRequest; request->autoStart = false; request->name = name; request->arg_list = arg_list; request->dbus_startup_type = KService::DBusNone; request->pid = 0; request->status = KLaunchRequest::Launching; request->envs = envs; request->wait = false; // Find service, if any - strip path if needed KService::Ptr service = KService::serviceByDesktopName(name.mid(name.lastIndexOf(QLatin1Char('/')) + 1)); if (service) { send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList()); } else { // no .desktop file, no startup info cancel_service_startup_info(request, startup_id.toLocal8Bit(), envs); } requestStart(request); // We don't care about this request any longer.... requestDone(request); } bool KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls, const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg) { KService::Ptr service; // Find service const QFileInfo fi(serviceName); if (fi.isAbsolute() && fi.exists()) { // Full path service = new KService(serviceName); } else { service = KService::serviceByDesktopPath(serviceName); // TODO? //if (!service) // service = KService::serviceByStorageId(serviceName); // This method should be named start_service_by_storage_id ideally... } if (!service) { requestResult.result = ENOENT; requestResult.error = i18n("Could not find service '%1'.", serviceName); - cancel_service_startup_info(NULL, startup_id.toLocal8Bit(), envs); // cancel it if any + cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs); // cancel it if any return false; } return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg); } bool KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls, const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg) { KService::Ptr service = KService::serviceByDesktopName(serviceName); if (!service) { requestResult.result = ENOENT; requestResult.error = i18n("Could not find service '%1'.", serviceName); - cancel_service_startup_info(NULL, startup_id.toLocal8Bit(), envs); // cancel it if any + cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs); // cancel it if any return false; } return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg); } bool KLauncher::start_service(KService::Ptr service, const QStringList &_urls, const QStringList &envs, const QByteArray &startup_id, bool blind, bool autoStart, const QDBusMessage &msg) { QStringList urls = _urls; bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath()); if (!service->isValid() || !runPermitted) { requestResult.result = ENOEXEC; if (service->isValid()) { requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath()); } else { requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath()); } - cancel_service_startup_info(NULL, startup_id, envs); // cancel it if any + cancel_service_startup_info(nullptr, startup_id, envs); // cancel it if any return false; } KLaunchRequest *request = new KLaunchRequest; request->autoStart = autoStart; enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (service->runOnDiscreteGpu() && s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QLatin1String("org.kde.Solid.PowerManagement"), QLatin1String("/org/kde/Solid/PowerManagement"), QLatin1String("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QLatin1String("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } QStringList _envs = envs; if (service->runOnDiscreteGpu() && s_gpuCheck == Present) { _envs << QLatin1String("DRI_PRIME=1"); } if ((urls.count() > 1) && !service->allowMultipleFiles()) { // We need to launch the application N times. That sucks. // We ignore the result for application 2 to N. // For the first file we launch the application in the // usual way. The reported result is based on this // application. QStringList::ConstIterator it = urls.constBegin(); for (++it; it != urls.constEnd(); ++it) { QStringList singleUrl; singleUrl.append(*it); QByteArray startup_id2 = startup_id; if (!startup_id2.isEmpty() && startup_id2 != "0") { startup_id2 = "0"; // can't use the same startup_id several times // krazy:exclude=doublequote_chars } start_service(service, singleUrl, _envs, startup_id2, true, false, msg); } const QString firstURL = urls.at(0); urls.clear(); urls.append(firstURL); } const QList qurls = QUrl::fromStringList(urls); createArgs(request, service, qurls); // We must have one argument at least! if (request->arg_list.isEmpty()) { requestResult.result = ENOEXEC; requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath()); delete request; - cancel_service_startup_info(NULL, startup_id, _envs); + cancel_service_startup_info(nullptr, startup_id, _envs); return false; } request->name = request->arg_list.takeFirst(); if (request->name.endsWith(QLatin1String("/kioexec"))) { // Special case for kioexec; if createArgs said we were going to use it, // then we have to expect a kioexec-PID, not a org.kde.finalapp... // Testcase: konqueror www.kde.org, RMB on link, open with, kruler. request->dbus_startup_type = KService::DBusMulti; request->dbus_name = QStringLiteral("org.kde.kioexec"); } else { request->dbus_startup_type = service->dbusStartupType(); if ((request->dbus_startup_type == KService::DBusUnique) || (request->dbus_startup_type == KService::DBusMulti)) { const QVariant v = service->property(QStringLiteral("X-DBUS-ServiceName")); if (v.isValid()) { request->dbus_name = v.toString(); } if (request->dbus_name.isEmpty()) { const QString binName = KIO::DesktopExecParser::executableName(service->exec()); request->dbus_name = QStringLiteral("org.kde.") + binName; request->tolerant_dbus_name = QStringLiteral("*.") + binName; } } } #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "name=" << request->name << "dbus_name=" << request->dbus_name << "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type]; #endif request->pid = 0; request->wait = false; request->envs = _envs; send_service_startup_info(request, service, startup_id, _envs); // Request will be handled later. if (!blind && !autoStart) { msg.setDelayedReply(true); request->transaction = msg; } queueRequest(request); return true; } void KLauncher::send_service_startup_info(KLaunchRequest *request, KService::Ptr service, const QByteArray &startup_id, const QStringList &envs) { #if HAVE_XCB if (!mIsX11) { return; } request->startup_id = "0";// krazy:exclude=doublequote_chars if (startup_id == "0") { return; } bool silent; QByteArray wmclass; if (!KRun::checkStartupNotify(QString(), service.data(), &silent, &wmclass)) { return; } KStartupInfoId id; id.initId(startup_id); QByteArray dpy_str; foreach (const QString &env, envs) { if (env.startsWith(QLatin1String("DISPLAY="))) { dpy_str = env.mid(8).toLocal8Bit(); } } XCBConnection conn = getXCBConnection(dpy_str); request->startup_id = id.id(); if (!conn) { cancel_service_startup_info(request, startup_id, envs); return; } request->startup_dpy = conn.displayName; KStartupInfoData data; data.setName(service->name()); data.setIcon(service->icon()); data.setDescription(i18n("Launching %1", service->name())); if (!wmclass.isEmpty()) { data.setWMClass(wmclass); } if (silent) { data.setSilent(KStartupInfoData::Yes); } data.setApplicationId(service->entryPath()); // the rest will be sent by kdeinit KStartupInfo::sendStartupXcb(conn.conn, conn.screen, id, data); return; #else return; #endif } void KLauncher::cancel_service_startup_info(KLaunchRequest *request, const QByteArray &startup_id, const QStringList &envs) { #if HAVE_XCB - if (request != NULL) { + if (request != nullptr) { request->startup_id = "0"; // krazy:exclude=doublequote_chars } if (!startup_id.isEmpty() && startup_id != "0" && mIsX11) { QString dpy_str; foreach (const QString &env, envs) { if (env.startsWith(QLatin1String("DISPLAY="))) { dpy_str = env.mid(8); } } XCBConnection conn = getXCBConnection(dpy_str.toLocal8Bit()); if (!conn) { return; } KStartupInfoId id; id.initId(startup_id); KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id); } #endif } bool KLauncher::kdeinit_exec(const QString &app, const QStringList &args, const QString &workdir, const QStringList &envs, const QString &startup_id, bool wait, const QDBusMessage &msg) { KLaunchRequest *request = new KLaunchRequest; request->autoStart = false; request->arg_list = args; request->name = app; request->dbus_startup_type = KService::DBusNone; request->pid = 0; request->wait = wait; #if HAVE_X11 request->startup_id = startup_id.toLocal8Bit(); #endif request->envs = envs; request->cwd = workdir; #if HAVE_X11 if (!app.endsWith(QLatin1String("kbuildsycoca5"))) { // avoid stupid loop // Find service, if any - strip path if needed const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1); KService::Ptr service = KService::serviceByDesktopName(desktopName); if (service) send_service_startup_info(request, service, request->startup_id, envs); else { // no .desktop file, no startup info cancel_service_startup_info(request, request->startup_id, envs); } } #endif msg.setDelayedReply(true); request->transaction = msg; queueRequest(request); return true; } void KLauncher::queueRequest(KLaunchRequest *request) { requestQueue.append(request); if (!bProcessingQueue) { bProcessingQueue = true; QTimer::singleShot(0, this, SLOT(slotDequeue())); } } void KLauncher::slotDequeue() { do { KLaunchRequest *request = requestQueue.takeFirst(); // process request request->status = KLaunchRequest::Launching; requestStart(request); if (request->status != KLaunchRequest::Launching) { // Request handled. #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "Request handled already"; #endif requestDone(request); continue; } } while (!requestQueue.isEmpty()); bProcessingQueue = false; } void KLauncher::createArgs(KLaunchRequest *request, const KService::Ptr service, const QList &urls) { KIO::DesktopExecParser parser(*service, urls); const QStringList params = parser.resultingArguments(); Q_FOREACH (const QString &arg, params) { request->arg_list.append(arg); } const QString& path = service->path(); if (!path.isEmpty()) { request->cwd = path; } else if (!urls.isEmpty()) { const QUrl& url = urls.first(); if (url.isLocalFile()) { request->cwd = url.adjusted(QUrl::RemoveFilename).toLocalFile(); } } } ///// IO-Slave functions pid_t KLauncher::requestHoldSlave(const QString &urlStr, const QString &app_socket) { const QUrl url(urlStr); - IdleSlave *slave = 0; + IdleSlave *slave = nullptr; foreach (IdleSlave *p, mSlaveList) { if (p->onHold(url)) { slave = p; break; } } if (slave) { mSlaveList.removeAll(slave); slave->connect(app_socket); return slave->pid(); } return 0; } pid_t KLauncher::requestSlave(const QString &protocol, const QString &host, const QString &app_socket, QString &error) { - IdleSlave *slave = 0; + IdleSlave *slave = nullptr; foreach (IdleSlave *p, mSlaveList) { if (p->match(protocol, host, true)) { slave = p; break; } } if (!slave) { foreach (IdleSlave *p, mSlaveList) { if (p->match(protocol, host, false)) { slave = p; break; } } } if (!slave) { foreach (IdleSlave *p, mSlaveList) { if (p->match(protocol, QString(), false)) { slave = p; break; } } } if (slave) { mSlaveList.removeAll(slave); slave->connect(app_socket); return slave->pid(); } QString slaveModule = KProtocolInfo::exec(protocol); if (slaveModule.isEmpty()) { error = i18n("Unknown protocol '%1'.\n", protocol); return 0; } KPluginLoader loader(slaveModule); QString slaveModulePath = loader.fileName(); if (slaveModulePath.isEmpty()) { error = i18n("Could not find the '%1' plugin.\n", slaveModule); return 0; } QStringList arg_list; #ifdef USE_KPROCESS_FOR_KIOSLAVES arg_list << slaveModulePath; arg_list << protocol; arg_list << mConnectionServer.address().toString(); arg_list << app_socket; QString name = QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave"); #else QString arg1 = protocol; QString arg2 = mConnectionServer.address().toString(); QString arg3 = app_socket; arg_list.append(arg1); arg_list.append(arg2); arg_list.append(arg3); QString name = slaveModulePath; #endif qCDebug(KLAUNCHER) << "KLauncher: launching new slave" << name << "with protocol=" << protocol << "args=" << arg_list; #ifdef Q_OS_UNIX #ifndef USE_KPROCESS_FOR_KIOSLAVES // see comments where mSlaveDebug is set in KLauncher::KLauncher if (mSlaveDebug == protocol) { klauncher_header request_header; request_header.cmd = LAUNCHER_DEBUG_WAIT; request_header.arg_length = 0; kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); } #endif if (mSlaveValgrind == protocol) { arg_list.prepend(name); #ifndef USE_KPROCESS_FOR_KIOSLAVES // otherwise we've already done this arg_list.prepend(QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave")); #endif name = QStringLiteral("valgrind"); if (!mSlaveValgrindSkin.isEmpty()) { arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin); } else { arg_list.prepend(QLatin1String("--tool=memcheck")); } } #endif KLaunchRequest *request = new KLaunchRequest; request->autoStart = false; request->name = name; request->arg_list = arg_list; request->dbus_startup_type = KService::DBusNone; request->pid = 0; request->wait = false; #if HAVE_X11 request->startup_id = "0"; // krazy:exclude=doublequote_chars #endif request->status = KLaunchRequest::Launching; requestStart(request); pid_t pid = request->pid; // qCDebug(KLAUNCHER) << "Slave launched, pid = " << pid; // We don't care about this request any longer.... requestDone(request); if (!pid) { error = i18n("Error loading '%1'.", name); } return pid; } bool KLauncher::checkForHeldSlave(const QString &urlStr) { QUrl url(urlStr); Q_FOREACH (const IdleSlave *p, mSlaveList) { if (p->onHold(url)) { return true; } } return false; } void KLauncher::waitForSlave(int pid) { Q_ASSERT(calledFromDBus()); foreach (IdleSlave *slave, mSlaveList) { if (slave->pid() == static_cast(pid)) { return; // Already here. } } SlaveWaitRequest *waitRequest = new SlaveWaitRequest; setDelayedReply(true); waitRequest->transaction = message(); // from QDBusContext waitRequest->pid = static_cast(pid); mSlaveWaitRequest.append(waitRequest); } void KLauncher::acceptSlave() { IdleSlave *slave = new IdleSlave(this); mConnectionServer.setNextPendingConnection(slave->connection()); mSlaveList.append(slave); connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone())); connect(slave, SIGNAL(statusUpdate(IdleSlave*)), this, SLOT(slotSlaveStatus(IdleSlave*))); if (!mTimer.isActive()) { mTimer.start(1000 * 10); } } void KLauncher::slotSlaveStatus(IdleSlave *slave) { QMutableListIterator it(mSlaveWaitRequest); while (it.hasNext()) { SlaveWaitRequest *waitRequest = it.next(); if (waitRequest->pid == slave->pid()) { QDBusConnection::sessionBus().send(waitRequest->transaction.createReply()); it.remove(); delete waitRequest; } } } void KLauncher::slotSlaveGone() { IdleSlave *slave = (IdleSlave *) sender(); mSlaveList.removeAll(slave); if ((mSlaveList.count() == 0) && (mTimer.isActive())) { mTimer.stop(); } } void KLauncher::idleTimeout() { bool keepOneFileSlave = true; QDateTime now = QDateTime::currentDateTime(); foreach (IdleSlave *slave, mSlaveList) { if ((slave->protocol() == QLatin1String("file")) && (keepOneFileSlave)) { keepOneFileSlave = false; } else if (slave->age(now) > SLAVE_MAX_IDLE) { // killing idle slave delete slave; } } } void KLauncher::reparseConfiguration() { KProtocolManager::reparseConfiguration(); foreach (IdleSlave *slave, mSlaveList) { slave->reparseConfiguration(); } } void KLauncher::slotGotOutput() { #ifdef USE_KPROCESS_FOR_KIOSLAVES QProcess *p = static_cast(sender()); QByteArray _stdout = p->readAllStandardOutput(); qCDebug(KLAUNCHER) << _stdout.data(); #endif } void KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus) { #ifdef USE_KPROCESS_FOR_KIOSLAVES QProcess *p = static_cast(sender()); qCDebug(KLAUNCHER) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus; foreach (KLaunchRequest *request, requestList) { if (request->process == p) { #ifdef KLAUNCHER_VERBOSE_OUTPUT qCDebug(KLAUNCHER) << "found KProcess, request done"; #endif if (exitCode == 0 && exitStatus == QProcess::NormalExit) { request->status = KLaunchRequest::Done; } else { request->status = KLaunchRequest::Error; } requestDone(request); request->process = 0; } } delete p; #else Q_UNUSED(exitCode); Q_UNUSED(exitStatus); #endif } void KLauncher::terminate_kdeinit() { qCDebug(KLAUNCHER); #ifndef USE_KPROCESS_FOR_KIOSLAVES klauncher_header request_header; request_header.cmd = LAUNCHER_TERMINATE_KDEINIT; request_header.arg_length = 0; kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); #endif } #if HAVE_XCB KLauncher::XCBConnection KLauncher::getXCBConnection(const QByteArray &_displayName) { const auto displayName = !_displayName.isEmpty() ? _displayName : qgetenv("DISPLAY"); // If cached connection is same as request if (mCached && displayName == mCached.displayName) { // Check error, if so close it, otherwise it's still valid, reuse the cached one if (xcb_connection_has_error(mCached.conn)) { close(); } else { return mCached; } } // At this point, the cached connection can't be reused, make a new connection XCBConnection conn; conn.conn = xcb_connect(displayName.constData(), &conn.screen); if (conn) { // check error first, if so return an empty one if (xcb_connection_has_error(conn.conn)) { xcb_disconnect(conn.conn); return XCBConnection(); } // if it's valid, replace mCached with new connection conn.displayName = displayName; close(); mCached = conn; } return conn; } #endif diff --git a/src/wrapper.cpp b/src/wrapper.cpp index ebe4f3f..21de52c 100644 --- a/src/wrapper.cpp +++ b/src/wrapper.cpp @@ -1,492 +1,492 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Waldo Bastian (c) 1999 Mario Weilguni (c) 2001 Lubos Lunak 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 "klauncher_cmds.h" #include "config-kdeinit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char **environ; // copied from kdeinit/kinit.cpp static const char* displayEnvVarName_c() { // Can't use QGuiApplication::platformName() here, there is no app instance. #if HAVE_X11 return "DISPLAY"; #elif defined(Q_OS_OSX) return "MAC_DISPLAY"; #elif defined(Q_OS_WIN) return "WIN_DISPLAY"; #endif } // adapted from kdeinit/kinit.cpp // WARNING, if you change the socket name, adjust kinit.cpp too static const QString generate_socket_file_name() { #if HAVE_X11 || HAVE_XCB // qt5: see displayEnvVarName_c() QByteArray display = qgetenv(displayEnvVarName_c()); if (display.isEmpty()) { fprintf(stderr, "Error: could not determine $%s.\n", displayEnvVarName_c()); return QString(); } int i; if ((i = display.lastIndexOf('.')) > display.lastIndexOf(':') && i >= 0) { display.truncate(i); } display.replace(':', '_'); #ifdef __APPLE__ // not entirely impossible, so let's leave it display.replace('/', '_'); #endif #else // not using a DISPLAY variable; use an empty string instead QByteArray display = ""; #endif // WARNING, if you change the socket name, adjust kwrapper too const QString socketFileName = QStringLiteral("kdeinit5_%1").arg(QLatin1String(display)); return socketFileName; } /* * Write 'len' bytes from 'buffer' into 'sock'. * returns 0 on success, -1 on failure. */ static int write_socket(int sock, char *buffer, int len) { ssize_t result; int bytes_left = len; while (bytes_left > 0) { result = write(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; } /* * 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 int openSocket() { const QString socketFileName = generate_socket_file_name(); if (socketFileName.isEmpty()) { return -1; } QByteArray socketName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + socketFileName); const char *sock_file = socketName.constData(); struct sockaddr_un server; if (strlen(sock_file) >= sizeof(server.sun_path)) { fprintf(stderr, "Warning: Path of socketfile exceeds UNIX_PATH_MAX.\n"); return -1; } /* * create the socket stream */ int s = socket(PF_UNIX, SOCK_STREAM, 0); if (s < 0) { perror("Warning: socket() failed: "); return -1; } server.sun_family = AF_UNIX; strcpy(server.sun_path, sock_file); kde_socklen_t socklen = sizeof(server); if (connect(s, (struct sockaddr *)&server, socklen) == -1) { fprintf(stderr, "kdeinit5_wrapper: Warning: connect(%s) failed:", sock_file); perror(" "); close(s); return -1; } return s; } static pid_t kwrapper_pid; static void sig_pass_handler(int signo); static void setup_signals(void); static void setup_signal_handler(int signo, int clean) { struct sigaction sa; if (clean) { sa.sa_handler = SIG_DFL; } else { sa.sa_handler = sig_pass_handler; } sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, signo); sa.sa_flags = 0; /* don't use SA_RESTART */ - sigaction(signo, &sa, 0); + sigaction(signo, &sa, nullptr); } static void sig_pass_handler(int signo) { int save_errno = errno; if (signo == SIGTSTP) { kill(kwrapper_pid, SIGSTOP); /* pass the signal to the real process */ } else { /* SIGTSTP wouldn't work ... I don't think is much */ kill(kwrapper_pid, signo); /* of a problem */ } if (signo == SIGCONT) { setup_signals(); /* restore signals */ } else if (signo == SIGCHLD) ; /* nothing, ignore */ else { /* do the default action ( most of them quit the app ) */ setup_signal_handler(signo, 1); raise(signo); /* handle the signal again */ } errno = save_errno; } static void setup_signals() { setup_signal_handler(SIGHUP, 0); setup_signal_handler(SIGINT, 0); setup_signal_handler(SIGQUIT, 0); setup_signal_handler(SIGILL, 0); /* e.g. this one is probably doesn't make sense to pass */ setup_signal_handler(SIGABRT, 0); /* but anyway ... */ setup_signal_handler(SIGFPE, 0); /* SIGKILL can't be handled :( */ setup_signal_handler(SIGSEGV, 0); setup_signal_handler(SIGPIPE, 0); setup_signal_handler(SIGALRM, 0); setup_signal_handler(SIGTERM, 0); setup_signal_handler(SIGUSR1, 0); setup_signal_handler(SIGUSR2, 0); setup_signal_handler(SIGCHLD, 0); /* is this a good idea ??? */ setup_signal_handler(SIGCONT, 0); /* SIGSTOP can't be handled, but SIGTSTP and SIGCONT can */ /* SIGSTOP */ /* which should be enough */ setup_signal_handler(SIGTSTP, 0); setup_signal_handler(SIGTTIN, 0); /* is this a good idea ??? */ setup_signal_handler(SIGTTOU, 0); /* is this a good idea ??? */ /* some more ? */ } static int kwrapper_run(pid_t wrapped, int sock) { klauncher_header header; char *buffer; long pid, status; kwrapper_pid = wrapped; setup_signals(); read_socket(sock, (char *)&header, sizeof(header)); if (header.cmd != LAUNCHER_CHILD_DIED) { fprintf(stderr, "Unexpected response from KInit (response = %ld).\n", header.cmd); exit(255); } buffer = (char *) malloc(header.arg_length); - if (buffer == NULL) { + if (buffer == nullptr) { perror("Error: malloc() failed\n"); exit(255); } read_socket(sock, buffer, header.arg_length); pid = ((long *) buffer)[0]; if (pid != kwrapper_pid) { fprintf(stderr, "Unexpected LAUNCHER_CHILD_DIED from KInit - pid = %ld\n", pid); exit(255); } status = ((long *) buffer)[1]; free(buffer); return (int) status; } int main(int argc, char **argv) { int i; int wrapper = 0; int ext_wrapper = 0; int kwrapper = 0; long arg_count; long env_count; klauncher_header header; char *start, *p, *buffer; char cwd[8192]; - const char *tty = NULL; + const char *tty = nullptr; long avoid_loops = 0; - const char *startup_id = NULL; + const char *startup_id = nullptr; int sock; long size = 0; start = argv[0]; p = start + strlen(argv[0]); while (--p > start) { if (*p == '/') { break; } } if (p > start) { p++; } start = p; if (strcmp(start, "kdeinit5_wrapper") == 0) { wrapper = 1; } else if (strcmp(start, "kshell5") == 0) { ext_wrapper = 1; } else if (strcmp(start, "kwrapper5") == 0) { kwrapper = 1; } else if (strcmp(start, "kdeinit5_shutdown") == 0) { if (argc > 1) { fprintf(stderr, "Usage: %s\n\n", start); fprintf(stderr, "Shuts down kdeinit5 master process and terminates all processes spawned from it.\n"); exit(255); } sock = openSocket(); if (sock < 0) { fprintf(stderr, "Error: Can not contact kdeinit5!\n"); exit(255); } header.cmd = LAUNCHER_TERMINATE_KDE; header.arg_length = 0; write_socket(sock, (char *) &header, sizeof(header)); read_socket(sock, (char *) &header, 1); /* wait for the socket to close */ return 0; } if (wrapper || ext_wrapper || kwrapper) { argv++; argc--; if (argc < 1) { fprintf(stderr, "Usage: %s []\n", start); exit(255); /* usage should be documented somewhere ... */ } start = argv[0]; } sock = openSocket(); if (sock < 0) { /* couldn't contact kdeinit5, start argv[ 0 ] directly */ execvp(argv[ 0 ], argv); fprintf(stderr, "Error: Can not run %s !\n", argv[ 0 ]); exit(255); } if (!wrapper && !ext_wrapper && !kwrapper) { /* was called as a symlink */ avoid_loops = 1; #if defined(WE_ARE_KWRAPPER) kwrapper = 1; #elif defined(WE_ARE_KSHELL) ext_wrapper = 1; #else wrapper = 1; #endif } arg_count = argc; env_count = 0; size += sizeof(long); /* Number of arguments*/ size += strlen(start) + 1; /* Size of first argument. */ for (i = 1; i < argc; i++) { size += strlen(argv[i]) + 1; } if (wrapper) { size += sizeof(long); /* empty envs */ } if (ext_wrapper || kwrapper) { if (!getcwd(cwd, 8192)) { cwd[0] = '\0'; } size += strlen(cwd) + 1; size += sizeof(long); /* Number of env.vars. */ for (; environ[env_count]; env_count++) { int l = strlen(environ[env_count]) + 1; size += l; } if (kwrapper) { tty = ttyname(1); if (!tty || !isatty(2)) { tty = ""; } size += strlen(tty) + 1; } } size += sizeof(avoid_loops); if (!wrapper) { startup_id = getenv("DESKTOP_STARTUP_ID"); - if (startup_id == NULL) { + if (startup_id == nullptr) { startup_id = ""; } size += strlen(startup_id) + 1; } if (wrapper) { header.cmd = LAUNCHER_EXEC_NEW; } else if (kwrapper) { header.cmd = LAUNCHER_KWRAPPER; } else { header.cmd = LAUNCHER_SHELL; } header.arg_length = size; write_socket(sock, (char *) &header, sizeof(header)); buffer = (char *) malloc(size); - if (buffer == NULL) { + if (buffer == nullptr) { perror("Error: malloc() failed."); exit(255); } p = buffer; memcpy(p, &arg_count, sizeof(arg_count)); p += sizeof(arg_count); memcpy(p, start, strlen(start) + 1); p += strlen(start) + 1; for (i = 1; i < argc; i++) { memcpy(p, argv[i], strlen(argv[i]) + 1); p += strlen(argv[i]) + 1; } if (wrapper) { long dummy = 0; memcpy(p, &dummy, sizeof(dummy)); /* empty envc */ p += sizeof(dummy); } if (ext_wrapper || kwrapper) { memcpy(p, cwd, strlen(cwd) + 1); p += strlen(cwd) + 1; memcpy(p, &env_count, sizeof(env_count)); p += sizeof(env_count); for (i = 0; i < env_count; i++) { int l = strlen(environ[i]) + 1; memcpy(p, environ[i], l); p += l; } if (kwrapper) { memcpy(p, tty, strlen(tty) + 1); p += strlen(tty) + 1; } } memcpy(p, &avoid_loops, sizeof(avoid_loops)); p += sizeof(avoid_loops); if (!wrapper) { memcpy(p, startup_id, strlen(startup_id) + 1); p += strlen(startup_id) + 1; } if (p - buffer != size) /* should fail only if you change this source and do */ /* a stupid mistake, it should be assert() actually */ { fprintf(stderr, "Oops. Invalid format.\n"); exit(255); } write_socket(sock, buffer, size); free(buffer); if (read_socket(sock, (char *) &header, sizeof(header)) == -1) { fprintf(stderr, "Communication error with KInit.\n"); exit(255); } if (header.cmd == LAUNCHER_OK) { long pid; buffer = (char *) malloc(header.arg_length); - if (buffer == NULL) { + if (buffer == nullptr) { perror("Error: malloc() failed\n"); exit(255); } read_socket(sock, buffer, header.arg_length); pid = *((long *) buffer); if (!kwrapper) { /* kwrapper shouldn't print any output */ printf("Launched ok, pid = %ld\n", pid); } else { exit(kwrapper_run(pid, sock)); } } else if (header.cmd == LAUNCHER_ERROR) { fprintf(stderr, "KInit could not launch '%s'.\n", start); exit(255); } else { fprintf(stderr, "Unexpected response from KInit (response = %ld).\n", header.cmd); exit(255); } exit(0); }