GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

C++ Klassen-Template für Prozessauführung

C++ Process execution class template

Ein Klassentemplate, um Prozesse auszuführen und deren stdout, stderr und exit code zu erfassen. Auch können Daten via stdin zum Prozess gesendet werden. Die Klasse ist bewusst simpel gehalten und wirft aktiv keine Exceptions (nur STL-Container darin werfen evtl Fehler bei Speicherproblemen).

Die Code-Beispiele sagen mehr als 0x3e8 Worte Beschreibung:

Class template to execute processes and fetch their exit code, stdout and stderr. Optionally you can send data to the child process via stdin, too. The class is intentionally kept simple and maintainable, and does not throw exceptions actively (only STL containers in the instance may throw if the have trouble allocation memory).

The code examples tell you more than a 0x3e8 words I could add to this introduction:

Dateien

Files

process.hh example microtest

Beispiel mit Erläuterungen

Example with explanations

#include <process.hh>
#include "test.hh"
 
using namespace std;
 
std::string space_replace(std::string s)
{
  std::replace_if(s.begin(), s.end(), ::isspace, ' ');
  auto p = s.find_last_not_of(' ');
  if(p != s.npos && p != s.length()-1) s.resize(p+1);
  return s;
}
 
void test()
{
  {
    test_comment("Run, fetch output");
    sw::process::data_t data;
    test_expect_noexcept(
      data = sw::process::exec(
        "grep -w --color 'hello'",        // command
        "hello world\nignored\nhello\n",  // stdin data
        false,                            // Don't redirect stderr to stdout
        1000                              // 1000ms child timeout
      )
    );
    test_expect( !data.error );
    test_expect( data.error_text.empty() );
    test_expect( data.exit_status == 0 );
    test_expect( data.stderr_data == "" );
    test_comment("output is: " << data.stdout_data);
    test_expect( space_replace(data.stdout_data) == "hello world hello" );
  }
  {
    test_comment("exec with timeout, should fail with timeout");
    sw::process::data_t data = sw::process::exec("find /", "", false, 100);
    test_comment("data.error = " << data.error);
    test_comment("data.error_text = " << data.error_text);
    test_comment("data.exit_status= " << data.exit_status);
    test_expect( data.error );
    test_expect( !data.error_text.empty() );
    test_expect( data.exit_status < 0 );
    test_expect( data.stderr_data == "" );
    test_expect( data.stdout_data == "" );
  }
 
 
  // Example: Using process instance
  {
    string command = "tree .";
    sw::process proc;
    test_comment("Run command '" << command << "' ...");
    std::string stdout_data, stderr_data;
    if(test_expect_cond( proc.run(command)) ) {
      // While the child process is still running, we wait for changes with a wait timeout of
      // 1000ms. In wait(), the instance reads stderr and stdout of the process if new data
      // have arrived. If wait() returns false, nothing changed - means no data, no error,
      // and the process did not exit.
      for(; proc.running(); proc.wait(1000)) {
        // If stdout data available, dump and clear
        if(!proc.stdout_data().empty()) {
          stdout_data += proc.stdout_data();
          test_comment( "STDOUT: " << proc.stdout_data());
          test_expect_noexcept( proc.stdout_data().clear() ); // clear, without deallocating
        }
        // If stderr data available, dump and clear
        if(!proc.stderr_data().empty()) {
          stderr_data += proc.stderr_data();
          test_comment( "STDERR: " << proc.stderr_data());
          test_expect_noexcept( proc.stderr_data().clear() ); // clear, without deallocating
        }
      }
      // If stdout data available, dump and clear
      if(!proc.stdout_data().empty()) {
        stdout_data += proc.stdout_data();
        test_comment( "STDOUT: " << proc.stdout_data());
        test_expect_noexcept( proc.stdout_data().clear() ); // clear, without deallocating
      }
      // If stderr data available, dump and clear
      if(!proc.stderr_data().empty()) {
        stderr_data += proc.stderr_data();
        test_comment( "STDERR: " << proc.stderr_data());
        test_expect_noexcept( proc.stderr_data().clear() ); // clear, without deallocating
      }
      // The exit status can be retrieved with proc.exit_status(). This value is initially -1
      // and modified when the child process has quit.
      test_comment("Exit status is: " << proc.exit_status());
      test_expect( proc.exit_status() == 0);
    }
    (void) stdout_data;
    (void) stderr_data;
 
    // Check errors, 0 === no error
    if(!test_expect_cond( !proc.error() )) {
      test_expect( !proc.error_text().empty() );
    }
 
    // Example, the process is no more running anyway, so this line has no effect here.
    // Saying proc.kill() or proc.kill(false) will send a SIGTERM signal to the process,
    // and causes the parent to close the child stdin pipe, but not the stdout/stderr
    // pipes. The resulting output may still be fetched, and the child process may ignore
    // the signal.
    test_expect_noexcept( proc.kill() );
 
    // Example, equivalent to "kill -9". Sending this will send a SIGKILL signal to the
    // child process and close all pipes to it. We lose interest in that process, even
    // if it ignores the kill signal (there's nothing we could do).
    test_expect_noexcept( proc.kill(true) );
 
    // Example, get all resulting data from the process
    sw::process::data_t data;
    test_expect_noexcept ( data = proc.data() );
    (void) data;
 
    // Example, close all pipes on the parent side, no effect here since the pipes are closed
    // already. Pipes will be closed automatically when the child process exits or when the
    // child process closes its side of a pipe.
    test_expect_noexcept ( proc.close_pipes() );
 
    // Closes only the parent side of the child stdin. It is the same as saying
    // `proc.write_stdin(data, 0);`
    test_expect_noexcept ( proc.close_stdin() );
 
    // kill the child process if it is still running, close pipes, reset error, free memory.
    test_expect_noexcept ( proc.clear() );
  }
}

