/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <config_java.h>

#include <signal.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <stdio.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>

#include <desktop/exithelper.h>
#include <osl/process.h>
#include <osl/thread.h>
#include <rtl/bootstrap.h>
#include <rtl/digest.h>
#include <rtl/process.h>
#include <rtl/ustrbuf.h>
#include <sal/main.h>

#include "args.h"
#include "pagein.h"
#include "splashx.h"

#define PIPEDEFAULTPATH      "/tmp"
#define PIPEALTERNATEPATH    "/var/tmp"

/* Easier conversions: rtl_uString to rtl_String */
static rtl_String *ustr_to_str(rtl_uString *pStr)
{
    rtl_String *pOut = NULL;

    rtl_uString2String(&pOut, rtl_uString_getStr(pStr),
            rtl_uString_getLength(pStr), osl_getThreadTextEncoding(), OUSTRING_TO_OSTRING_CVTFLAGS);

    return pOut;
}

/* Easier conversions: char * to rtl_uString */
static rtl_uString *charp_to_ustr(const char *pStr)
{
    rtl_uString *pOut = NULL;

    rtl_string2UString(&pOut, pStr, strlen(pStr), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS);

    return pOut;
}

typedef struct {
    int status_fd;
    oslProcess child;
} ChildInfo;

static int
child_info_get_status_fd(ChildInfo const *info)
{
    return info->status_fd;
}

static void
child_info_destroy(ChildInfo *info)
{
    close (info->status_fd);
    osl_freeProcessHandle (info->child);
    free (info);
}

static ChildInfo * child_spawn(Args *args, sal_Bool bAllArgs, sal_Bool bWithStatus)
{
    rtl_uString *pApp = NULL, *pTmp = NULL;
    rtl_uString **ppArgs;
    sal_uInt32 nArgs, i;
    ChildInfo *info;
    int status_pipe[2];
    oslProcessError nError;

    info = calloc (1, sizeof (ChildInfo));

    /* create pipe */
    if (pipe(status_pipe) < 0)
    {
        fprintf(stderr, "ERROR: no file handles\n");
        exit(1);
    }
    info->status_fd = status_pipe[0];

    /* application name */
    rtl_uString_newFromAscii(&pApp, "file://");
    rtl_uString_newConcat(&pApp, pApp, args->pAppPath);
    rtl_uString_newFromAscii(&pTmp, "soffice.bin");
    rtl_uString_newConcat(&pApp, pApp, pTmp);
    rtl_uString_release(pTmp);
    pTmp = NULL;

    /* copy args */
    nArgs = bAllArgs ? args->nArgsTotal : args->nArgsEnv;
    ppArgs = (rtl_uString **)calloc(nArgs + 1, sizeof(rtl_uString*));
    for (i = 0; i < nArgs; ++i)
        ppArgs[i] = args->ppArgs[i];

    if(bWithStatus)
    {
        char buffer[64];

        /* add the pipe arg */
        snprintf(buffer, 63, "--splash-pipe=%d", status_pipe[1]);
        rtl_uString_newFromAscii( &pTmp, buffer );
        ppArgs[nArgs] = pTmp;
        ++nArgs;
    }

    /* start the main process */
    nError = osl_executeProcess(pApp, ppArgs, nArgs,
                                osl_Process_NORMAL,
                                NULL,
                                NULL,
                                NULL, 0,
                                &info->child );

    if (pTmp)
        rtl_uString_release(pTmp);
    free (ppArgs);

    if (nError != osl_Process_E_None)
    {
        fprintf(stderr, "ERROR %d forking process\n", nError);
        rtl_uString_release(pApp);
        _exit (1);
    }

    rtl_uString_release(pApp);
    close( status_pipe[1] );

    return info;
}

static sal_Bool child_exited_wait(ChildInfo *info, sal_Bool bShortWait)
{
    TimeValue t = { 0, 250 /* ms */ * 1000 * 1000 };
    if (!bShortWait)
        t.Seconds = 1024;

    return osl_joinProcessWithTimeout(info->child, &t) != osl_Process_E_TimedOut;
}

