root/src/kits/shared/CommandPipe.cpp
/*
 * Copyright 2007 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ramshankar, v.ramshankar@gmail.com
 *              Stephan Aßmus <superstippi@gmx.de>
 */

//! BCommandPipe class to handle reading shell output
//  (stdout/stderr) of other programs into memory.
#include "CommandPipe.h"

#include <stdlib.h>
#include <unistd.h>

#include <image.h>
#include <Message.h>
#include <Messenger.h>
#include <String.h>


BCommandPipe::BCommandPipe()
        :
        fStdOutOpen(false),
        fStdErrOpen(false)
{
}


BCommandPipe::~BCommandPipe()
{
        FlushArgs();
}


status_t
BCommandPipe::AddArg(const char* arg)
{
        if (arg == NULL || arg[0] == '\0')
                return B_BAD_VALUE;

        char* argCopy = strdup(arg);
        if (argCopy == NULL)
                return B_NO_MEMORY;

        if (!fArgList.AddItem(reinterpret_cast<void*>(argCopy))) {
                free(argCopy);
                return B_NO_MEMORY;
        }

        return B_OK;
}


void
BCommandPipe::PrintToStream() const
{
        for (int32 i = 0L; i < fArgList.CountItems(); i++)
                printf("%s ", reinterpret_cast<char*>(fArgList.ItemAtFast(i)));

        printf("\n");
}


void
BCommandPipe::FlushArgs()
{
        // Delete all arguments from the list
        for (int32 i = fArgList.CountItems() - 1; i >= 0; i--)
                free(fArgList.ItemAtFast(i));
        fArgList.MakeEmpty();

        Close();
}


void
BCommandPipe::Close()
{
        if (fStdErrOpen) {
                close(fStdErr[0]);
                fStdErrOpen = false;
        }

        if (fStdOutOpen) {
                close(fStdOut[0]);
                fStdOutOpen = false;
        }
}


const char**
BCommandPipe::Argv(int32& argc) const
{
        // NOTE: Freeing is left to caller as indicated in the header!
        argc = fArgList.CountItems();
        const char** argv = reinterpret_cast<const char**>(
                malloc((argc + 1) * sizeof(char*)));
        for (int32 i = 0; i < argc; i++)
                argv[i] = reinterpret_cast<const char*>(fArgList.ItemAtFast(i));

        argv[argc] = NULL;
        return argv;
}


// #pragma mark -