Quelltext

Source code

/**
 * @package de.atwillys.cc.swl
 * @license BSD (simplified)
 * @author Stefan Wilhelm (stfwi)
 *
 * @file process.hh
 * @ccflags
 * @ldflags
 * @platform linux, bsd, windows
 * @standard >= c++98
 *
 * -----------------------------------------------------------------------------
 *
 * Simple process wrapper template class (sw::process / sw::detail::basic_process<...>)
 *
 *  To directly execute a program, optionally send stdin data, fetch stdout / stderr and
 *  exit status, use:
 *
 *    sw::process::data_t data = sw::process::exec(
 *      (string) command,                 // Parses with quotes, e.g. "ls -lsia   '--color' "
 *      (string) stdin_text,              // Optional, default empty string
 *      (bool) redirect_stderr_to_stdout, // Optional, default false
 *      (unsigned long) timeout           // Optional, default 0 === no timeout
 *    );
 *    if(data.error) {
 *      cerr << "Error: " << data.error_text << endl;
 *    } else {
 *      cerr << data.exit_status << endl;
 *      cout << data.stdout_data << endl;
 *      cerr << data.stderr_data << endl;
 *    }
 *
 * For better control or large amount of data that you like to process directly when they
 * arrive, instantiate:
 *
 *    sw::process proc; // instance
 *    string command = "e.g. tree .";
 *    if(proc.run(command)) {
 *      // Starting the process succeeded, loop while the process is still running
 *      while(proc.running()) {
 *        // wait returns false if nothing changed, no data, no error, no exit
 *        if(!proc.wait(1000)) continue;
 *        // Here something changed, check what happened
 *        if(proc.error()) {
 *          // Check the error, maybe quit
 *          cerr << proc.error_text() << endl;
 *          if( ... something_fatal ... ) {
 *            proc.kill(true);
 *            return ... ;
 *          }
 *        }
 *        if(!proc.stdout_data().empty()) {
 *          // Do something with the stdout data, filter, store, evaluate, whatever
 *          cout << proc.stdout_data();
 *          proc.stdout_data().clear();
 *        }
 *        if(!proc.stderr_data().empty()) {
 *          // Do something with the error data ...
 *          cerr << proc.stderr_data();
 *          proc.proc().clear();
 *        }
 *      }
 *    }
 *    proc.clear(); // Optional, kill the process if still running, free memory, etc.
 *    return ... ;  // The child process is also killed when the proc object is destructed.
 *
 * -----------------------------------------------------------------------------
 *
 * Notes:
 *
 *  - This class does not actively throw exceptions. Only STL containers may throw
 *    memory allocation errors.
 *
 *  - This class is not indendet to be used by more than one thread. It does not
 *    contain synchronisation and uses `errno`, which is thread dependent.
 *
 *  - This class uses ::fork() instead of ::vfork() or ::clone() for the sake of
 *    safety and stability. That will, in worst case, duplicate almost the whole
 *    current memory of your application, which can take a while compared to
 *    ::clone(). Unfortunately, there is currently no really safe way around it.
 *
 *  - Only `char` allowed as first template argument. `wchar_t` implementation
 *    pending.
 *
 *  - Windows currently not supported.
 *
 * -----------------------------------------------------------------------------
 * +++ BSD license header (You know that ...) +++
 * Copyright (c) 2006-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: (1) Redistributions
 * of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer. (2) Redistributions in binary form must reproduce
 * the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * (3) Neither the name of atwillys.de nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * -----------------------------------------------------------------------------
 */