static int child_get_exit_code(ChildInfo *info)
{
    oslProcessInfo inf;

    inf.Code = -1;
    inf.Size = sizeof(inf);

    if (osl_getProcessInfo(info->child, osl_Process_EXITCODE, &inf) != osl_Process_E_None)
    {
        fprintf(stderr, "Warning: failed to fetch libreoffice exit status\n");
        return -1;
    }

    return inf.Code;
}

typedef enum { ProgressContinue, ProgressRestart, ProgressExit } ProgressStatus;

/* Path of the application, with trailing slash. */
static rtl_uString *get_app_path(const char *pAppExec)
{
    char pRealPath[PATH_MAX];
    rtl_uString *pResult;
    sal_Int32 len;
    char* dummy;

    char *pOrigPath = strdup(pAppExec);
    char *pPath = dirname(pOrigPath);

    dummy = realpath(pPath, pRealPath);
    (void)dummy;
    pResult = charp_to_ustr(pRealPath);
    free(pOrigPath);

    len = rtl_uString_getLength(pResult);
    if (len > 0 && rtl_uString_getStr(pResult)[len - 1] != '/')
    {
        rtl_uString *pSlash = NULL;
        rtl_uString_newFromAscii(&pSlash, "/");
        rtl_uString_newConcat(&pResult, pResult, pSlash);
        rtl_uString_release(pSlash);
    }

    return pResult;
}

/* Compute the OOo md5 hash from 'pText' */
static rtl_uString *get_md5hash(rtl_uString *pText)
{
    rtl_uString *pResult = NULL;
    sal_Int32 nCapacity = 100;
    unsigned char *pData = NULL;
    sal_uInt32 nSize = 0;
    rtlDigest digest;
    sal_uInt32 md5_key_len = 0;
    sal_uInt8* md5_buf = NULL;
    sal_uInt32 i = 0;

    if ( !pText )
        return NULL;

    pData = (unsigned char *)rtl_uString_getStr(pText);
    nSize = rtl_uString_getLength(pText) * sizeof(sal_Unicode);
    if (!pData)
        return NULL;

    digest = rtl_digest_create(rtl_Digest_AlgorithmMD5);
    if (!digest)
        return NULL;

    md5_key_len = rtl_digest_queryLength(digest);
    md5_buf = (sal_uInt8 *)calloc(md5_key_len, sizeof(sal_uInt8));

    rtl_digest_init(digest, pData , nSize);
    rtl_digest_update(digest, pData, nSize);
    rtl_digest_get(digest, md5_buf, md5_key_len);
    rtl_digest_destroy(digest);

    /* create hex-value string from the MD5 value to keep
       the string size minimal */
    rtl_uString_new_WithLength(&pResult, nCapacity);
    for (; i < md5_key_len; ++i)
    {
        char val[3];
        snprintf(val, 3, "%x", md5_buf[i]); /* sic! we ignore some of the 0's */

        rtl_uStringbuffer_insert_ascii(&pResult, &nCapacity, rtl_uString_getLength(pResult),
                val, strlen(val));
    }

    /* cleanup */
    free(md5_buf);

    return pResult;
}

