/*
 *  Generic Monitor plugin for the Xfce4 panel
 *  Spawn - Spawn a process and capture its output
 *  Copyright (c) 2004 Roger Seguin <roger_seguin@msn.com>
 *                                  <http://rmlx.dyndns.org>
 *  Copyright (c) 2006 Julien Devemy <jujucece@gmail.com>
 *  
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.

 *  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
 *  Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* Posix-compliance to make sure that only the calling thread is
   duplicated, not the whole process (e.g Solaris) */

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199506L
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif

#include "cmdspawn.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <strings.h>
#include <string.h>
#include <poll.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/wait.h>


/**********************************************************************/
static int ParseCmdline (const char *const p_pcCmdLine, char ***p_pppcArgv, ...
 /* &argc, */
 /* acError, ErrorBufferSize */ )
/**********************************************************************/
 /* Split a commandline string into an argv-type dynamically allocated
    array. The caller shall free this array when not needed any longer */
 /* If acError is provided, it will host any error. Otherwise errors will
    be sent to stderr */
 /* Return 0 on success, -1 on failure */
{
    const size_t    M = strlen (p_pcCmdLine),
        N = M + 1,
        P = M * sizeof (char *);
    size_t          BufSafeSize;
    char            acFormat[16];
    char           *pcStr, *pcStr1, *pcStr2;
    char          **argv;
    int             argc;
    int             n;

    /* Optional parameters */
    va_list         ap;
    int            *piArgc;
    char           *pcError = 0;
    size_t          BufferSize = 0;

    /* Get function's optional parameters */
    va_start (ap, p_pppcArgv);
    piArgc = va_arg (ap, int *);
    if (piArgc) {
        pcError = va_arg (ap, char *);
        if (pcError)
            BufferSize = va_arg (ap, size_t);
    }
    va_end (ap);

    BufSafeSize = (BufferSize > 0 ? BufferSize - 1 : 0);
    pcStr = (char *) malloc (N);
    pcStr1 = (char *) malloc (N);
    pcStr2 = (char *) malloc (N);
    argv = (char **) malloc (P);
    if (!(pcStr && pcStr1 && pcStr2 && argv)) {
        if (pcError) {
            n = errno;
            snprintf (pcError, BufSafeSize, "malloc(%d): %s", n,
                strerror (n));
        }
        else
            perror ("malloc(argv)");
        return (-1);
    }
    memset (argv, 0, P);

    /* Build argv from the command line string */
    sprintf (acFormat, "%%s %%%dc", N - 1);
    strcpy (pcStr, p_pcCmdLine);
    for (argc = 0;;) {
        memset (pcStr2, 0, N);
        n = sscanf (pcStr, acFormat, pcStr1, pcStr2);
        if (n <= 0)
            break;
        argv[argc] = (char *) malloc (strlen (pcStr1) + 1);
        if (!(argv[argc])) {
            if (pcError) {
                n = errno;
                snprintf (pcError, BufSafeSize, "malloc(%d): %s", n,
                    strerror (n));
            }
            else
                perror ("malloc(argv[i])");
            free (pcStr), free (pcStr1), free (pcStr2);
            while (argc-- > 0)
                free (argv[argc]);
            free (argv);
            return (-1);
        }
        strcpy (argv[argc++], pcStr1);
        if (n <= 1)
            break;
        strcpy (pcStr, pcStr2);
    }
    free (pcStr), free (pcStr1), free (pcStr2);

    *p_pppcArgv = argv;
    if (piArgc)
        *piArgc = argc;
    return (0);
}// ParseCmdline()


/**********************************************************************/
int genmon_Spawn (char *const argv[], char *const p_pcOutput,
    const size_t p_BufferSize, const int wait)