#ifndef SW_PROCESS_HH
#define SW_PROCESS_HH
 
// <editor-fold desc="preprocessor" defaultstate="collapsed">
#if defined(_WIN32) || defined(__MSC_VER)
  #error "Not implemented"
#endif
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <poll.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef __linux__
  #include <wait.h>
#else
  #include <sys/wait.h>
#endif
#ifdef SW_PROCESS_WITH_DEBUG
  #include <iostream>
  namespace {
    template <typename T=void>
    struct process_dbg_timeval {
      static process_dbg_timeval instance;
      struct ::timespec reftime;
      process_dbg_timeval() { ::clock_gettime(CLOCK_MONOTONIC, &reftime); }
 
      static std::string now() noexcept
      {
        struct ::timespec t;
        ::clock_gettime(CLOCK_MONOTONIC, &t);
        t.tv_sec -= instance.reftime.tv_sec;
        t.tv_nsec -= instance.reftime.tv_nsec;
        t.tv_sec *= 1000;
        t.tv_nsec /= 100000;
        char s[32];
        ::snprintf(s, sizeof(s)-1, "%03ld.%04ld", (long)t.tv_sec, (long)t.tv_nsec);
        s[sizeof(s)-1] = '\0';
        return std::string(s);
      }
 
    };
    template <typename T> process_dbg_timeval<T> process_dbg_timeval<T>::instance;
 
  }
  #define SW_PROCESS_DBG(X) { std::cerr << process_dbg_timeval<>::now() << " process::" << X << std::endl; }
#else
  #define SW_PROCESS_DBG(X)
#endif
// </editor-fold>
 
// <editor-fold desc="basic_process" defaultstate="collapsed">
namespace sw { namespace detail {
 
/**
 * class basic_process
 */
template <typename Char_Type=char>
class basic_process
{
public:
 
  /**
   * Types
   */
  typedef std::basic_string<Char_Type> string_type;
  typedef pid_t pid_type;
  typedef int fd_type;
  typedef int timeout_type;
 
  /**
   * Data structure to store resulting data of a child process run.
   */
  typedef struct {
    int exit_status;          // Child exit status
    int error;                // Error number of the last error
    string_type error_text;   // Error string of the last error
    string_type stdout_data;  // Read stdout data
    string_type stderr_data;  // Read stderr data
  } data_t;
 
  static constexpr pid_type invalid_pid() { return -1; }
 
public:
 
  /**
   * Default constructor
   */
  inline basic_process() : pid_(invalid_pid()), i_(-1), o_(-1), e_(-1)
  { data_.exit_status = -1; data_.error = 0; }
 
  basic_process(const basic_process&) = delete;
  basic_process& operator=(const basic_process&) = delete;
 
  /**
   * Destructor
   */
  virtual ~basic_process()
  { close_pipes(); kill(true); }
 
public:
 
  /**
   * Returns the process ID of the child process or < 0 if the process is not running.
   * @return pid_type
   */
  inline pid_type pid() const noexcept
  { return pid_; }
 
  /**
   * Returns the code of the last occurred error, 0 if no error.
   * @return int
   */
  inline int error() const noexcept
  { return data_.error; }
 
  /**
   * Returns the text of the last occurred error (if available), or "" (if not or no error).
   * @return const string_type &
   */
  inline const string_type & error_text() const noexcept
  { return data_.error_text; }
 
  /**
   * Returns the exit status of the child process. Only valid if the child process
   * has already stopped.
   * @return int
   */
  inline int exit_status() const noexcept
  { return data_.exit_status; }
 
  /**
   * Returns a reference to the currently cached stdout data from the child process.
   * @return string_type &
   */
  inline string_type & stdout_data() noexcept
  { return data_.stdout_data; }
 
  /**
   * Returns a reference to the currently cached stdout data from the child process.
   * @return string_type &
   */
  inline const string_type & stdout_data() const noexcept
  { return data_.stdout_data; }
 
  /**
   * Returns a reference to the currently cached stderr data from the child process.
   * @return string_type &
   */
  inline string_type & stderr_data() noexcept
  { return data_.stderr_data; }
 