/* Construct the pipe name */
static rtl_uString *get_pipe_path(rtl_uString *pAppPath)
{
    rtl_uString *pPath = NULL, *pTmp = NULL, *pUserInstallation = NULL;
    rtl_uString *pResult = NULL, *pBasePath = NULL, *pAbsUserInstallation = NULL;
    rtlBootstrapHandle handle;
    rtl_uString *pMd5hash = NULL;
    sal_Unicode pUnicode[RTL_USTR_MAX_VALUEOFINT32];

    /* setup bootstrap filename */
    rtl_uString_newFromAscii(&pPath, "file://");
    rtl_uString_newConcat(&pPath, pPath, pAppPath);
    rtl_uString_newConcat(&pPath, pPath, pTmp);
    rtl_uString_newFromAscii(&pTmp, SAL_CONFIGFILE("bootstrap"));
    rtl_uString_newConcat(&pPath, pPath, pTmp);

    /* read userinstallation value */
    handle = rtl_bootstrap_args_open(pPath);

    rtl_uString_newFromAscii(&pTmp, "UserInstallation");
    rtl_bootstrap_get_from_handle(handle, pTmp, &pUserInstallation, NULL);

    rtl_bootstrap_args_close(handle);

    /* turn it into an absolute path - unwinding symlinks etc. */
    if (osl_getProcessWorkingDir(&pBasePath) ||
        osl_getAbsoluteFileURL(pBasePath, pUserInstallation, &pAbsUserInstallation))
        rtl_uString_newFromString(&pAbsUserInstallation, pUserInstallation);

    /* create the pipe name */
    pMd5hash = get_md5hash(pAbsUserInstallation);
    if (!pMd5hash)
        rtl_uString_new(&pMd5hash);

    if (access(PIPEDEFAULTPATH, W_OK) == 0)
    {
        rtl_uString_newFromAscii(&pResult, PIPEDEFAULTPATH);
    }
    else if (access(PIPEALTERNATEPATH, W_OK) == 0)
    {
        rtl_uString_newFromAscii(&pResult, PIPEALTERNATEPATH);
    }
    else
    {
        fprintf(stderr, "ERROR: no valid pipe path found.\n");
        exit(1);
    }

    rtl_uString_newFromAscii(&pTmp, "/OSL_PIPE_");
    rtl_uString_newConcat(&pResult, pResult, pTmp);

    rtl_ustr_valueOfInt32(pUnicode, (int)getuid(), 10);
    rtl_uString_newFromStr(&pTmp, pUnicode);
    rtl_uString_newConcat(&pResult, pResult, pTmp);

    rtl_uString_newFromAscii(&pTmp, "_SingleOfficeIPC_");
    rtl_uString_newConcat(&pResult, pResult, pTmp);

    rtl_uString_newConcat(&pResult, pResult, pMd5hash);

    /* cleanup */
    rtl_uString_release(pMd5hash);
    rtl_uString_release(pPath);
    rtl_uString_release(pTmp);

    if (pBasePath)
        rtl_uString_release(pBasePath);

    rtl_uString_release(pUserInstallation);
    rtl_uString_release(pAbsUserInstallation);

    return pResult;
}

/* Get fd of the pipe of the already running OOo. */
static int connect_pipe(rtl_uString *pPipePath)
{
    int fd;
    size_t len;
    struct sockaddr_un addr;

    rtl_String *pPipeStr = ustr_to_str(pPipePath);

    memset(&addr, 0, sizeof(addr));

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return fd;

    (void)fcntl(fd, F_SETFD, FD_CLOEXEC);

    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, rtl_string_getStr(pPipeStr), sizeof(addr.sun_path) - 1);
    rtl_string_release(pPipeStr);

/* cut / paste from osl's pipe.c */
#if defined(FREEBSD)
    len = SUN_LEN(&addr);
#else
    len = sizeof(addr);
#endif

    if (connect(fd, (struct sockaddr *)&addr, len) < 0)
    {
        close(fd);
        fd = -1;
    }
    return fd;
}

/* Escape: "," -> "\\,", "\0" -> "\\0", "\\" -> "\\\\" */
static rtl_uString *escape_path(rtl_uString const *pToEscape)
{
    rtl_uString *pBuffer = NULL;
    sal_Int32 nCapacity = 1000;
    sal_Int32 i = 0;
    sal_Int32 nEscapeLength = rtl_uString_getLength(pToEscape);

    rtl_uString_new_WithLength(&pBuffer, nCapacity);

    for (; i < nEscapeLength; ++i)
    {
        sal_Unicode c = pToEscape->buffer[i];
        switch (c)
        {
            case '\0':
                rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
                        rtl_uString_getLength(pBuffer),
                        RTL_CONSTASCII_STRINGPARAM("\\0"));
                break;
            case ',':
                rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
                        rtl_uString_getLength(pBuffer),
                        RTL_CONSTASCII_STRINGPARAM("\\,"));
                break;
            case '\\':
                rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
                        rtl_uString_getLength(pBuffer),
                        RTL_CONSTASCII_STRINGPARAM("\\\\"));
                break;
            default:
                rtl_uStringbuffer_insert(&pBuffer, &nCapacity,
                        rtl_uString_getLength(pBuffer),
                        &c, 1);
        }
    }

    return pBuffer;
}

