Warning, /frameworks/kinit/src/kdeinit/kinit_mac.mm is written in an unsupported language. File is not indexed.

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 1999 Mario Weilguni <mweilguni@sime.com>
0005     SPDX-FileCopyrightText: 2001 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2015, 2016 René J.V. Bertin <rjvbertin@gmail.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include <config-kdeinit.h>
0012 
0013 #include <sys/types.h>
0014 #include <sys/time.h>
0015 #include <sys/resource.h>
0016 #include <sys/stat.h>
0017 #include <sys/socket.h>
0018 #include <sys/un.h>
0019 #include <sys/wait.h>
0020 #if HAVE_SYS_SELECT_H
0021 #include <sys/select.h>     // Needed on some systems.
0022 #endif
0023 
0024 #include <ctype.h>
0025 #include <errno.h>
0026 #include <fcntl.h>
0027 #include "proctitle.h"
0028 #include <dlfcn.h>
0029 #include <signal.h>
0030 #include <stdio.h>
0031 #include <stdlib.h>
0032 #include <string.h>
0033 #include <unistd.h>
0034 #include <locale.h>
0035 
0036 #include <QtCore/QLibrary>
0037 #include <QtCore/QString>
0038 #include <QtCore/QFile>
0039 #include <QtCore/QDate>
0040 #include <QtCore/QDir>
0041 #include <QtCore/QFileInfo>
0042 #include <QtCore/QRegExp>
0043 #include <QCoreApplication>
0044 #include <QFont>
0045 #include <KConfig>
0046 #include <KLocalizedString>
0047 #include <QDebug>
0048 #include <QSaveFile>
0049 
0050 #import <Foundation/Foundation.h>
0051 #import <AppKit/AppKit.h>
0052 #include <CoreFoundation/CFBundle.h>
0053 #include <CoreFoundation/CFString.h>
0054 #include <CoreFoundation/CFURL.h>
0055 #include <crt_externs.h> // for _NSGetArgc and friends
0056 #include <mach-o/dyld.h> // for _NSGetExecutablePath
0057 
0058 #include <kinit_version.h>
0059 
0060 #include "klauncher_cmds.h"
0061 
0062 #include <QStandardPaths>
0063 
0064 #include "kinit.h"
0065 #include "kinit_mac.h"
0066 
0067 extern char **environ;
0068 
0069 static int show_message_box( const QString &title, const QString &message )
0070 {
0071     int (*_CGSDefaultConnection_p)();
0072     _CGSDefaultConnection_p = (int (*)()) dlsym(RTLD_DEFAULT, "_CGSDefaultConnection");
0073     if (!_CGSDefaultConnection_p) {
0074         NSLog(@"qt_fatal_message_box(%@,%@): couldn't load _CGSDefaultConnection: %s",
0075             [NSString stringWithCString:message.toUtf8().constData() encoding:NSUTF8StringEncoding],
0076             [NSString stringWithCString:title.toUtf8().constData() encoding:NSUTF8StringEncoding], dlerror());
0077     }
0078     if (_CGSDefaultConnection_p && (*_CGSDefaultConnection_p)()) {
0079         @autoreleasepool {
0080             NSAlert* alert = [[[NSAlert alloc] init] autorelease];
0081             NSString *msg, *tit;
0082             @synchronized([NSAlert class]){
0083                 [alert addButtonWithTitle:@"OK"];
0084                 [alert setAlertStyle:NSCriticalAlertStyle];
0085                 [alert setMessageText:@"" ];
0086                 if( !(msg = [NSString stringWithCString:message.toUtf8().constData() encoding:NSUTF8StringEncoding]) ){
0087                     msg = [NSString stringWithCString:message.toLatin1().constData() encoding:NSASCIIStringEncoding];
0088                 }
0089                 if( !(tit = [NSString stringWithCString:title.toUtf8().constData() encoding:NSUTF8StringEncoding]) ){
0090                     tit = [NSString stringWithCString:title.toLatin1().constData() encoding:NSASCIIStringEncoding];
0091                 }
0092                 if( msg ){
0093                     [alert setInformativeText:msg];
0094                 }
0095                 else{
0096                     NSLog( @"msg=%@ tit=%@", msg, tit );
0097                 }
0098                 [[alert window] setTitle:tit];
0099                 return NSAlertSecondButtonReturn == [alert runModal];
0100             }
0101         }
0102     }
0103     return 0;
0104 }
0105 
0106 static void exitWithErrorMsg(const QString &errorMsg, bool doExit)
0107 {
0108     fprintf(stderr, "%s\n", errorMsg.toLocal8Bit().data());
0109     QByteArray utf8ErrorMsg = errorMsg.toUtf8();
0110     d.result = 3; // Error with msg
0111     write(d.fd[1], &d.result, 1);
0112     int l = utf8ErrorMsg.length();
0113     write(d.fd[1], &l, sizeof(int));
0114     write(d.fd[1], utf8ErrorMsg.data(), l);
0115     close(d.fd[1]);
0116     show_message_box(QStringLiteral("kdeinit5"), errorMsg);
0117     if (doExit) {
0118         exit(255);
0119     }
0120 }
0121 
0122 pid_t launch(int argc, const char *_name, const char *args,
0123                     const char *cwd, int envc, const char *envs,
0124                     bool reset_env, const char *tty, bool avoid_loops,
0125                     const char *startup_id_str)  // krazy:exclude=doublequote_chars
0126 {
0127     QString bin, libpath;
0128     QByteArray name;
0129     QByteArray execpath;
0130 
0131     if (_name[0] != '/') {
0132         name = _name;
0133         bin = QFile::decodeName(name);
0134         execpath = execpath_avoid_loops(name, envc, envs, avoid_loops);
0135     } else {
0136         name = _name;
0137         bin = QFile::decodeName(name);
0138         name = name.mid(name.lastIndexOf('/') + 1);
0139 
0140         // FIXME: this .so extension stuff is very Linux-specific
0141         // a .so extension can occur on OS X, but the typical extension is .dylib
0142         if (bin.endsWith(QStringLiteral(".so")) || bin.endsWith(QStringLiteral(".dylib"))) {
0143             libpath = bin;
0144         } else {
0145             execpath = _name;
0146         }
0147     }
0148 #ifndef NDEBUG
0149     fprintf(stderr, "kdeinit5: preparing to launch '%s'\n", libpath.isEmpty()
0150             ? execpath.constData() : libpath.toUtf8().constData());
0151 #endif
0152     if (!args) {
0153         argc = 1;
0154     }
0155 
0156     // do certain checks before forking
0157     if (execpath.isEmpty()) {
0158         QString errorMsg = i18n("Could not find '%1' executable.", QFile::decodeName(_name));
0159         exitWithErrorMsg(errorMsg, false);
0160         d.result = 3;
0161         d.fork = 0;
0162         return d.fork;
0163     } else if (!libpath.isEmpty()) {
0164         QString errorMsg = i18n("Launching library '%1' is not supported on OS X.", libpath);
0165         exitWithErrorMsg(errorMsg, false);
0166         d.result = 3;
0167         d.fork = 0;
0168         return d.fork;
0169     }
0170 
0171     if (0 > pipe(d.fd)) {
0172         perror("kdeinit5: pipe() failed");
0173         d.result = 3;
0174         d.errorMsg = i18n("Unable to start new process.\n"
0175                           "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();
0176         d.fork = 0;
0177         return d.fork;
0178     }
0179 
0180     // find out this path before forking, doing it afterwards
0181     // crashes on some platforms, notably OSX
0182     const QString bundlepath = QStandardPaths::findExecutable(QFile::decodeName(execpath));
0183 
0184     const QString argvexe = QStandardPaths::findExecutable(QLatin1String(_name));
0185 
0186     d.errorMsg = 0;
0187     d.fork = fork();
0188     switch (d.fork) {
0189     case -1:
0190         perror("kdeinit5: fork() failed");
0191         d.result = 3;
0192         d.errorMsg = i18n("Unable to create new process.\n"
0193                           "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();
0194         close(d.fd[0]);
0195         close(d.fd[1]);
0196         d.fork = 0;
0197         break;
0198     case 0: {
0199         /** Child **/
0200         close(d.fd[0]);
0201         close_fds();
0202         reset_oom_protect();
0203 
0204         // Try to chdir, either to the requested directory or to the user's document path by default.
0205         // We ignore errors - if you write a desktop file with Exec=foo and Path=/doesnotexist,
0206         // we still want to execute `foo` even if the chdir() failed.
0207         if (cwd && *cwd) {
0208             (void)chdir(cwd);
0209         }
0210 
0211         if (reset_env) { // KWRAPPER/SHELL
0212 
0213             QList<QByteArray> unset_envs;
0214             for (int tmp_env_count = 0;
0215                     environ[tmp_env_count];
0216                     tmp_env_count++) {
0217                 unset_envs.append(environ[ tmp_env_count ]);
0218             }
0219             for (const QByteArray &tmp : std::as_const(unset_envs)) {
0220                 int pos = tmp.indexOf('=');
0221                 if (pos >= 0) {
0222                     unsetenv(tmp.left(pos).constData());
0223                 }
0224             }
0225         }
0226 
0227         for (int i = 0;  i < envc; i++) {
0228             putenv((char *)envs);
0229             while (*envs != 0) {
0230                 envs++;
0231             }
0232             envs++;
0233         }
0234 
0235         {
0236             QByteArray procTitle;
0237             // launching an executable: can do an exec directly
0238             d.argv = (char **) malloc(sizeof(char *) * (argc + 1));
0239             if (!argvexe.isEmpty()) {
0240                 QByteArray cstr = argvexe.toLocal8Bit();
0241                 d.argv[0] = strdup(cstr.data());
0242             } else {
0243                 d.argv[0] = (char *) _name;
0244             }
0245             for (int i = 1;  i < argc; i++) {
0246                 d.argv[i] = (char *) args;
0247                 procTitle += ' ';
0248                 procTitle += (char *) args;
0249                 while (*args != 0) {
0250                     args++;
0251                 }
0252                 args++;
0253             }
0254             d.argv[argc] = 0;
0255 
0256 #ifndef SKIP_PROCTITLE
0257             /** Give the process a new name **/
0258             proctitle_set("%s%s", name.data(), procTitle.data() ? procTitle.data() : "");
0259 #endif
0260         }
0261 
0262         if (!execpath.isEmpty()) {
0263             // we're launching an executable; even after a fork and being exec'ed, that
0264             // executable is allowed to use non-POSIX APIs.
0265             d.result = 2; // Try execing
0266             write(d.fd[1], &d.result, 1);
0267 
0268             // We set the close on exec flag.
0269             // Closing of d.fd[1] indicates that the execvp succeeded!
0270             fcntl(d.fd[1], F_SETFD, FD_CLOEXEC);
0271 
0272             setup_tty(tty);
0273 
0274             QByteArray executable = execpath;
0275             if (!bundlepath.isEmpty()) {
0276                 executable = QFile::encodeName(bundlepath);
0277             }
0278             // TODO: we probably want to use [[NSProcessInfo processInfo] setProcessName:NSString*] here to
0279             // make the new app show up under its own name in non-POSIX process listings.
0280 
0281             if (!executable.isEmpty()) {
0282 #ifndef NDEBUG
0283                 qDebug() << "execvp" << executable;
0284                 for (int i = 0 ; i < argc ; ++i) {
0285                     qDebug() << "arg #" << i << "=" << d.argv[i];
0286                 }
0287 #endif
0288                 // attempt to the correct application name
0289                 QFileInfo fi(QString::fromUtf8(executable));
0290                 [[NSProcessInfo processInfo] setProcessName:(NSString*)fi.baseName().toCFString()];
0291                 qApp->setApplicationName(fi.baseName());
0292                 execvp(executable.constData(), d.argv);
0293             }
0294 
0295             d.result = 1; // Error
0296             write(d.fd[1], &d.result, 1);
0297             close(d.fd[1]);
0298             exit(255);
0299         }
0300 
0301         break;
0302     }
0303     default:
0304         /** Parent **/
0305         close(d.fd[1]);
0306         bool exec = false;
0307         for (;;) {
0308             d.n = read(d.fd[0], &d.result, 1);
0309             if (d.n == 1) {
0310                 if (d.result == 2) {
0311 #ifndef NDEBUG
0312                     //fprintf(stderr, "kdeinit5: no kdeinit module, trying exec....\n");
0313 #endif
0314                     exec = true;
0315                     continue;
0316                 }
0317                 if (d.result == 3) {
0318                     int l = 0;
0319                     d.n = read(d.fd[0], &l, sizeof(int));
0320                     if (d.n == sizeof(int)) {
0321                         QByteArray tmp;
0322                         tmp.resize(l + 1);
0323                         d.n = read(d.fd[0], tmp.data(), l);
0324                         tmp[l] = 0;
0325                         if (d.n == l) {
0326                             d.errorMsg = tmp;
0327                         }
0328                     }
0329                 }
0330                 // Finished
0331                 break;
0332             }
0333             if (d.n == -1) {
0334                 if (errno == ECHILD) {  // a child died.
0335                     continue;
0336                 }
0337                 if (errno == EINTR || errno == EAGAIN) { // interrupted or more to read
0338                     continue;
0339                 }
0340             }
0341             if (d.n == 0) {
0342                 if (exec) {
0343                     d.result = 0;
0344                 } else {
0345                     fprintf(stderr, "kdeinit5: (%s %s) Pipe closed unexpectedly", name.constData(), execpath.constData());
0346                     perror("kdeinit5: Pipe closed unexpectedly");
0347                     d.result = 1; // Error
0348                 }
0349                 break;
0350             }
0351             perror("kdeinit5: Error reading from pipe");
0352             d.result = 1; // Error
0353             break;
0354         }
0355         close(d.fd[0]);
0356     }
0357     return d.fork;
0358 }
0359 
0360 /**
0361  Calling CoreFoundation and other non-POSIX APIs (which is unavoidable) has always caused issues
0362  with fork/exec on Mac OS X, but as of 10.5 is explicitly disallowed with an exception. As a
0363  result, in the case where we would normally fork and then dlopen code, or continue
0364  to run other code, we must now fork-and-exec, and even then we need to use a helper (proxy)
0365  to launch the actual application we wish to launch, a proxy that will only have used POSIX APIs.
0366  This probably cancels the whole idea of preloading libraries, but it is as it is.
0367  Note that this function is called only when kdeinit5 is forking itself,
0368  in order to disappear into the background.
0369 */
0370 // Copied from kkernel_mac.cpp
0371 void mac_fork_and_reexec_self()
0372 {
0373     int argc = *_NSGetArgc();
0374     char **argv = *_NSGetArgv();
0375     char *newargv[argc + 2];
0376     char progname[PATH_MAX];
0377     uint32_t buflen = PATH_MAX;
0378     _NSGetExecutablePath(progname, &buflen);
0379 
0380     for (int i = 0; i < argc; i++) {
0381         newargv[i] = argv[i];
0382     }
0383 
0384     newargv[argc] = (char*)"--nofork";
0385     newargv[argc + 1] = 0;
0386 
0387     int x_fork_result = fork();
0388     switch (x_fork_result) {
0389 
0390     case -1:
0391 #ifndef NDEBUG
0392         fprintf(stderr, "Mac OS X workaround fork() failed!\n");
0393 #endif
0394         ::_exit(255);
0395         break;
0396 
0397     case 0:
0398         // Child
0399         execvp(progname, newargv);
0400         break;
0401 
0402     default:
0403         // Parent
0404         _exit(0);
0405         break;
0406 
0407     }
0408 }