  /**
   * Returns a reference to the currently cached stderr data from the child process.
   * @return string_type &
   */
  inline const string_type & stderr_data() const noexcept
  { return data_.stderr_data; }
 
  /**
   * Returns a reference to the currently set (process result) data structure.
   * @return const data_t &
   */
  inline const data_t & data() const noexcept
  { return data_; }
 
  /**
   * Returns true if the child process is currently running. Performs a double check.
   * If pipes shall be verified, then the method returns true (even if the process is not running
   * anymore) if there are still data to read in the stderr/stdout pipe. That allows to read all
   * data of the process without cutting off the tailing output.
   * @return bool
   */
  inline bool running() noexcept
  {
    if(pid_ == invalid_pid() || wait(0)) return false;
    return (pid_ != invalid_pid());
  }
 
  /**
   * Starts the child process, specifying a command directly. Returns success.
   * @param const string_type& command
   * @return bool
   */
  bool run(const string_type& command, bool stderr_to_stdout=false, bool close_stdin_pipe=false)
  {
    static const int max_args = 256;
    static const int cl_length = PATH_MAX * 2;  // command line length
    char cl[cl_length]; // command line
    char *argv[max_args]; // argument pointers
    if(command.length() >= sizeof(cl)-2) { // -2: leave some space for safe tokenising
      seterr("Specified command line is too long");
      return false;
    }
    // We put the data on the stack to prevent leaks on exceptions.
    // Parse using good ole tokenisation
    char *pw = cl;
    int argc = 0;
    memset(cl, 0, cl_length);
    memset(argv, 0, max_args);
    typename string_type::const_iterator it=command.begin();
    while(it != command.end() && ::isspace(*it)) ++it; // ltrim, all spaces
    argv[0] = cl;
    for(; it != command.end(); ++it) {
      char c = *it;
      switch(c) {
        case '\\':
          if(++it == command.end()) { // Not accepted, cannot be newline escape as it is end of str.
            seterr("Unescaped backslash a end of command line");
            return false;
          }
          *pw++ = *it;
          break;
        case '"':
          while(++it != command.end() && (*it != '"')) {
            if(*it == '\\' && (++it == command.end())) break; // Missing quote err more appropriate
            *pw++ = *it;
          }
          if(it == command.end()) {
            seterr("Missing closing double quote");
            return false;
          }
          break;
        case '\'':
          while(++it != command.end() && (*it != '\'')) {
            if(*it == '\\' && (++it == command.end())) break; // Missing quote err more appropriate
            *pw++ = *it;
          }
          if(it == command.end()) {
            seterr("Missing closing single quote");
            return false;
          }
          break;
        case ' ':
        case '\t':
          *pw++ = 0; // end of arg
          if(++argc >= max_args) {
            seterr("Specified command line has too many arguments");
            return false;
          }
          argv[argc] = pw;
          while((it != command.end()) && (*it == ' ' || *it == '\t' )) {
            ++it;
          }
          --it;
          break;
        default:
          *pw++ = *it;
      }
    }
    if(argv[argc]) {
      for(pw=argv[argc]; *pw; ++pw) {;}
      for(--pw; pw >= argv[argc]; --pw) {
        if(*pw == '\t' || *pw == ' ') *pw = 0; // ltrim, only spaces and tabs
      }
      if(argv[argc][0] == 0) {
        argv[argc] = 0;
      }
    }
    return run((const char*)cl, (const char**)argv, NULL, stderr_to_stdout, close_stdin_pipe);
  }
 