/* Send args to the LO instance (using the 'fd' file descriptor) */
static sal_Bool send_args(int fd, rtl_uString const *pCwdPath)
{
    rtl_uString *pBuffer = NULL, *pTmp = NULL;
    sal_Int32 nCapacity = 1000;
    rtl_String *pOut = NULL;
    sal_Bool bResult;
    size_t nLen;
    rtl_uString *pEscapedCwdPath = escape_path(pCwdPath);
    sal_uInt32 nArg = 0;
    sal_uInt32 nArgCount = rtl_getAppCommandArgCount();

    rtl_uString_new_WithLength(&pBuffer, nCapacity);
    rtl_uString_new(&pTmp);

    rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
            rtl_uString_getLength(pBuffer),
            RTL_CONSTASCII_STRINGPARAM("InternalIPC::Arguments"));

    if (rtl_uString_getLength(pEscapedCwdPath))
    {
        rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
            rtl_uString_getLength(pBuffer),
            RTL_CONSTASCII_STRINGPARAM("1"));

        rtl_uStringbuffer_insert(&pBuffer, &nCapacity,
                    rtl_uString_getLength(pBuffer),
                    rtl_uString_getStr(pEscapedCwdPath),
                    rtl_uString_getLength(pEscapedCwdPath));
    }
    else
    {
        rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
            rtl_uString_getLength(pBuffer),
            RTL_CONSTASCII_STRINGPARAM("0"));
    }

    for (nArg = 0; nArg < nArgCount; ++nArg)
    {
        rtl_uString *pEscapedTmp = NULL;
        rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity,
                rtl_uString_getLength(pBuffer),
                ",", 1);

        rtl_getAppCommandArg(nArg, &pTmp);

        pEscapedTmp = escape_path(pTmp);

        rtl_uStringbuffer_insert(&pBuffer, &nCapacity,
                rtl_uString_getLength(pBuffer),
                rtl_uString_getStr(pEscapedTmp),
                rtl_uString_getLength(pEscapedTmp));

        rtl_uString_release(pEscapedTmp);
    }

    if (!rtl_convertUStringToString(
             &pOut, rtl_uString_getStr(pBuffer),
             rtl_uString_getLength(pBuffer), RTL_TEXTENCODING_UTF8,
             (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
               | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
    {
        fprintf(stderr, "ERROR: cannot convert arguments to UTF-8\n");
        exit(1);
    }

    nLen = rtl_string_getLength(pOut) + 1;
    ssize_t n = write(fd, rtl_string_getStr(pOut), nLen);
    bResult = (n >= 0 && (size_t) n == nLen);

    if ( bResult )
    {
        char resp[SAL_N_ELEMENTS("InternalIPC::ProcessingDone")];
        n = read(fd, resp, SAL_N_ELEMENTS(resp));
        bResult = n == SAL_N_ELEMENTS(resp)
            && (memcmp(
                    resp, "InternalIPC::ProcessingDone",
                    SAL_N_ELEMENTS(resp))
                == 0);
    }

    /* cleanup */
    rtl_uString_release(pEscapedCwdPath);
    rtl_uString_release(pBuffer);
    rtl_uString_release(pTmp);
    rtl_string_release(pOut);

    return bResult;
}


#define BUFFER_LEN 255

/* Read the percent to show in splash. */
static ProgressStatus read_percent(ChildInfo const *info, int *pPercent)
{
    static char pBuffer[BUFFER_LEN + 1];
    static char *pNext = pBuffer;
    static ssize_t nRead = 0;

    char *pBegin;
    char *pIter;
    char c;

    /* from the last call */
    int nNotProcessed = nRead - (pNext - pBuffer);
    if (nNotProcessed >= BUFFER_LEN)
        return ProgressContinue;

    memmove(pBuffer, pNext, nNotProcessed);

    /* read data */
    ssize_t nThisRead = read(child_info_get_status_fd(info),
                             pBuffer + nNotProcessed, BUFFER_LEN - nNotProcessed);

    if (nThisRead < 0)
    {
        if (errno == EINTR)
            return ProgressContinue;

        return ProgressExit;
    }

    nRead = nThisRead + nNotProcessed;
    pBuffer[nRead] = '\0';

    /* skip old data */
    pBegin = pBuffer;
    pNext = pBuffer;
    for (pIter = pBuffer; *pIter; ++pIter)
    {
        if (*pIter == '\n')
        {
            pBegin = pNext;
            pNext = pIter + 1;
        }
    }

    if (!strncasecmp(pBegin, "end", 3))
        return ProgressExit;
    else if (!strncasecmp(pBegin, "restart", 7))
        return ProgressRestart;
    else if (sscanf(pBegin, "%d%c", pPercent, &c) == 2 && c == '%')
        return ProgressContinue;

    /* unexpected - let's exit the splash to be safe */
    return ProgressExit;
}

/* Simple system check. */
static void system_checks(void)
{
#ifdef LINUX
    struct stat buf;

    /* check proc is mounted - lots of things fail otherwise */
    if (stat("/proc/version", &buf) != 0)
    {
        fprintf(stderr, "ERROR: /proc not mounted - LibreOffice is unlikely to work well if at all\n");
        exit(1);
    }
#endif
}

static void exec_pagein (Args *args)
{
    rtl_String * path = ustr_to_str(args->pAppPath);
    pagein_execute(rtl_string_getStr(path), "pagein-common");

    if (args->pPageinType)
        pagein_execute(rtl_string_getStr(path), args->pPageinType);

    rtl_string_release(path);
}

#if HAVE_FEATURE_JAVA

static void extend_library_path(const char *new_element)
{
    rtl_uString *pEnvName=NULL, *pOrigEnvVar=NULL, *pNewEnvVar=NULL;

    rtl_uString_newFromAscii(&pEnvName, "LD_LIBRARY_PATH");
    rtl_string2UString(&pNewEnvVar, new_element, strlen(new_element), RTL_TEXTENCODING_UTF8, OSTRING_TO_OUSTRING_CVTFLAGS);

    osl_getEnvironment(pEnvName, &pOrigEnvVar);
    if (pOrigEnvVar && pOrigEnvVar->length)
    {
        rtl_uString *pDelim = NULL;
        rtl_uString_newFromAscii(&pDelim, ":");
        rtl_uString_newConcat(&pNewEnvVar, pNewEnvVar, pDelim);
        rtl_uString_newConcat(&pNewEnvVar, pNewEnvVar, pOrigEnvVar);
        rtl_uString_release(pDelim);
    }

    osl_setEnvironment(pEnvName, pNewEnvVar);

    if (pOrigEnvVar)
        rtl_uString_release(pOrigEnvVar);

    rtl_uString_release(pNewEnvVar);
    rtl_uString_release(pEnvName);
}

static void exec_javaldx(Args *args)
{
    char newpath[4096];
    sal_uInt32 nArgs;
    rtl_uString *pApp;
    rtl_uString **ppArgs;
    rtl_uString *pTmp, *pTmp2;

    oslProcess javaldx = NULL;
    oslFileHandle fileOut = NULL;
    oslProcessError err;

    ppArgs = (rtl_uString **)calloc(args->nArgsEnv + 2, sizeof(rtl_uString*));

    for (nArgs = 0; nArgs < args->nArgsEnv; ++nArgs)
        ppArgs[nArgs] = args->ppArgs[nArgs];

    /* Use absolute path to redirectrc */
    pTmp = NULL;
    rtl_uString_newFromAscii(&pTmp, "-env:INIFILENAME=vnd.sun.star.pathname:");
    rtl_uString_newConcat(&pTmp, pTmp, args->pAppPath);
    pTmp2 = NULL;
    rtl_uString_newFromAscii(&pTmp2, "redirectrc");
    rtl_uString_newConcat(&pTmp, pTmp, pTmp2);
    ppArgs[nArgs] = pTmp;
    rtl_uString_release (pTmp2);
    nArgs++;

    /* And also to javaldx */
    pApp = NULL;
    rtl_uString_newFromAscii(&pApp, "file://");
    rtl_uString_newConcat(&pApp, pApp, args->pAppPath);
    pTmp = NULL;
    rtl_uString_newFromAscii(&pTmp, "javaldx");
    rtl_uString_newConcat(&pApp, pApp, pTmp);
    rtl_uString_release(pTmp);

    err = osl_executeProcess_WithRedirectedIO(pApp, ppArgs, nArgs,
                                              osl_Process_NORMAL,
                                              NULL, // security
                                              NULL, // work dir
                                              NULL, 0,
                                              &javaldx, // process handle
                                              NULL,
                                              &fileOut,
                                              NULL);

    rtl_uString_release(ppArgs[nArgs-1]);
    rtl_uString_release(pApp);
    free(ppArgs);

    if(err != osl_Process_E_None)
    {
        putenv("javaldx_warn=failed to launch javaldx - java may not function correctly");

        if (javaldx)
            osl_freeProcessHandle(javaldx);
        if (fileOut)
            osl_closeFile(fileOut);
        return;
    }
    else
    {
        char *chomp;
        sal_uInt64 bytes_read;

        /* Magically osl_readLine doesn't work with pipes with E_SPIPE - so be this lame instead: */
        while (osl_readFile (fileOut, newpath, SAL_N_ELEMENTS (newpath), &bytes_read) == osl_File_E_INTR);

        if (bytes_read <= 0)
        {
            putenv("javaldx_warn=failed to read path from javaldx");

            if (javaldx)
                osl_freeProcessHandle(javaldx);

            if (fileOut)
                osl_closeFile(fileOut);

            return;
        }

        newpath[bytes_read] = '\0';

        if ((chomp = strstr (newpath, "\n")))
            *chomp = '\0';
    }

    if (newpath[0] != '\0') {
        extend_library_path(newpath);
    }

    if (javaldx)
        osl_freeProcessHandle(javaldx);

    if (fileOut)
        osl_closeFile(fileOut);
}

#endif

// has to be a global :(
static oslProcess * volatile g_pProcess = NULL;

static void sigterm_handler(int ignored)
{
    (void) ignored;

    if (g_pProcess) {
        int SigTermSucceded = 0;
        oslProcessInfo info;
        info.Size = sizeof(oslProcessInfo);

        // forward SIGTERM to soffice.bin and give it a chance to semi-gracefully exit
        // enough to remove named pipe
        if (osl_getProcessInfo(g_pProcess, osl_Process_IDENTIFIER, &info) == osl_Process_E_None) {
            TimeValue delay = { 1, 0 }; // 1 sec
            SigTermSucceded = kill(info.Ident, SIGTERM) == 0 &&
                              osl_joinProcessWithTimeout(g_pProcess, &delay) == osl_Process_E_None;
        }

        // didn't work, SIGKILL instead
        if (!SigTermSucceded) {
            osl_terminateProcess(g_pProcess); // uses SIGKILL to terminate soffice.bin
            osl_joinProcess(g_pProcess);
        }
    }

    _exit(255);
}


SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
{
    sal_Bool bSentArgs = sal_False;
    const char* pUsePlugin;
    rtl_uString *pPipePath = NULL;
    Args *args;
    int status = 0;
    struct splash* splash = NULL;
    struct sigaction sigpipe_action;
    struct sigaction sigterm_action;

    /* turn SIGPIPE into an error */
    memset(&sigpipe_action, 0, sizeof(struct sigaction));
    sigpipe_action.sa_handler = SIG_IGN;
    sigemptyset(&sigpipe_action.sa_mask);
    sigaction(SIGPIPE, &sigpipe_action, NULL);
    memset(&sigterm_action, 0, sizeof(struct sigaction));
    sigterm_action.sa_handler = &sigterm_handler;
    sigemptyset(&sigterm_action.sa_mask);
    sigaction(SIGTERM, &sigterm_action, NULL);

    args = args_parse();
    args->pAppPath = get_app_path(argv[0]);
    if (!args->pAppPath)
    {
        fprintf(stderr, "ERROR: Can't read app link\n");
        exit(1);
    }

#ifndef ENABLE_QUICKSTART_LIBPNG
    /* we can't load and render it anyway */
    args->bInhibitSplash = sal_True;
#endif

    pUsePlugin = getenv("SAL_USE_VCLPLUGIN");
    if (pUsePlugin && !strcmp(pUsePlugin, "svp"))
        args->bInhibitSplash = sal_True;

    if (!args->bInhibitPipe && !getenv("LIBO_FLATPAK"))
    {
        int fd = 0;
        pPipePath = get_pipe_path(args->pAppPath);

        if ((fd=connect_pipe(pPipePath)) >= 0)
        {
            // Wait for answer
            char resp[27]; // strlen("InternalIPC::SendArguments") + 1
            ssize_t n = read(fd, resp, SAL_N_ELEMENTS(resp));
            if (n == (ssize_t) SAL_N_ELEMENTS(resp) &&
                (memcmp(resp, "InternalIPC::SendArguments",
                        SAL_N_ELEMENTS(resp) - 1) == 0))
            {
                rtl_uString *pCwdPath = NULL;
                osl_getProcessWorkingDir(&pCwdPath);

                // Then send args
                bSentArgs = send_args(fd, pCwdPath);
            }

            close(fd);
        }
    }

    if (!bSentArgs)
    {
        /* we have to prepare for, and exec the binary */
        int nPercent = 0;
        ChildInfo *info;
        sal_Bool bAllArgs = sal_True;
        sal_Bool bShortWait, bRestart;

        /* sanity check pieces */
        system_checks();

        /* load splash image and create window */
        if (!args->bInhibitSplash)
            splash = splash_create(args->pAppPath, argc, argv);

        /* pagein */
        if (!args->bInhibitPagein)
            exec_pagein(args);

        /* javaldx */
#if HAVE_FEATURE_JAVA
        if (!args->bInhibitJavaLdx)
            exec_javaldx (args);
#endif

        do
        {
            bRestart = sal_False;

            /* fast updates if we have somewhere to update it to */
            bShortWait = splash ? sal_True : sal_False;

            /* Periodically update the splash & the percent according
               to what status_fd says, poll quickly only while starting */
            info = child_spawn (args, bAllArgs, bShortWait);
            g_pProcess = info->child;

            while (!child_exited_wait(info, bShortWait))
            {
                ProgressStatus eResult;

                splash_draw_progress(splash, nPercent);
                eResult = read_percent(info, &nPercent);

                if (eResult != ProgressContinue)
                {
                    splash_destroy(splash);
                    splash = NULL;
                    bShortWait = sal_False;
                }
            }

            status = child_get_exit_code(info);
            g_pProcess = NULL; // reset

            switch (status)
            {
                case EXITHELPER_CRASH_WITH_RESTART: // re-start with just -env: parameters
                    bRestart = sal_True;
                    bAllArgs = sal_False;
                    break;
                case EXITHELPER_NORMAL_RESTART: // re-start with all arguments
                    bRestart = sal_True;
                    bAllArgs = sal_True;
                    break;
                default:
                    break;
            }

            child_info_destroy(info);
        } while (bRestart);
    }

    /* cleanup */
    if (pPipePath)
        rtl_uString_release(pPipePath);

    args_free(args);

    return status;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
