/**
* Relational pipes
* Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <sstream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <ext/stdio_filebuf.h>
#include <relpipe/writer/typedefs.h>
#include <relpipe/cli/RelpipeCLIException.h>
namespace relpipe {
namespace in {
namespace filesystem {
/**
* Simple wrapper for a system process (fork+exec) that captures and returns just the STDOUT.
*/
class SystemProcess {
private:
/**
* the command + its arguments
*/
std::vector<std::string> commandLine;
std::vector<std::string> environment;
int nullFile = -1;
/**
* TODO: move to a common library (copied from the AWK module)
* @param args
*/
void execp(const std::vector<std::string>& args) {
const char** a = new const char*[args.size() + 1];
for (size_t i = 0; i < args.size(); i++) a[i] = args[i].c_str();
a[args.size()] = nullptr;
execvp(a[0], (char*const*) a);
delete[] a;
throw relpipe::cli::RelpipeCLIException(L"Unable to do execvp().", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
}
/**
* TODO: move to a common library (copied from the AWK module)
* @param readerFD
* @param writerFD
*/
void createPipe(int& readerFD, int& writerFD) {
int fds[2];
int result = pipe(fds);
readerFD = fds[0];
writerFD = fds[1];
if (result < 0) throw relpipe::cli::RelpipeCLIException(L"Unable to create a pipe.", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
}
/**
* TODO: move to a common library (copied from the AWK module)
*/
void redirectFD(int oldfd, int newfd) {
int result = dup2(oldfd, newfd);
if (result < 0) throw relpipe::cli::RelpipeCLIException(L"Unable redirect FD.", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
}
/**
* TODO: move to a common library (copied from the AWK module)
*/
void closeOrThrow(int fd) {
int error = close(fd);
if (error) throw relpipe::cli::RelpipeCLIException(L"Unable to close FD: " + to_wstring(fd) + L" from PID: " + to_wstring(getpid()), relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
}
public:
SystemProcess(const std::vector<std::string>& commandLine, const std::vector<std::string>& environment = {}) : commandLine(commandLine), environment(environment) {
nullFile = open("/dev/null", O_RDWR);
}
virtual ~SystemProcess() {
close(nullFile);
}
std::string execute() {
std::stringstream result;
// FIXME: different kinds of exception or return the exit code (now it enters infinite loop if the execp() fails)
// TODO: rename (not specific to hash)
int hashReaderFD;
int hashWriterFD;
createPipe(hashReaderFD, hashWriterFD);
__pid_t hashPid = fork();
if (hashPid < 0) {
throw relpipe::cli::RelpipeCLIException(L"Unable to fork the hash process.", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
} else if (hashPid == 0) {
// Child process
closeOrThrow(hashReaderFD);
redirectFD(nullFile, STDIN_FILENO);
redirectFD(nullFile, STDERR_FILENO);
redirectFD(hashWriterFD, STDOUT_FILENO);
for (int i = 0; i < environment.size();) {
std::string name = environment[i++];
std::string value = environment[i++];
setenv(name.c_str(), value.c_str(), true);
}
execp(commandLine);
} else {
// Parent process
closeOrThrow(hashWriterFD);
__gnu_cxx::stdio_filebuf<char> hashReaderBuffer(hashReaderFD, std::ios::in);
std::istream hashReader(&hashReaderBuffer);
for (char ch; hashReader.read(&ch, 1).good();) result.put(ch);
int waitError;
__pid_t waitPID = wait(&waitError);
if (waitError) throw relpipe::cli::RelpipeCLIException(L"The child process returned an error exit code.", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception?
}
return result.str();
}
};
}
}
}