  /**
   * Starts the child process with command, arguments and environment, returns success.
   * Note: cmd and argv must not be NULL. envv is optionally a null terminated c-string-array
   *       ("env1","env2" ..., NULL) or NULL to pass the own environment to the child process.
   * Note: argv is NULL terminated and the first argument must represent the command
   *       ("command", "arg1", "arg2", ..., NULL).
   * Note: When stderr2stdout==true, the child process STDERR will be implicitly redirected
   *       to STDOUT and the method `ssize_t read_stderr(data, size)` will always returns immediately
   *       with 0 as return value.
   *
   * @return bool
   */
  bool run(const char* cmd, const char* argv[], const char* envv[], bool stderr_to_stdout,
          bool close_stdin_pipe)
  {
    if(!cmd || !argv || running()) {
      SW_PROCESS_DBG("run(): Invalid args or already running.");
      return false;
    } else {
      #ifdef SW_PROCESS_WITH_DEBUG
      std::string sargs, eargs;
      if(!argv) {
        sargs = "nullptr";
      } else {
        for(int i=0; argv[i]; ++i) sargs += std::string("'") + argv[i] + "' ";
      }
      if(!envv) {
        eargs = "nullptr";
      } else {
        for(int i=0; envv[i]; ++i) eargs +=std::string("'") + envv[i] + "' ";
      }
      SW_PROCESS_DBG("run(cmd='" << cmd << "', argv=" << sargs << ", envv="
        << eargs << ", err2out="<< (int)stderr_to_stdout << ", closestdin="
        << (int)close_stdin_pipe << ")");
      #endif
    }
 
    union pipes_u { fd_type a[6]; struct { fd_type i[2], o[2], e[2]; } s; } pip;
    bool ok = true;
    pid_type pid = invalid_pid();
    o_ = e_ = i_ = -1;
    for(unsigned i=0; i<6; ++i) pip.a[i] = -1;
 
    // Make pipes
    if(ok && ::pipe(pip.s.i))  {
      seterr(errno);
      SW_PROCESS_DBG("run(): Create stdin pipe failed." << data_.error_text);
      pip.s.i[0] = pip.s.i[1] = -1;
      ok=false;
    }
    if(ok && ::pipe(pip.s.o) ) {
      seterr(errno);
      SW_PROCESS_DBG("run(): Create stdout pipe failed." << data_.error_text);
      pip.s.o[0] = pip.s.o[1] = -1;
      ok=false;
    }
    if(ok && (!stderr_to_stdout) && ::pipe(pip.s.e))  {
      SW_PROCESS_DBG("run(): Create stderr pipe failed." << data_.error_text);
      pip.s.e[0] = pip.s.e[1] = -1;
      ok=false;
    }
    if(ok && ((pid = ::fork()) < 0)) {
      seterr(errno);
      SW_PROCESS_DBG("run(): Forking failed." << data_.error_text);
      pid = invalid_pid();
      ok = false;
    }
    if(!ok) {
      // Close open pipes
      for(unsigned i=0; i<sizeof(pip.a); ++i) {
        if(pip.a[i] >= 0) ::close(pip.a[i]);
      }
      return false;
    }
    if(pid == 0) {
      // Child
      if(pip.s.i[1] >= 0) ::close(pip.s.i[1]);
      if(pip.s.o[0] >= 0) ::close(pip.s.o[0]);
      if(pip.s.e[0] >= 0) ::close(pip.s.e[0]);
      if(ok && (pip.s.i[0] >= 0) && (::dup2(pip.s.i[0], STDIN_FILENO) < 0)) ok = false;
      if(ok && (pip.s.o[1] >= 0) && (::dup2(pip.s.o[1], STDOUT_FILENO) < 0)) ok = false;
      if(!stderr_to_stdout) {
        if(ok && (pip.s.e[1] >= 0) && (::dup2(pip.s.e[1], STDERR_FILENO) < 0)) ok = false;
      } else {
        if(ok && (pip.s.o[1] >= 0) && (::dup2(pip.s.o[1], STDERR_FILENO) < 0)) ok = false;
      }
      if(ok) {
        for(int i=3; i<1024; ++i) ::close(i); // @sw: check how to safely get the highest fd.
        if(!!envv) {
          #ifdef __linux__
          ::execvpe(cmd, (char* const*)argv, (char* const*)envv);
          #else
          for(int i=0; envv[i] && envv[i+1]; i+=2) ::setenv(envv[i], envv[i+1], 1);
          ::execvp(cmd, (char* const*)argv);
          #endif
        } else {
          ::execvp(cmd, (char* const*)argv);
        }
      }
      _exit(-1);
    } else {
      // Parent
      if(pip.s.i[0] >= 0) ::close(pip.s.i[0]);
      if(pip.s.o[1] >= 0) ::close(pip.s.o[1]);
      if(pip.s.e[1] >= 0) ::close(pip.s.e[1]);
      pid_ = pid;
      i_ = pip.s.i[1];
      o_ = pip.s.o[0];
      e_ = pip.s.e[0];
      if(close_stdin_pipe) close_stdin();
      if(i_ >= 0) nonblocking(i_);
      if(o_ >= 0) nonblocking(o_);
      if(e_ >= 0) nonblocking(e_);
      SW_PROCESS_DBG("run(): running, pid=" << (long)pid << ", stdin=" << i_ << ", stdout=" << o_ << ", stderr=" << e_);
    }
    return true;
  }
 