/**********************************************************************/
 /* Spawn a command and capture its output */
 /* Return 0 on success, otherwise copy stderr into the output string and
    return -1 */
{
    enum { OUT, ERR, OUT_ERR };
    enum { RD, WR, RD_WR };
    const size_t    BufSafeSize = p_BufferSize - 1;
    // Make sure that the output string will be NULL-terminated
    int             aaiPipe[OUT_ERR][RD_WR];
    pid_t           pid;
    struct pollfd   aoPoll[OUT_ERR];
    int             fError;
    int             status;
    int             i, j, k;

    if (p_BufferSize <= 0) {
        fprintf (stderr, "Spawn() error: Wrong buffer size!\n");
        return (-1);
    }
    memset (p_pcOutput, 0, p_BufferSize);
    if (!(*argv)) {
        strncpy (p_pcOutput, "Spawn() error: No parameters passed!",
            BufSafeSize);
        return (-1);
    }
    for (i = 0; i < OUT_ERR; i++)
        pipe (aaiPipe[i]);
    switch (pid = fork ()) {
        case -1:
            i = errno;
            snprintf (p_pcOutput, BufSafeSize, "fork(%d): %s", i,
                strerror (i));
            for (i = 0; i < OUT_ERR; i++)
                for (j = 0; j < RD_WR; j++)
                    close (aaiPipe[i][j]);
            return (-1);
        case 0:
            /* Redirect stdout/stderr to associated pipe's write-ends */
            for (i = 0; i < OUT_ERR; i++) {
                j = i + 1; // stdout/stderr file descriptor
                close (j);
                k = dup2 (aaiPipe[i][WR], j);
                if (k != j) {
                    perror ("dup2()");
                    exit (-1);
                }
            }
        /* Execute the given command */
        execvp (argv[0], argv);
        perror (argv[0]);
        exit (-1);
    }

    /* Wait for child completion */
    if (wait == 1)
    {
        status = waitpid (pid, 0, 0);
        if (status == -1) {
            i = errno;
            snprintf (p_pcOutput, BufSafeSize, "waitpid(%d): %s", i,
                strerror (i));
            fError = 1;
            goto End;
        }

        /* Read stdout/stderr pipes' read-ends */
        for (i = 0; i < OUT_ERR; i++) {
            aoPoll[i].fd = aaiPipe[i][RD];
            aoPoll[i].events = POLLIN;
            aoPoll[i].revents = 0;
        }
        poll (aoPoll, OUT_ERR, ~0);
        for (i = 0; i < OUT_ERR; i++)
            if (aoPoll[i].revents & POLLIN)
                break;
        if (i < OUT_ERR)
            read (aaiPipe[i][RD], p_pcOutput, BufSafeSize);
        fError = (i != OUT);

        /* Remove trailing carriage return if any */
        if (p_pcOutput[(i = strlen (p_pcOutput) - 1)] == '\n')
            p_pcOutput[i] = 0;
    }

    End:
    /* Close created pipes */
    for (i = 0; i < OUT_ERR; i++)
        for (j = 0; j < RD_WR; j++)
            close (aaiPipe[i][j]);

    return (-fError);
}// Spawn()


/**********************************************************************/
int genmon_SpawnCmd (const char *const p_pcCmdLine, char *const p_pcOutput,
    const size_t p_BufferSize, const int wait)
/**********************************************************************/
 /* Spawn a command and capture its output */
 /* Return 0 on success, otherwise copy stderr into the output string and
    return -1 */
{
    char          **argv;
    int             argc;
    int             status;

    if (strlen(p_pcCmdLine) == 0)
        return (-1);
    
    /* Split the commandline into an argv array */
    status = ParseCmdline (p_pcCmdLine, &argv, &argc,
        p_pcOutput, p_BufferSize);
    if (status == -1)
        /* Memory allocation problem */
        return (-1);

    /* Spawn the command and free allocated memory */
    status = genmon_Spawn (argv, p_pcOutput, p_BufferSize, wait);
    while (argc-- > 0)
        free (argv[argc]);
    free (argv);
    return (status);
}// SpawnCmd()