thread_id
BCommandPipe::PipeAll(int* stdOutAndErr) const
{
        // This function pipes both stdout and stderr to the same filedescriptor
        // (stdOut)
        int oldStdOut;
        int oldStdErr;
        pipe(stdOutAndErr);
        oldStdOut = dup(STDOUT_FILENO);
        oldStdErr = dup(STDERR_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        // TODO: This looks broken, using "stdOutAndErr[1]" twice!
        dup2(stdOutAndErr[1], STDOUT_FILENO);
        dup2(stdOutAndErr[1], STDERR_FILENO);

        // Construct the argv vector
        int32 argc;
        const char** argv = Argv(argc);

        // Load the app image... and pass the args
        thread_id appThread = load_image((int)argc, argv,
                const_cast<const char**>(environ));

        dup2(oldStdOut, STDOUT_FILENO);
        dup2(oldStdErr, STDERR_FILENO);
        close(oldStdOut);
        close(oldStdErr);

        free(argv);

        return appThread;
}


thread_id
BCommandPipe::Pipe(int* stdOut, int* stdErr) const
{
        int oldStdOut;
        int oldStdErr;
        pipe(stdOut);
        pipe(stdErr);
        oldStdOut = dup(STDOUT_FILENO);
        oldStdErr = dup(STDERR_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        dup2(stdOut[1], STDOUT_FILENO);
        dup2(stdErr[1], STDERR_FILENO);

        // Construct the argv vector
        int32 argc;
        const char** argv = Argv(argc);

        // Load the app image... and pass the args
        thread_id appThread = load_image((int)argc, argv, const_cast<
                const char**>(environ));

        dup2(oldStdOut, STDOUT_FILENO);
        dup2(oldStdErr, STDERR_FILENO);
        close(oldStdOut);
        close(oldStdErr);

        free(argv);

        return appThread;
}


thread_id
BCommandPipe::Pipe(int* stdOut) const
{
        // Redirects only output (stdout) to caller, stderr is closed
        int stdErr[2];
        thread_id tid = Pipe(stdOut, stdErr);
        close(stdErr[0]);
        close(stdErr[1]);
        return tid;
}


thread_id
BCommandPipe::PipeInto(FILE** _out, FILE** _err)
{
        Close();

        thread_id tid = Pipe(fStdOut, fStdErr);
        if (tid >= 0)
                resume_thread(tid);

        close(fStdErr[1]);
        close(fStdOut[1]);

        fStdOutOpen = true;
        fStdErrOpen = true;

        *_out = fdopen(fStdOut[0], "r");
        *_err = fdopen(fStdErr[0], "r");

        return tid;
}


thread_id
BCommandPipe::PipeInto(FILE** _outAndErr)
{
        Close();

        thread_id tid = PipeAll(fStdOut);
        if (tid >= 0)
                resume_thread(tid);

        close(fStdOut[1]);
        fStdOutOpen = true;

        *_outAndErr = fdopen(fStdOut[0], "r");
        return tid;
}


// #pragma mark -


void
BCommandPipe::Run()
{
        // Runs the command without bothering to redirect streams, this is similar
        // to system() but uses pipes and wait_for_thread.... Synchronous.
        int stdOut[2], stdErr[2];
        status_t exitCode;
        wait_for_thread(Pipe(stdOut, stdErr), &exitCode);

        close(stdOut[0]);
        close(stdErr[0]);
        close(stdOut[1]);
        close(stdErr[1]);
}


void
BCommandPipe::RunAsync()
{
        // Runs the command without bothering to redirect streams, this is similar
        // to system() but uses pipes.... Asynchronous.
        Close();
        FILE* f = NULL;
        PipeInto(&f);
        fclose(f);
}


// #pragma mark -


status_t
BCommandPipe::ReadLines(FILE* file, LineReader* lineReader)
{
        // Reads output of file, line by line. Each line is passed to lineReader
        // for inspection, and the IsCanceled() method is repeatedly called.

        if (file == NULL || lineReader == NULL)
                return B_BAD_VALUE;

        BString line;

        while (!feof(file)) {
                if (lineReader->IsCanceled())
                        return B_CANCELED;

                unsigned char c = fgetc(file);
                        // TODO: fgetc() blocks, is there a way to make it timeout?

                if (c != 255)
                        line << (char)c;

                if (c == '\n') {
                        status_t ret = lineReader->ReadLine(line);
                        if (ret != B_OK)
                                return ret;
                        line = "";
                }
        }

        return B_OK;
}


BString
BCommandPipe::ReadLines(FILE* file)
{
        class AllLinesReader : public LineReader {
        public:
                AllLinesReader()
                        :
                        fResult("")
                {
                }

                virtual bool IsCanceled()
                {
                        return false;
                }

                virtual status_t ReadLine(const BString& line)
                {
                        int lineLength = line.Length();
                        int resultLength = fResult.Length();
                        fResult << line;
                        if (fResult.Length() != lineLength + resultLength)
                                return B_NO_MEMORY;
                        return B_OK;
                }

                BString Result() const
                {
                        return fResult;
                }

        private:
                BString fResult;
        } lineReader;

        ReadLines(file, &lineReader);

        return lineReader.Result();
}


// #pragma mark -


BCommandPipe&
BCommandPipe::operator<<(const char* arg)
{
        AddArg(arg);
        return *this;
}


BCommandPipe&
BCommandPipe::operator<<(const BString& arg)
{
        AddArg(arg.String());
        return *this;
}


BCommandPipe&
BCommandPipe::operator<<(const BCommandPipe& arg)
{
        int32 argc;
        const char** argv = arg.Argv(argc);
        for (int32 i = 0; i < argc; i++)
                AddArg(argv[i]);

        free(argv);
        return *this;
}