  /**
   * Write data to the child process stdin. Returns >=0 on success, <0 on error.
   * Writing 0 bytes (size==0) will close the stdin pipe to the child process.
   * @param const void* data
   * @param size_t size
   * @return ssize_t
   */
  ssize_t write_stdin(const void* data, size_t size)
  {
    SW_PROCESS_DBG("write_stdin(data, size=" << (long) size << ")");
    if(!data && !size) { if(i_ >= 0) ::close(i_); i_ = -1; return 0; }
    if(i_ < 0) { running(); return seterr("Child process stdin pipe already closed"); }
    ssize_t r = ::write(i_, data, size);
    if(r >= 0) return r;
    return seterr(errno);
  }
 
  /**
   * Clears the object. Kills (force) the child process if it is still running and attached.
   */
  void clear()
  {
    SW_PROCESS_DBG("clear()");
    kill(true); data_.error = 0; e_ = o_ = i_ = data_.exit_status = -1;
    string_type().swap(data_.error_text); string_type().swap(data_.stderr_data);
    string_type().swap(data_.stdout_data); pid_ = invalid_pid();
  }
 
  /**
   * Kill the child process, either with SIGTERM or SIGKILL
   * @param bool force=false
   */
  void kill(bool force=false, timeout_type timeout=0)
  {
    SW_PROCESS_DBG("kill(force=" << (int) force << ", timeout=" << timeout << ")");
    pid_type pid = pid_;
    if(pid == invalid_pid()) {
      SW_PROCESS_DBG("kill(): No PID to kill.");
      return;
    }
    close_stdin();
    ::kill(pid, force ? SIGKILL : SIGTERM);
    if(force) close_pipes();
    if(timeout > 0) wait(timeout);
  }
 
  /**
   * Wait until anything with the child process changes. Returns false on timeout, true on
   * Pipe I/O, state change (e.g. child process exit) or error.
   * @param timeout_type timeout_ms
   * @return bool
   */
  bool wait(timeout_type timeout_ms=100)
  {
    SW_PROCESS_DBG("wait(timeout_ms=" << timeout_ms << ")");
    if(pid_ == invalid_pid()) return false;
    struct ::timespec to, cur;
    if(::clock_gettime(CLOCK_MONOTONIC, &cur) < 0) return pid_ != invalid_pid();
    if(timeout_ms < 0) timeout_ms = 0;
    to.tv_sec  = cur.tv_sec  + (timeout_ms / 1000);
    to.tv_nsec = cur.tv_nsec + ((timeout_ms % 1000) * 1000000);
    bool changed = false;
    long time_left = 1;
 
    while(((pid_ != invalid_pid())) && (time_left > 0) && (cur.tv_sec <= to.tv_sec)) {
      time_left = (1000L * (to.tv_sec-cur.tv_sec)) + ((to.tv_nsec-cur.tv_nsec)/1000000);
      if(time_left < 0) {
        time_left = 0; // note: don't return here, at least one check
      } else if(time_left > 100) {
        time_left = 100; // double check at least every second.
      }
      SW_PROCESS_DBG("wait(): time_left=" << time_left);
 
      // Pipe processing
      // Note: poll ignores fds that are -1.
      if((o_ >= 0) || (e_ >= 0)) {
        SW_PROCESS_DBG("wait(): o_fd=" << o_ << ", e_fd=" << e_);
        struct ::pollfd pfd[2];
        memset(pfd, 0, sizeof(pfd));
        pfd[0].fd = o_; pfd[0].events = POLLIN|POLLPRI; pfd[0].events = 0;
        pfd[1].fd = e_; pfd[1].events = POLLIN|POLLPRI; pfd[1].events = 0;
        int r = ::poll(pfd, sizeof(pfd)/sizeof(struct ::pollfd), time_left);
        if(r < 0) {
          switch(errno) {
            case EAGAIN:
              SW_PROCESS_DBG("wait(): poll: EAGAIN");
              continue;
            case EINTR:
              SW_PROCESS_DBG("wait(): poll: EINTR");
              return changed; // interrupted with signal
            default:
              seterr(errno);
              SW_PROCESS_DBG("wait(): poll: error -> return");
              return true; // error --> something changed
          }
        } else if(r > 0) {
          if(pfd[0].revents) {
            char data[4096];
            ssize_t r = ::read(o_, data, sizeof(data));
            while(r > 0) {
              SW_PROCESS_DBG("wait(): read(o): nread=" << r);
              data_.stdout_data.append(data, r);
              changed = true;
              r = ::read(o_, data, sizeof(data));
            }
            if((!r) || ((r<0) && (errno != EAGAIN) && (errno != EINTR))) {
              SW_PROCESS_DBG("wait(): read(o): close pipe, nread=" << r << ".");
              if(r) seterr(errno);
              ::close(o_);
              o_ = -1;
            } else {
              SW_PROCESS_DBG("wait(): read(o): all read or eintr.");
            }
          }
          if(pfd[1].revents) {
            char data[4096];
            ssize_t r = ::read(e_, data, sizeof(data));
            while(r > 0) {
              data_.stderr_data.append(data, r);
              SW_PROCESS_DBG("wait(): read(e): nread=" << r);
              changed = true;
              r = ::read(e_, data, sizeof(data));
            }
            if((!r) || ((r<0) && (errno != EAGAIN) && (errno != EINTR))) {
              SW_PROCESS_DBG("wait(): read(e): close pipe, nread=" << r << ".");
              if(r) seterr(errno);
              ::close(e_);
              e_ = -1;
            } else {
              SW_PROCESS_DBG("wait(): read(e): all read or eintr.");
            }
          }
        }
      }
 
      // Process check
      {
        int r, status = 0;
        if((r=::waitpid((const pid_type) pid_, &status, WNOHANG)) < 0) {
          switch(errno) {
            case EAGAIN:
              SW_PROCESS_DBG("wait(): waitpid(): EAGAIN.");
              break;
            case EINTR:
              time_left = 0;
              SW_PROCESS_DBG("wait(): waitpid(): EINTR.");
              break;
            case ECHILD:
              SW_PROCESS_DBG("wait(): waitpid(): ECHILD.");
              r = pid_;
              break;
            default:
              SW_PROCESS_DBG("wait(): waitpid(): error");
              seterr(errno);
              changed = true;
              time_left = 0;
          }
        }
        if(r == pid_) {
          data_.exit_status = WEXITSTATUS(status);
          SW_PROCESS_DBG("wait(): waitpid(): exit_status=" << data_.exit_status);
          pid_ = invalid_pid();
          close_pipes();
          changed = true;
          time_left = 0;
        }
      }
 
      // Timeout update
      if((time_left > 0) && (o_ < 0) && (e_ < 0)) {
        SW_PROCESS_DBG("wait(): usleep");
        ::usleep(1000);
      }
      if(::clock_gettime(CLOCK_MONOTONIC, &cur) < 0) break;
    }
 
    SW_PROCESS_DBG("wait(): return, changed=" << (int) changed);
    return changed;
  }
 
  /**
   * Close open pipes.
   */
  void close_stdin() noexcept
  {
    SW_PROCESS_DBG("close_stdin()");
    if(i_ >= 0) { ::close(i_); } i_ = -1;
  }
 
  /**
   * Close open pipes.
   */
  void close_pipes() noexcept
  {
    SW_PROCESS_DBG("close_pipes()");
    if(o_ >= 0) ::close(o_);
    if(e_ >= 0) ::close(e_);
    if(i_ >= 0) ::close(i_);
    o_ = e_ = i_ = -1;
  }
 
  inline void nonblocking(fd_type fd) noexcept
  {
    SW_PROCESS_DBG("nonblocking(" << (int) fd << ")");
    int o = ::fcntl(fd, F_GETFL, 0);
    if(o >= 0) {
      o |= O_NONBLOCK;
      ::fcntl(fd, F_SETFL, o);
    }
  }
 
public:
 
  /**
   * Execute a command, send stdin data (if `stdin_data` is not empty), closes the child input pipe,
   * collects stderr and stdout coming from the child process, and waits until the process is
   * finished. When the function returns, the child process will be implicitly killed if it is
   * still running (e.g. in case of errors or timeout).
   * @param const string_type & command
   * @param const string_type & stdin_data = string_type()
   * @param bool stderr_to_stdout = false
   * @return data_t
   */
  static data_t exec(const string_type & command, const string_type & stdin_data=string_type(),
          bool stderr_to_stdout=false, unsigned long timeout_ms=0)
  {
    basic_process proc;
    if(proc.run(command, stderr_to_stdout)) {
      if(!stdin_data.empty()) {
        proc.write_stdin(stdin_data.data(), stdin_data.length());
      }
      proc.close_stdin();
      if(!timeout_ms) {
        for(; proc.running(); proc.wait(100)) {;}
      } else {
        struct ::timespec to, cur;
        if(::clock_gettime(CLOCK_MONOTONIC, &cur) < 0) {
          data_t data;
          data.error = data.exit_status = -1;
          data.error_text = "Failed to clock_gettime for timeout.";
          return data; // ~basic_process() kills (9) the process.
        }
        to.tv_sec  = cur.tv_sec  + (timeout_ms / 1000);
        to.tv_nsec = cur.tv_nsec + ((timeout_ms % 1000) * 1000000);
        for(; proc.running(); proc.wait(1000)) {
          if(::clock_gettime(CLOCK_MONOTONIC, &cur) < 0) {
            data_t data = proc.data();
            data.error = -1;
            data.error_text = "Failed to clock_gettime for timeout.";
            return data;
          }
          if(cur.tv_sec < to.tv_sec) continue;
          if(cur.tv_sec >= to.tv_sec || cur.tv_nsec >= to.tv_nsec) {
            data_t data = proc.data();
            data.error = -1;
            data.error_text = "Child process execution timeout exceeded";
            return data;
          }
        }
      }
    }
    return proc.data();
  }
 
  /**
   * Makes this process a daemon. On error the function does not return but aborts the program
   * with the exit code 1 and prints an error message to stderr (if stderr is still open).
   * Note: The function does not allow the process becoming a daemon when the current user
   *       has root permissions. You must drop the permissions before (safety feature).
   * Ref: http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html
   */
  static bool daemonize()
  {
    SW_PROCESS_DBG("daemonize()");
    #ifndef NO_SYSTEM_DAEMON_FUNCTIONALITY
    if(::daemon(0, 0) != 0) {
      ::fprintf(stderr, "Failed to daemonize.\n");
      ::exit(1);
    }
    #else
    pid_type pid;
    if(::geteuid() == 0 || ::getuid() == 0) {
      ::fprintf(stderr, "Permissions not dropped before daemonizing the process.\n");
      ::exit(1);
    }
    if((pid = ::fork()) == PID_INV) {
      ::fprintf(stderr, "Failed to fork original process.\n");
      ::exit(1);
    } else if(pid) {
      _exit(0);
    }
    if(::setsid() < 0) {
      ::fprintf(stderr, "Failed to start new session.\n");
      ::exit(1);
    }
    ::signal(SIGHUP, SIG_IGN); // Fork again, allowing the parent process to terminate.
    if((pid = ::fork()) == PID_INV) {
      ::fprintf(stderr, "Failed to fork process.\n");
      ::exit(1);
    } else if(pid) {
      _exit(0);
    }
    if(::chdir("/tmp") < 0) {
      ::fprintf(stderr, "Failed to switch directory to /tmp.\n");
      ::exit(1);
    }
    ::umask(0);
    ::close(STDIN_FILENO);
    ::close(STDOUT_FILENO);
    ::close(STDERR_FILENO);
    if(::open("/dev/null", O_RDONLY) < 0) ::exit(1);
    if(::open("/dev/null", O_WRONLY) < 0) ::exit(1);
    if(::open("/dev/null", O_RDWR) < 0)  ::exit(1);
    #endif
    return true;
  }
 
private:
 
  /**
   * Set error
   * @param int error
   * @return int
   */
  int seterr(int error)
  {
    if(error == EAGAIN || error == EINTR) return 0;
    data_.error = error;
    if(!error) {
      data_.error_text.clear();
    } else {
      data_.error_text = ::strerror(error);
      SW_PROCESS_DBG("seterr('" << data_.error_text << "')");
    }
    return (!error) ? 0 : -1;
  }
 
  /**
   * Set error message (error implicitly set to -1). Returns -1;
   * @param const char* msg
   * @return int
   */
  int seterr(const char* msg)
  {
    data_.error_text = (msg ? msg : "Unspecified error");
    SW_PROCESS_DBG("seterr('" << data_.error_text << "')");
    return (data_.error=-1);
  }
 
private:
 
  pid_type pid_;      // Child pid
  fd_type i_, o_, e_; // Pipes: child stdin, stdout, stderr
  data_t data_;       // exit status, stdout, stderr
};
}}
// </editor-fold>
 
// <editor-fold desc="specialisation" defaultstate="collapsed">
namespace sw {
  typedef detail::basic_process<> process;
}
// </editor-fold>
 
// <editor-fold desc="undefs" defaultstate="collapsed">
#undef SW_PROCESS_DBG
// </editor-fold>
 
#endif