]> granicus.if.org Git - icinga2/commitdiff
Use popen_noshell() instead of popen().
authorGunnar Beutner <gunnar@beutner.name>
Tue, 19 Jun 2012 17:05:24 +0000 (19:05 +0200)
committerGunnar Beutner <gunnar@beutner.name>
Tue, 19 Jun 2012 17:05:24 +0000 (19:05 +0200)
25 files changed:
components/checker/checkercomponent.cpp
configure.ac
icinga/Makefile.am
icinga/nagioschecktask.cpp
third-party/Makefile.am
third-party/popen-noshell/Makefile.am [new file with mode: 0644]
third-party/popen-noshell/README [new file with mode: 0644]
third-party/popen-noshell/performance_tests/fork-performance [new file with mode: 0755]
third-party/popen-noshell/performance_tests/fork-performance.c [new file with mode: 0644]
third-party/popen-noshell/performance_tests/popen_noshell.c [new symlink]
third-party/popen-noshell/performance_tests/popen_noshell.h [new symlink]
third-party/popen-noshell/performance_tests/run-tests.pl [new file with mode: 0755]
third-party/popen-noshell/performance_tests/tiny/freebsd/make.sh [new file with mode: 0755]
third-party/popen-noshell/performance_tests/tiny/freebsd/system.inc [new file with mode: 0644]
third-party/popen-noshell/performance_tests/tiny/freebsd/tiny.asm [new file with mode: 0644]
third-party/popen-noshell/performance_tests/tiny/freebsd/tiny2 [new file with mode: 0755]
third-party/popen-noshell/performance_tests/tiny/linux/make.sh [new file with mode: 0755]
third-party/popen-noshell/performance_tests/tiny/linux/tiny2 [new file with mode: 0755]
third-party/popen-noshell/performance_tests/tiny/linux/tiny2.asm [new file with mode: 0644]
third-party/popen-noshell/performance_tests/tiny2 [new symlink]
third-party/popen-noshell/popen_noshell.c [new file with mode: 0644]
third-party/popen-noshell/popen_noshell.h [new file with mode: 0644]
third-party/popen-noshell/popen_noshell_examples.c [new file with mode: 0644]
third-party/popen-noshell/popen_noshell_tests.c [new file with mode: 0644]
third-party/popen-noshell/popen_noshell_tests.cpp [new file with mode: 0644]

index c0cfa3ac86b4cd4b2afb9e058e7c94bc5817b3dd..2dd5154074d95a2fcf8e3640268f334db079cf07 100644 (file)
@@ -100,7 +100,7 @@ void CheckerComponent::ResultTimerHandler(void)
        time_t now;
        time(&now);
 
-       long latency = 0, results = 0;
+       long min_latency = -1, max_latency = 0, avg_latency = 0, results = 0;
 
        vector<CheckTask::Ptr> finishedTasks = CheckTask::GetFinishedTasks();
 
@@ -112,7 +112,15 @@ void CheckerComponent::ResultTimerHandler(void)
                CheckResult result = task->GetResult();
 //             Application::Log(LogInformation, "checker", "Got result! Plugin output: " + result.Output);
 
-               latency += result.EndTime - result.StartTime;
+               long latency = result.EndTime - result.StartTime;
+               avg_latency += latency;
+
+               if (min_latency == -1 || latency < min_latency)
+                       min_latency = latency;
+
+               if (latency > max_latency)
+                       max_latency = latency;
+
                results++;
 
                service.SetNextCheck(now + service.GetCheckInterval());
@@ -120,7 +128,7 @@ void CheckerComponent::ResultTimerHandler(void)
        }
 
        stringstream msgbuf;
-       msgbuf << "ResultTimerHandler: " << results << " results; avg. latency: " << latency / (results ? results : 1);
+       msgbuf << "ResultTimerHandler: " << results << " results; latency: avg=" << avg_latency / (results ? results : 1) << ", min=" << min_latency << ", max: " << max_latency;
        Application::Log(LogDebug, "checker", msgbuf.str());
 }
 
index e5d9e25848178e8a4ebd0016df318392e37db74c..34e431f3a26f1616f4e6e17b64adcc20adb68965 100644 (file)
@@ -83,6 +83,7 @@ test/Makefile
 third-party/Makefile
 third-party/cJSON/Makefile
 third-party/mmatch/Makefile
+third-party/popen-noshell/Makefile
 Doxyfile
 ])
 AC_OUTPUT
index f5f19acb1e6f4e488d51cddda527d825a5b20041..641c596a8fae76bb5e9109540817deaed4f657c8 100644 (file)
@@ -37,6 +37,7 @@ libicinga_la_CPPFLAGS = \
        -I${top_srcdir}/base \
        -I${top_srcdir}/jsonrpc \
        -I${top_srcdir}/cJSON \
+       -I${top_srcdir}/third-party/popen-noshell \
        -I${top_srcdir}
 
 libicinga_la_LDFLAGS = \
@@ -48,4 +49,5 @@ libicinga_la_LDFLAGS = \
 libicinga_la_LIBADD = \
        $(BOOST_THREAD_LIB) \
        ${top_builddir}/jsonrpc/libjsonrpc.la \
-       ${top_builddir}/base/libbase.la
+       ${top_builddir}/base/libbase.la \
+       ${top_builddir}/third-party/popen-noshell/libpopen_noshell.la
index 1b67bc17e3388bc2fe7ea931952315f876d8944f..1bf3c44a5bae2a394d6ae6badca378cb5acbd988 100644 (file)
@@ -1,4 +1,5 @@
 #include "i2-icinga.h"
+#include "popen_noshell.h"
 
 using namespace icinga;
 
@@ -11,7 +12,7 @@ NagiosCheckTask::NagiosCheckTask(const Service& service)
        : CheckTask(service)
 {
        string checkCommand = service.GetCheckCommand();
-       m_Command = MacroProcessor::ResolveMacros(checkCommand, service.GetMacros()) + " 2>&1";
+       m_Command = MacroProcessor::ResolveMacros(checkCommand, service.GetMacros()); // + " 2>&1";
 }
 
 void NagiosCheckTask::Enqueue(void)
@@ -56,29 +57,30 @@ void NagiosCheckTask::RunCheck(void)
 #ifdef _MSC_VER
        fp = _popen(m_Command.c_str(), "r");
 #else /* _MSC_VER */
-       fp = popen(m_Command.c_str(), "r");
+       popen_noshell_pass_to_pclose pclose_arg;
+       fp = popen_noshell_compat(m_Command.c_str(), "r", &pclose_arg);
 #endif /* _MSC_VER */
 
-//     ostringstream outputbuf;
+       stringstream outputbuf;
 
        while (!feof(fp)) {
-               char buffer[128];
+               char buffer[512];
                size_t read = fread(buffer, 1, sizeof(buffer), fp);
 
                if (read == 0)
                        break;
 
-//             outputbuf.write(buffer, read);
+               outputbuf.write(buffer, read);
        }
 
-//     m_Result.Output = outputbuf.str();
-//     boost::algorithm::trim(m_Result.Output);
+       m_Result.Output = outputbuf.str();
+       boost::algorithm::trim(m_Result.Output);
 
        int status, exitcode;
 #ifdef _MSC_VER
        status = _pclose(fp);
 #else /* _MSC_VER */
-       status = pclose(fp);
+       status = pclose_noshell(&pclose_arg);
 #endif /* _MSC_VER */
 
 #ifndef _MSC_VER
index 8befe30ddd5b607de0baa4ce51c8df96eeb7cfc5..2e4968a84f785a2d2c7ff5c48b7604d5c914c14d 100644 (file)
@@ -3,4 +3,5 @@
 SUBDIRS = \
        ltdl \
        cJSON \
-       mmatch
+       mmatch \
+       popen-noshell
diff --git a/third-party/popen-noshell/Makefile.am b/third-party/popen-noshell/Makefile.am
new file mode 100644 (file)
index 0000000..f73162a
--- /dev/null
@@ -0,0 +1,9 @@
+## Process this file with automake to produce Makefile.in
+
+
+noinst_LTLIBRARIES =  \
+       libpopen_noshell.la
+
+libpopen_noshell_la_SOURCES =  \
+       popen_noshell.c \
+       popen_noshell.h
diff --git a/third-party/popen-noshell/README b/third-party/popen-noshell/README
new file mode 100644 (file)
index 0000000..586f878
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+This is the faster popen() alternative implementation.
+
+The package provides the following files:
+       * The C implementation of the library:
+               popen_noshell.c
+               popen_noshell.h
+       * Examples:
+               popen_noshell_examples.c
+       * Unit tests:
+               popen_noshell_tests.c
+
+Compile instructions are included in each file.
diff --git a/third-party/popen-noshell/performance_tests/fork-performance b/third-party/popen-noshell/performance_tests/fork-performance
new file mode 100755 (executable)
index 0000000..7a3d8ba
Binary files /dev/null and b/third-party/popen-noshell/performance_tests/fork-performance differ
diff --git a/third-party/popen-noshell/performance_tests/fork-performance.c b/third-party/popen-noshell/performance_tests/fork-performance.c
new file mode 100644 (file)
index 0000000..f9d577b
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#include <unistd.h>
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+#include "popen_noshell.h"
+
+/*
+ * This is a performance test program.
+ * It invokes the extremely light, statically build binary "./tiny2" which outputs "Hello, world!" and exits.
+ *
+ * Different approaches for calling "./tiny2" are tried, in order to compare their performance results.
+ *
+ */
+
+#define USE_LIBC_POPEN 0
+#define USE_NOSHELL_POPEN 1
+
+int use_noshell_compat = 0;
+
+void popen_test(int type) {
+       char *exec_file = "./tiny2";
+       char *arg1 = (char *) NULL;
+       char *argv[] = {exec_file, arg1};
+       FILE *fp;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+       int status;
+       char buf[64];
+
+       if (type) {
+               if (!use_noshell_compat) {
+                       fp = popen_noshell(exec_file, (const char * const *)argv, "r", &pclose_arg, 0);
+               } else {
+                       fp = popen_noshell_compat(exec_file, "r", &pclose_arg);
+                       argv[0] = NULL; // satisfy GCC warnings
+               }
+       } else {
+               fp = popen("./tiny2", "r");
+       }
+       if (!fp) {
+               err(EXIT_FAILURE, "popen()");
+       }
+
+       while (fgets(buf, sizeof(buf)-1, fp)) {
+               if (strcmp(buf, "Hello, world!\n") != 0) {
+                       errx(EXIT_FAILURE, "bad response: %s", buf);
+               }
+       }
+
+       if (type) {
+               status = pclose_noshell(&pclose_arg);
+       } else {
+               status = pclose(fp);
+       }
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose()");
+       }
+       if (status != 0) {
+               errx(EXIT_FAILURE, "status code is non-zero");
+       }
+}
+
+void fork_test(int type) {
+       pid_t pid;
+       int status;
+
+       if (type) {
+               pid = fork();
+       } else {
+               pid = vfork();
+       }
+       if (pid == -1) {
+               err(EXIT_FAILURE, "fork()");
+       }
+
+       if (pid == 0) { // child
+               execl("./tiny2", "./tiny2", (char *) NULL);
+               _exit(255);
+       }
+
+       // parent process
+       if (waitpid(pid, &status, 0) != pid) {
+               err(EXIT_FAILURE, "waitpid()");
+       }
+       if (status != 0) {
+               errx(EXIT_FAILURE, "status code is non-zero");
+       }
+}
+
+char *allocate_memory(int size_in_mb, int ratio) {
+       char *m;
+       int size;
+       int i;
+
+       size = size_in_mb*1024*1024;
+       m = malloc(sizeof(char) * size);
+       if (!m) {
+               err(EXIT_FAILURE, "malloc()");
+       }
+
+       /* allocate part of the memory, so that we can simulate some memory activity before fork()'ing */
+       if (ratio != 0) {
+               for (i = 0; i < size/ratio; ++i) {
+                       *(m + i) = 'z';
+               }
+       }
+
+       return m;
+}
+
+int safe_atoi(char *s) {
+       int i;
+
+       if (strlen(s) == 0) {
+               errx(EXIT_FAILURE, "safe_atoi(): String is empty");
+       }
+
+       for (i = 0; i < strlen(s); ++i) {
+               if (!isdigit(s[i])) {
+                       errx(EXIT_FAILURE, "safe_atoi(): Non-numeric characters found in string '%s'", s);
+               }
+       }
+
+       return atoi(s);
+}
+
+void parse_argv(int argc, char **argv, int *count, int *allocated_memory_size_in_mb, int *allocated_memory_usage_ratio, int *test_mode) {
+       const struct option long_options[] = {
+               {"count", 1, 0, 1},
+               {"memsize", 1, 0, 2},
+               {"ratio", 1, 0, 3},
+               {"mode", 1, 0, 4},
+               {0, 0, 0, 0}
+       };
+       int c;
+       int usage = 0;
+       int got[4];
+       int optarg_int;
+
+       memset(&got, 0, sizeof(got));
+       while (1) {
+               c = getopt_long(argc, argv, "", &long_options[0], NULL);
+               if (c == -1) { // no more arguments
+                       break;
+               }
+
+               if (c >= 1 && c <= sizeof(got)/sizeof(int)) {
+                       got[c-1] = 1;
+               }
+
+               if (!optarg) {
+                       warnx("You provided no value");
+                       usage = 1;
+                       break;
+               }
+               optarg_int = safe_atoi(optarg);
+
+               switch (c) {
+                       case 1:
+                               *count = optarg_int;
+                               break;
+                       case 2:
+                               *allocated_memory_size_in_mb = optarg_int;
+                               break;
+                       case 3:
+                               *allocated_memory_usage_ratio = optarg_int;
+                               break;
+                       case 4:
+                               *test_mode = optarg_int;
+                               break;
+                       default:
+                               warnx("Bad option");
+                               usage = 1;
+                               break;
+               }
+
+               if (usage) {
+                       break;
+               }
+       }
+
+       for (c = 0; c < sizeof(got)/sizeof(int); ++c) {
+               if (!got[c]) {
+                       warnx("Option #%d not specified", c);
+                       usage = 1;
+               }
+       }
+
+       if (usage) {
+               warnx("Usage: %s ...options - all are required...\n", argv[0]);
+               warnx("\t--count\n\t--memsize [MBytes]\n\t--ratio [0..N, 0=no_usage_of_memory]\n\t--mode [0..7]\n");
+               exit(EXIT_FAILURE);
+       }
+}
+
+int main(int argc, char **argv) {
+       int count;
+       char *m;
+       int allocated_memory_size_in_mb;
+       int allocated_memory_usage_ratio;
+       int test_mode;
+       int wrote = 0;
+
+       count = 30000;
+       allocated_memory_size_in_mb = 20;
+       allocated_memory_usage_ratio = 2; /* the memory usage is 1 divided by the "allocated_memory_usage_ratio", use 0 for "no usage" at all */
+       test_mode = 5;
+
+       parse_argv(argc, argv, &count, &allocated_memory_size_in_mb, &allocated_memory_usage_ratio, &test_mode);
+
+       m = allocate_memory(allocated_memory_size_in_mb, allocated_memory_usage_ratio);
+
+       warnx("Test options: count=%d, memsize=%d, ratio=%d, mode=%d", count, allocated_memory_size_in_mb, allocated_memory_usage_ratio, test_mode);
+
+       while (count--) {
+               switch (test_mode) {
+                       /* the following fork + exec calls do not return the output of their commands */
+                       case 0:
+                               if (!wrote) warnx("fork() + exec(), standard Libc");
+                               fork_test(1);
+                               break;
+                       case 1:
+                               if (!wrote) warnx("vfork() + exec(), standard Libc");
+                               fork_test(0);
+                               break;
+                       case 2:
+                               if (!wrote) warnx("system(), standard Libc");
+                               system("./tiny2 >/dev/null");
+                               break;
+
+                       /* all the below popen() use-cases are tested if they return the correct string in *fp */
+                       case 3:
+                               if (!wrote) warnx("popen(), standard Libc");
+                               popen_test(USE_LIBC_POPEN);
+                               break;
+                       case 4:
+                               use_noshell_compat = 0;
+                               if (!wrote) warnx("the new noshell, debug fork(), compat=%d", use_noshell_compat);
+                               popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_FORK);
+                               popen_test(USE_NOSHELL_POPEN);
+                               break;
+                       case 5:
+                               use_noshell_compat = 0;
+                               if (!wrote) warnx("the new noshell, default vfork(), compat=%d", use_noshell_compat);
+                               popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_CLONE);
+                               popen_test(USE_NOSHELL_POPEN);
+                               break;
+                       case 6:
+                               use_noshell_compat = 1;
+                               if (!wrote) warnx("the new noshell, debug fork(), compat=%d", use_noshell_compat);
+                               popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_FORK);
+                               popen_test(USE_NOSHELL_POPEN);
+                               break;
+                       case 7:
+                               use_noshell_compat = 1;
+                               if (!wrote) warnx("the new noshell, default vfork(), compat=%d", use_noshell_compat);
+                               popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_CLONE);
+                               popen_test(USE_NOSHELL_POPEN);
+                               break;
+                       default:
+                               errx(EXIT_FAILURE, "Bad mode");
+                               break;
+               }
+               wrote = 1;
+       }
+
+       return 0;
+}
diff --git a/third-party/popen-noshell/performance_tests/popen_noshell.c b/third-party/popen-noshell/performance_tests/popen_noshell.c
new file mode 120000 (symlink)
index 0000000..10c53de
--- /dev/null
@@ -0,0 +1 @@
+../popen_noshell.c
\ No newline at end of file
diff --git a/third-party/popen-noshell/performance_tests/popen_noshell.h b/third-party/popen-noshell/performance_tests/popen_noshell.h
new file mode 120000 (symlink)
index 0000000..4dc8458
--- /dev/null
@@ -0,0 +1 @@
+../popen_noshell.h
\ No newline at end of file
diff --git a/third-party/popen-noshell/performance_tests/run-tests.pl b/third-party/popen-noshell/performance_tests/run-tests.pl
new file mode 100755 (executable)
index 0000000..00a2028
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/perl
+# popen_noshell: A faster implementation of popen() and system() for Linux.
+# Copyright (c) 2009 Ivan Zahariev (famzah)
+# Version: 1.0
+#
+# This program 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; under 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses>.
+use strict;
+use warnings;
+
+use Data::Dumper;
+
+my $count = 10000;
+my $memsize = 20;
+my $ratio = 2;
+my $allowed_deviation = 20; # +/- percent
+my $repeat_tests = 3;
+my $uname_args = '-s -r -m'; # or just "-a" :)
+
+my $s;
+my @lines;
+my $line;
+my $i;
+my $options;
+my $caption;
+my $user_t;
+my $sys_t;
+my %results;
+my $k;
+my $mode;
+my $avg_user_t;
+my $avg_sys_t;
+my $success;
+my @sheet = ();
+
+sub parse_die($$) {
+       my ($i, $line) = @_;
+       die("Unable to parse line $i: $line");
+}
+
+if ($repeat_tests < 2) {
+       die('repeat_tests must be at least 2');
+}
+
+$options = undef;
+print "The tests are being performed, this will take some time...\n\n";
+for $mode (0..7) {
+       print(('-'x80)."\n\n");
+       for (1..$repeat_tests) {
+               $s = `gcc -Wall fork-performance.c popen_noshell.c -o fork-performance && time ./fork-performance --count=$count --memsize=$memsize --ratio=$ratio --mode=$mode 2>&1 >/dev/null`;
+               print "$s\n";
+
+               @lines = split(/\n/, $s);
+               $i = 0;
+               $caption = $user_t = $sys_t = undef;
+               foreach $line (@lines) {
+                       ++$i;
+                       if ($i == 1) {
+                               if ($line =~ /^fork-performance: Test options: (.+), mode=\d+$/) {
+                                       if (!defined($options)) {
+                                               $options = $1;
+                                       } else {
+                                               if ($options ne $1) {
+                                                       die("Parsed options is not the same: $options vs. $1");
+                                               }
+                                       }
+                               } else {
+                                       parse_die($i, $line);
+                               }
+                       } elsif ($i == 2) {
+                               if ($line =~ /^fork-performance: (.+)$/) {
+                                       $caption = $1;
+                               } else {
+                                       parse_die($i, $line);
+                               }
+                       } elsif ($i == 3) {
+                               if ($line =~ /(\d+.\d+)\s?user\s+(\d+.\d+)\s?sys(?:tem)?/) {
+                                       $user_t = $1;
+                                       $sys_t = $2;
+                               } else {
+                                       parse_die($i, $line);
+                               }
+                       } elsif ($i == 4) {
+                               # noop
+                       } else {
+                               parse_die($i, $line);
+                       }
+               }
+               if (!defined($options) || !defined($caption) || !defined($user_t) || !defined($sys_t)) {
+                       die('Parsing failed');
+               }
+               $k = $caption;
+               if (!exists($results{$k})) {
+                       $results{$k} = [];
+               }
+               push(@{$results{$k}}, [$user_t, $sys_t]);
+       }
+}
+
+sub print_deviation($$$) {
+       my ($label, $val, $avg_val) = @_;
+       my $deviation;
+       my $retval = 1;
+
+       if ($avg_val == 0) {
+               $deviation = '?? ';
+               $retval = 0;
+       } else {
+               $deviation = (($val / $avg_val) - 1) * 100;
+               $deviation = sprintf('%.0f', $deviation);
+               if (abs($deviation) > $allowed_deviation) {
+                       $retval = 0;
+               }
+       }
+
+       printf("\t%s: %s (%4s%%)%s\n", $label, $val, $deviation, ($retval == 0 ? ' BAD VALUE' : ''));
+
+       return $retval;
+}
+
+$success = 1;
+print("\n\n".('-'x80)."\n");
+print "RAW PERFORMANCE TESTS RESULTS\n";
+print(('-'x80)."\n");
+foreach $k (keys %results) {
+       $avg_user_t = 0;
+       $avg_sys_t = 0;
+       $i = 0;
+       foreach (@{$results{$k}}) {
+               ($user_t, $sys_t) = @{$_};
+               $avg_user_t += $user_t;
+               $avg_sys_t += $sys_t;
+               ++$i;
+       }
+       if ($i != $repeat_tests) {
+               die("Sanity check failed for count: $count vs. $i");
+       }
+       $avg_user_t /= $i;
+       $avg_sys_t  /= $i;
+       $avg_user_t = sprintf('%.2f', $avg_user_t);
+       $avg_sys_t = sprintf('%.2f', $avg_sys_t);
+
+       $s = sprintf("%s | avg_user_t | %s | avg_sys_t | %s | total_t | %s\n", $k, $avg_user_t, $avg_sys_t, ($avg_user_t + $avg_sys_t));
+       push(@sheet, $s);
+       print $s;
+
+       foreach (@{$results{$k}}) {
+               ($user_t, $sys_t) = @{$_};
+
+               $success &= print_deviation('user_t', $user_t, $avg_user_t);
+               $success &= print_deviation('sys_t ', $sys_t, $avg_sys_t);
+       }
+}
+
+print("\n\n".('-'x80)."\n");
+print "PERFORMANCE TESTS REPORT\n";
+print(('-'x80)."\n");
+print "System and setup:\n\t | ".`uname $uname_args`."\t | $options\n";
+print "Here is the data for the graphs:\n";
+foreach (@sheet) {
+       print "\t | ".$_;
+}
+print(('-'x80)."\n");
+
+if (!$success) {
+       print "\nWARNING! Some of the measurements were not accurate enough!\n";
+       print "It is recommended that you re-run the test having the following in mind:\n";
+       print "\t* the machine must be idle and not busy with other tasks\n";
+       print "\t* increase the 'count' to a larger number to have more accurate results\n";
+       print "\t* it is recommended that 'count' is so big, that the user/sys average time is at least bigger than 1.00\n";
+}
diff --git a/third-party/popen-noshell/performance_tests/tiny/freebsd/make.sh b/third-party/popen-noshell/performance_tests/tiny/freebsd/make.sh
new file mode 100755 (executable)
index 0000000..2cbc393
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+nasm -f elf tiny.asm
+ld -s -o tiny2 tiny.o
diff --git a/third-party/popen-noshell/performance_tests/tiny/freebsd/system.inc b/third-party/popen-noshell/performance_tests/tiny/freebsd/system.inc
new file mode 100644 (file)
index 0000000..d2345d7
--- /dev/null
@@ -0,0 +1,37 @@
+%define stdin   0
+%define stdout  1
+%define stderr  2
+
+%define    SYS_nosys   0
+%define SYS_exit    1
+%define SYS_fork    2
+%define SYS_read    3
+%define SYS_write   4
+
+section    .text
+align 4
+access.the.bsd.kernel:
+    int 80h
+    ret
+
+%macro system  1
+    mov eax, %1
+    call    access.the.bsd.kernel
+%endmacro
+
+%macro sys.exit    0
+    system  SYS_exit
+%endmacro
+
+%macro  sys.fork    0
+    system  SYS_fork
+%endmacro
+
+%macro  sys.read    0
+    system  SYS_read
+%endmacro
+
+%macro  sys.write   0
+    system  SYS_write
+%endmacro
+
diff --git a/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny.asm b/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny.asm
new file mode 100644 (file)
index 0000000..ef90704
--- /dev/null
@@ -0,0 +1,18 @@
+; http://www.freebsd.org/doc/en/books/developers-handbook/x86-first-program.html
+
+   %include    'system.inc'
+
+section .data
+hello   db  'Hello, world!', 0Ah
+hbytes  equ $-hello
+
+section .text
+global  _start
+_start:
+push    dword hbytes
+push    dword hello
+push    dword stdout
+sys.write
+
+push    dword 0
+sys.exit
diff --git a/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny2 b/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny2
new file mode 100755 (executable)
index 0000000..93656c5
Binary files /dev/null and b/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny2 differ
diff --git a/third-party/popen-noshell/performance_tests/tiny/linux/make.sh b/third-party/popen-noshell/performance_tests/tiny/linux/make.sh
new file mode 100755 (executable)
index 0000000..4524164
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nasm -f elf tiny2.asm && ld -s -o tiny2 tiny2.o
diff --git a/third-party/popen-noshell/performance_tests/tiny/linux/tiny2 b/third-party/popen-noshell/performance_tests/tiny/linux/tiny2
new file mode 100755 (executable)
index 0000000..3893c34
Binary files /dev/null and b/third-party/popen-noshell/performance_tests/tiny/linux/tiny2 differ
diff --git a/third-party/popen-noshell/performance_tests/tiny/linux/tiny2.asm b/third-party/popen-noshell/performance_tests/tiny/linux/tiny2.asm
new file mode 100644 (file)
index 0000000..99d1754
--- /dev/null
@@ -0,0 +1,30 @@
+; Compile with: nasm -f elf tiny2.asm && ld -s -o tiny2 tiny2.o
+;
+; Resource: http://www.faqs.org/docs/Linux-HOWTO/Assembly-HOWTO.html#AEN853
+
+section .data                          ;section declaration
+
+msg     db      "Hello, world!",0xa    ;our dear string
+len     equ     $ - msg                 ;length of our dear string
+
+section .text                          ;section declaration
+
+                       ;we must export the entry point to the ELF linker or
+    global _start      ;loader. They conventionally recognize _start as their
+                       ;entry point. Use ld -e foo to override the default.
+
+_start:
+
+;write our string to stdout
+
+        mov     edx,len ;third argument: message length
+        mov     ecx,msg ;second argument: pointer to message to write
+        mov     ebx,1   ;first argument: file handle (stdout)
+        mov     eax,4   ;system call number (sys_write)
+        int     0x80   ;call kernel
+
+;and exit
+
+       mov     ebx,0   ;first syscall argument: exit code
+        mov     eax,1   ;system call number (sys_exit)
+        int     0x80   ;call kernel
diff --git a/third-party/popen-noshell/performance_tests/tiny2 b/third-party/popen-noshell/performance_tests/tiny2
new file mode 120000 (symlink)
index 0000000..e22eb9b
--- /dev/null
@@ -0,0 +1 @@
+tiny/linux/tiny2
\ No newline at end of file
diff --git a/third-party/popen-noshell/popen_noshell.c b/third-party/popen-noshell/popen_noshell.c
new file mode 100644 (file)
index 0000000..3125144
--- /dev/null
@@ -0,0 +1,551 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#include "popen_noshell.h"
+#include <errno.h>
+#include <unistd.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <sched.h>
+#include <linux/sched.h>
+
+/*
+ * Wish-list:
+ *     1) Make the "ignore_stderr" parameter a mode - ignore, leave unchanged, redirect to stdout (the last is not implemented yet)
+ *     2) Code a faster system(): system_noshell(), system_noshell_compat()
+ */
+
+//#define POPEN_NOSHELL_DEBUG
+
+// because of C++, we can't call err() or errx() within the child, because they call exit(), and _exit() is what must be called; so we wrap
+#define _ERR(EVAL, FMT, ...) \
+       { \
+               warn(FMT, ##__VA_ARGS__); \
+               _exit(EVAL); \
+       }
+#define _ERRX(EVAL, FMT, ...) \
+       { \
+               warnx(FMT, ##__VA_ARGS__); \
+               _exit(EVAL); \
+       }
+
+int _popen_noshell_fork_mode = POPEN_NOSHELL_MODE_CLONE;
+
+void popen_noshell_set_fork_mode(int mode) { // see "popen_noshell.h" POPEN_NOSHELL_MODE_* constants
+       _popen_noshell_fork_mode = mode;
+}
+
+int popen_noshell_reopen_fd_to_dev_null(int fd) {
+       int dev_null_fd;
+
+       dev_null_fd = open("/dev/null", O_RDWR);
+       if (dev_null_fd < 0) return -1;
+
+       if (close(fd) != 0) {
+               return -1;
+       }
+       if (dup2(dev_null_fd, fd) == -1) {
+               return -1;
+       }
+       if (close(dev_null_fd) != 0) {
+               return -1;
+       }
+
+       return 0;
+}
+
+int _popen_noshell_close_and_dup(int pipefd[2], int closed_pipefd, int target_fd) {
+       int dupped_pipefd;
+
+       dupped_pipefd = (closed_pipefd == 0 ? 1 : 0); // get the FD of the other end of the pipe
+
+       if (close(pipefd[closed_pipefd]) != 0) {
+               return -1;
+       }
+
+       if (close(target_fd) != 0) {
+               return -1;
+       }
+       if (dup2(pipefd[dupped_pipefd], target_fd) == -1) {
+               return -1;
+       }
+       if (close(pipefd[dupped_pipefd]) != 0) {
+               return -1;
+       }
+
+       return 0;
+}
+
+void _pclose_noshell_free_clone_arg_memory(struct popen_noshell_clone_arg *func_args) {
+       char **cmd_argv;
+
+       free((char *)func_args->file);
+       cmd_argv = (char **)func_args->argv;
+       while (*cmd_argv) {
+               free(*cmd_argv);
+               ++cmd_argv;
+       }
+       free((char **)func_args->argv);
+       free(func_args);
+}
+
+void _popen_noshell_child_process(
+       /* We need the pointer *arg_ptr only to free whatever we reference if exec() fails and we were fork()'ed (thus memory was copied),
+        * not clone()'d */
+       struct popen_noshell_clone_arg *arg_ptr, /* NULL if we were called by pure fork() (not because of Valgrind) */
+       int pipefd_0, int pipefd_1, int read_pipe, int ignore_stderr, const char *file, const char * const *argv) {
+
+       int closed_child_fd;
+       int closed_pipe_fd;
+       int dupped_child_fd;
+       int pipefd[2] = {pipefd_0, pipefd_1};
+
+       if (ignore_stderr) { /* ignore STDERR completely? */
+               if (popen_noshell_reopen_fd_to_dev_null(STDERR_FILENO) != 0) _ERR(255, "popen_noshell_reopen_fd_to_dev_null(%d)", STDERR_FILENO);
+       }
+
+       if (read_pipe) {
+               closed_child_fd = STDIN_FILENO;         /* re-open STDIN to /dev/null */
+               closed_pipe_fd = 0;                     /* close read end of pipe */
+               dupped_child_fd = STDOUT_FILENO;        /* dup the other pipe end to STDOUT */
+       } else {
+               closed_child_fd = STDOUT_FILENO;        /* ignore STDOUT completely */
+               closed_pipe_fd = 1;                     /* close write end of pipe */
+               dupped_child_fd = STDIN_FILENO;         /* dup the other pipe end to STDIN */
+       }
+       if (popen_noshell_reopen_fd_to_dev_null(closed_child_fd) != 0) {
+               _ERR(255, "popen_noshell_reopen_fd_to_dev_null(%d)", closed_child_fd);
+       }
+       if (_popen_noshell_close_and_dup(pipefd, closed_pipe_fd, dupped_child_fd) != 0) {
+               _ERR(255, "_popen_noshell_close_and_dup(%d ,%d)", closed_pipe_fd, dupped_child_fd);
+       }
+
+       execvp(file, (char * const *)argv);
+
+       /* if we are here, exec() failed */
+
+       warn("exec(\"%s\") inside the child", file);
+
+#ifdef POPEN_NOSHELL_VALGRIND_DEBUG
+       if (arg_ptr) { /* not NULL if we were called by clone() */
+               /* but Valgrind does not support clone(), so we were actually called by fork(), thus memory was copied... */
+               /* free this copied memory; if it was not Valgrind, this memory would have been shared and would belong to the parent! */
+               _pclose_noshell_free_clone_arg_memory(arg_ptr);
+       }
+#endif
+
+       if (fflush(stdout) != 0) _ERR(255, "fflush(stdout)");
+       if (fflush(stderr) != 0) _ERR(255, "fflush(stderr)");
+       close(STDIN_FILENO);
+       close(STDOUT_FILENO);
+       close(STDERR_FILENO);
+
+       _exit(255); // call _exit() and not exit(), or you'll have troubles in C++
+}
+
+int popen_noshell_child_process_by_clone(void *raw_arg) {
+       struct popen_noshell_clone_arg *arg;
+
+       arg = (struct popen_noshell_clone_arg *)raw_arg;
+       _popen_noshell_child_process(arg, arg->pipefd_0, arg->pipefd_1, arg->read_pipe, arg->ignore_stderr, arg->file, arg->argv);
+
+       return 0;
+}
+
+char ** popen_noshell_copy_argv(const char * const *argv_orig) {
+       int size = 1; /* there is at least one NULL element */
+       char **argv;
+       char **argv_new;
+       int n;
+
+       argv = (char **) argv_orig;
+       while (*argv) {
+               ++size;
+               ++argv;
+       }
+
+       argv_new = (char **)malloc(sizeof(char *) * size);
+       if (!argv_new) return NULL;
+
+       argv = (char **)argv_orig;
+       n = 0;
+       while (*argv) {
+               argv_new[n] = strdup(*argv);
+               if (!argv_new[n]) return NULL;
+               ++argv;
+               ++n;
+       }
+       argv_new[n] = (char *)NULL;
+
+       return argv_new;
+}
+
+/*
+ * Similar to vfork() and threading.
+ * Starts a process which behaves like a thread (shares global variables in memory with the parent) but
+ * has a different PID and can call exec(), unlike traditional threads which are not allowed to call exec().
+ *
+ * This fork function is very resource-light because it does not copy any memory from the parent, but shares it.
+ *
+ * Like standard threads, you have to provide a start function *fn and arguments to it *arg. The life of the
+ * new vmfork()'ed process starts from this function.
+ *
+ * After you have reaped the child via waitpid(), you have to free() the memory at "*memory_to_free_on_child_exit".
+ *
+ * When the *fn function returns, the child process terminates.  The integer returned by *fn is the exit code for the child process.
+ * The child process may also terminate explicitly by calling exit(2) or after receiving a fatal signal.
+ *
+ * Returns -1 on error. On success returns the PID of the newly created child.
+ */
+pid_t popen_noshell_vmfork(int (*fn)(void *), void *arg, void **memory_to_free_on_child_exit) {
+               void *stack, *stack_aligned;
+               pid_t pid;
+
+               stack = malloc(POPEN_NOSHELL_STACK_SIZE + 15);
+               if (!stack) return -1;
+               *memory_to_free_on_child_exit = stack;
+
+               /*
+                * On all supported Linux platforms the stack grows down, except for HP-PARISC.
+                * You can grep the kernel source for "STACK_GROWSUP", in order to get this information.
+                */
+               // stack grows down, set pointer at the end of the block
+               stack_aligned = (void *) ((char * /*byte*/)stack + POPEN_NOSHELL_STACK_SIZE/*bytes*/);
+
+               /*
+                * On all supported platforms by GNU libc, the stack is aligned to 16 bytes, except for the SuperH platform which is aligned to 8 bytes.
+                * You can grep the glibc source for "STACK_ALIGN", in order to get this information.
+                */
+               stack_aligned = (void *) ( ((uintptr_t)stack_aligned+15) & ~ 0x0F ); // align to 16 bytes
+
+               /*
+                * Maybe we could have used posix_memalign() here...
+                * Our implementation seems a bit more portable though - I've read somewhere that posix_memalign() is not supported on all platforms.
+                * The above malloc() + align implementation is taken from:
+                *      http://stackoverflow.com/questions/227897/solve-the-memory-alignment-in-c-interview-question-that-stumped-me
+                */
+               
+#ifndef POPEN_NOSHELL_VALGRIND_DEBUG
+               pid = clone(fn, stack_aligned, CLONE_VM | SIGCHLD, arg);
+#else
+               pid = fork(); // Valgrind does not support arbitrary clone() calls, so we use fork for the tests
+#endif
+               if (pid == -1) return -1;
+
+               if (pid == 0) { // child
+#ifdef POPEN_NOSHELL_VALGRIND_DEBUG
+                       free(stack); // this is a copy because of the fork(), we are not using it at all
+                       _exit(fn(arg)); // if we used fork() because of Valgrind, invoke the child function manually; always use _exit()
+#endif
+                       errx(EXIT_FAILURE, "This must never happen");
+               } // child life ends here, for sure
+
+               return pid;
+}
+
+/*
+ * Pipe stream to or from process. Similar to popen(), only much faster.
+ *
+ * "file" is the command to be executed. It is searched within the PATH environment variable.
+ * "argv[]" is an array to "char *" elements which are passed as command-line arguments.
+ *     Note: The first element must be the same string as "file".
+ *     Note: The last element must be a (char *)NULL terminating element.
+ * "type" specifies if we are reading from the STDOUT or writing to the STDIN of the executed command "file". Use "r" for reading, "w" for writing.
+ * "pid" is a pointer to an interger. The PID of the child process is stored there.
+ * "ignore_stderr" has the following meaning:
+ *     0: leave STDERR of the child process attached to the current STDERR of the parent process
+ *     1: ignore the STDERR of the child process
+ *
+ * This function is not very sustainable on failures. This means that if it fails for some reason (out of memory, no such executable, etc.),
+ * you are probably in trouble, because the function allocated some memory or file descriptors and never released them.
+ * Normally, this function should never fail.
+ *
+ * Returns NULL on any error, "errno" is set appropriately.
+ * On success, a stream pointer is returned.
+ *     When you are done working with the stream, you have to close it by calling pclose_noshell(), or else you will leave zombie processes.
+ */
+FILE *popen_noshell(const char *file, const char * const *argv, const char *type, struct popen_noshell_pass_to_pclose *pclose_arg, int ignore_stderr) {
+       int read_pipe;
+       int pipefd[2]; // 0 -> READ, 1 -> WRITE ends
+       pid_t pid;
+       FILE *fp;
+
+       memset(pclose_arg, 0, sizeof(struct popen_noshell_pass_to_pclose));
+
+       if (strcmp(type, "r") == 0) {
+               read_pipe = 1;
+       } else if (strcmp(type, "w") == 0) {
+               read_pipe = 0;
+       } else {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (pipe(pipefd) != 0) return NULL;
+
+       if (_popen_noshell_fork_mode) { // use fork()
+
+               pid = fork();
+               if (pid == -1) return NULL;
+               if (pid == 0) {
+                       _popen_noshell_child_process(NULL, pipefd[0], pipefd[1], read_pipe, ignore_stderr, file, argv);
+                       errx(EXIT_FAILURE, "This must never happen");
+               } // child life ends here, for sure
+
+       } else { // use clone()
+
+               struct popen_noshell_clone_arg *arg = NULL;
+
+               arg = (struct popen_noshell_clone_arg*) malloc(sizeof(struct popen_noshell_clone_arg));
+               if (!arg) return NULL;
+
+               /* Copy memory structures, so that nobody can free() our memory while we use it in the child! */
+               arg->pipefd_0 = pipefd[0];
+               arg->pipefd_1 = pipefd[1];
+               arg->read_pipe = read_pipe;
+               arg->ignore_stderr = ignore_stderr;
+               arg->file = strdup(file);
+               if (!arg->file) return NULL;
+               arg->argv = (const char * const *)popen_noshell_copy_argv(argv);
+               if (!arg->argv) return NULL;
+
+               pclose_arg->free_clone_mem = 1;
+               pclose_arg->func_args = arg;
+               pclose_arg->stack = NULL; // we will populate it below
+
+               pid = popen_noshell_vmfork(&popen_noshell_child_process_by_clone, arg, &(pclose_arg->stack));
+               if (pid == -1) return NULL;
+
+       } // done: using clone()
+
+       /* parent process */
+
+       if (read_pipe) {
+               if (close(pipefd[1/*write*/]) != 0) return NULL;
+               fp = fdopen(pipefd[0/*read*/], "r");
+       } else { // write_pipe
+               if (close(pipefd[0/*read*/]) != 0) return NULL;
+               fp = fdopen(pipefd[1/*write*/], "w");
+       }
+       if (fp == NULL) {
+               return NULL; // fdopen() failed
+       }
+
+       pclose_arg->fp = fp;
+       pclose_arg->pid = pid;
+       
+       return fp; // we should never end up here
+}
+
+int popen_noshell_add_ptr_to_argv(char ***argv, int *count, char *start) {
+               *count += 1;
+               *argv = (char **) realloc(*argv, *count * sizeof(char **));
+               if (*argv == NULL) {
+                       return -1;
+               }
+               *(*argv + *count - 1) = start;
+               return 0;
+}
+
+int _popen_noshell_add_token(char ***argv, int *count, char *start, char *command, int *j) {
+       if (start != NULL && command + *j - 1 - start >= 0) {
+               command[*j] = '\0'; // terminate the token in memory
+               *j += 1;
+#ifdef POPEN_NOSHELL_DEBUG
+               printf("Token: %s\n", start);
+#endif
+               if (popen_noshell_add_ptr_to_argv(argv, count, start) != 0) {
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+#define _popen_noshell_split_return_NULL { if (argv != NULL) free(argv); if (command != NULL) free(command); return NULL; }
+char ** popen_noshell_split_command_to_argv(const char *command_original, char **free_this_buf) {
+       char *command;
+       size_t i, len;
+       char *start = NULL;
+       char c;
+       char **argv = NULL;
+       int count = 0;
+       const char _popen_bash_meta_characters[] = "!\\$`\n|&;()<>";
+       int in_sq = 0;
+       int in_dq = 0;
+       int j = 0;
+#ifdef POPEN_NOSHELL_DEBUG
+       char **tmp;
+#endif
+
+       command = (char *)calloc(strlen(command_original) + 1, sizeof(char));
+       if (!command) _popen_noshell_split_return_NULL;
+
+       *free_this_buf = command;
+
+       len = strlen(command_original); // get the original length
+       j = 0;
+       for (i = 0; i < len; ++i) {
+               if (!start) start = command + j;
+               c = command_original[i];
+
+               if (index(_popen_bash_meta_characters, c) != NULL) {
+                       errno = EINVAL;
+                       _popen_noshell_split_return_NULL;
+               }
+
+               if (c == ' ' || c == '\t') {
+                       if (in_sq || in_dq) {
+                               command[j++] = c;
+                               continue;
+                       }
+
+                       // new token
+                       if (_popen_noshell_add_token(&argv, &count, start, command, &j) != 0) {
+                               _popen_noshell_split_return_NULL;
+                       }
+                       start = NULL;
+                       continue;
+               }
+
+               if (c == '\'' && !in_dq) {
+                       in_sq = !in_sq;
+                       continue;
+               }
+               if (c == '"' && !in_sq) {
+                       in_dq = !in_dq;
+                       continue;
+               }
+
+               command[j++] = c;
+       }
+       if (in_sq || in_dq) { // unmatched single/double quote
+               errno = EINVAL;
+               _popen_noshell_split_return_NULL;
+       }
+
+       if (_popen_noshell_add_token(&argv, &count, start, command, &j) != 0) {
+               _popen_noshell_split_return_NULL;
+       }
+
+       if (count == 0) {
+               errno = EINVAL;
+               _popen_noshell_split_return_NULL;
+       }
+
+       if (popen_noshell_add_ptr_to_argv(&argv, &count, NULL) != 0) { // NULL-terminate the list
+               _popen_noshell_split_return_NULL;
+       }
+
+#ifdef POPEN_NOSHELL_DEBUG
+       tmp = argv;
+       while (*tmp) {
+               printf("ARGV: |%s|\n", *tmp);
+               ++tmp;
+       }
+#endif
+
+       return argv;
+
+       /* Example test strings:
+               "a'zz bb edd"
+               " abc ff  "
+               " abc ff"
+               "' abc   ff  ' "
+               ""
+               "     "
+               "     '"
+               "ab\\c"
+               "ls -la /proc/self/fd 'z'  'ab'g'z\" zz'   \" abc'd\" ' ab\"c   def '"
+       */
+}
+
+/*
+ * Pipe stream to or from process. Similar to popen(), only much faster.
+ *
+ * This is simpler than popen_noshell() but is more INSECURE.
+ * Since shells have very complicated expansion, quoting and word splitting algorithms, we do NOT try to re-implement them here.
+ * This function does NOT support any special characters. It will immediately return an error if such symbols are encountered in "command".
+ * The "command" is split only by space and tab delimiters. The special symbols are pre-defined in _popen_bash_meta_characters[].
+ * The only special characters supported are single and double quotes. You can enclose arguments in quotes and they should be splitted correctly.
+ *
+ * If possible, use popen_noshell() because of its better security.
+ *
+ * "command" is the command and its arguments to be executed. The command is searched within the PATH environment variable.
+ *     The whole "command" string is parsed and splitted, so that it can be directly given to popen_noshell() and resp. to exec().
+ *     This parsing is very simple and may contain bugs (see above). If possible, use popen_noshell() directly.
+ * "type" specifies if we are reading from the STDOUT or writing to the STDIN of the executed command. Use "r" for reading, "w" for writing.
+ * "pid" is a pointer to an interger. The PID of the child process is stored there.
+ *
+ * Returns NULL on any error, "errno" is set appropriately.
+ * On success, a stream pointer is returned.
+ *     When you are done working with the stream, you have to close it by calling pclose_noshell(), or else you will leave zombie processes.
+ */
+FILE *popen_noshell_compat(const char *command, const char *type, struct popen_noshell_pass_to_pclose *pclose_arg) {
+       char **argv;
+       FILE *fp;
+       char *to_free;
+
+       argv = popen_noshell_split_command_to_argv(command, &to_free);
+       if (!argv) {
+               if (to_free) free(to_free);
+               return NULL;
+       }
+
+       fp = popen_noshell(argv[0], (const char * const *)argv, type, pclose_arg, 0);
+
+       free(to_free);
+       free(argv);
+
+       return fp;
+}
+
+/*
+ * You have to call this function after you have done working with the FILE pointer "fp" returned by popen_noshell() or by popen_noshell_compat().
+ *
+ * Returns -1 on any error, "errno" is set appropriately.
+ * Returns the "status" of the child process as returned by waitpid().
+ */
+int pclose_noshell(struct popen_noshell_pass_to_pclose *arg) {
+       int status;
+
+       if (fclose(arg->fp) != 0) {
+               return -1;
+       }
+
+       if (waitpid(arg->pid, &status, 0) != arg->pid) {
+               return -1;
+       }
+
+       if (arg->free_clone_mem) {
+               free(arg->stack);
+               _pclose_noshell_free_clone_arg_memory(arg->func_args);
+       }
+
+       return status;
+}
diff --git a/third-party/popen-noshell/popen_noshell.h b/third-party/popen-noshell/popen_noshell.h
new file mode 100644 (file)
index 0000000..795dc8a
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#ifndef POPEN_NOSHELL_H
+#define POPEN_NOSHELL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* stack for the child process before it does exec() */
+#define POPEN_NOSHELL_STACK_SIZE 8*1024*1024 /* currently most Linux distros set this to 8 MBytes */
+
+/* constants to use with popen_noshell_set_fork_mode() */
+#define POPEN_NOSHELL_MODE_CLONE 0 /* default, faster */
+#define POPEN_NOSHELL_MODE_FORK 1 /* slower */
+
+struct popen_noshell_clone_arg {
+       int pipefd_0;
+       int pipefd_1;
+       int read_pipe;
+       int ignore_stderr;
+       const char *file;
+       const char * const *argv;
+};
+
+struct popen_noshell_pass_to_pclose {
+       FILE *fp;
+       pid_t pid;
+       int free_clone_mem;
+       void *stack;
+       struct popen_noshell_clone_arg *func_args;
+};
+
+/***************************
+ * PUBLIC FUNCTIONS FOLLOW *
+ ***************************/
+
+/* this is the native function call */
+FILE *popen_noshell(const char *file, const char * const *argv, const char *type, struct popen_noshell_pass_to_pclose *pclose_arg, int ignore_stderr);
+
+/* more insecure, but more compatible with popen() */
+FILE *popen_noshell_compat(const char *command, const char *type, struct popen_noshell_pass_to_pclose *pclose_arg);
+
+/* call this when you have finished reading and writing from/to the child process */
+int pclose_noshell(struct popen_noshell_pass_to_pclose *arg); /* the pclose() equivalent */
+
+/* this is the innovative faster vmfork() which shares memory with the parent and is very resource-light; see the source code for documentation */
+pid_t popen_noshell_vmfork(int (*fn)(void *), void *arg, void **memory_to_free_on_child_exit);
+
+/* used only for benchmarking purposes */
+void popen_noshell_set_fork_mode(int mode);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/third-party/popen-noshell/popen_noshell_examples.c b/third-party/popen-noshell/popen_noshell_examples.c
new file mode 100644 (file)
index 0000000..b2b20e4
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#include "popen_noshell.h"
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+
+/***********************************
+ * popen_noshell use-case examples *
+ ***********************************
+ *
+ * Compile and test via:
+ *     gcc -Wall popen_noshell.c popen_noshell_examples.c -o popen_noshell_examples && ./popen_noshell_examples
+ *
+ * If you want to profile using Valgrind, then compile and run via:
+ *     gcc -Wall -g -DPOPEN_NOSHELL_VALGRIND_DEBUG popen_noshell.c popen_noshell_examples.c -o popen_noshell_examples && \
+ *        valgrind -q --tool=memcheck --leak-check=yes --show-reachable=yes --track-fds=yes ./popen_noshell_examples
+ */
+
+void satisfy_open_FDs_leak_detection_and_exit() {
+       /* satisfy Valgrind FDs leak detection for the parent process */
+       if (fflush(stdout) != 0) err(EXIT_FAILURE, "fflush(stdout)");
+       if (fflush(stderr) != 0) err(EXIT_FAILURE, "fflush(stderr)");
+       close(STDIN_FILENO);
+       close(STDOUT_FILENO);
+       close(STDERR_FILENO);
+
+       exit(0);
+}
+
+void example_reading(int use_compat) {
+       FILE *fp;
+       char buf[256];
+       int status;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+
+       char *cmd = "ls -la /proc/self/fd"; /* used only by popen_noshell_compat(), we discourage this type of providing a command */
+
+       /* the command arguments used by popen_noshell() */
+       char *exec_file = "ls";
+       char *arg1 = "-la";
+       char *arg2 = "/proc/self/fd";
+       char *arg3 = (char *) NULL; /* last element */
+       char *argv[] = {exec_file, arg1, arg2, arg3}; /* NOTE! The first argv[] must be the executed *exec_file itself */
+       
+       if (use_compat) {
+               fp = popen_noshell_compat(cmd, "r", &pclose_arg);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell_compat()");
+               }
+       } else {
+               fp = popen_noshell(exec_file, (const char * const *)argv, "r", &pclose_arg, 0);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell()");
+               }
+       }
+
+       while (fgets(buf, sizeof(buf)-1, fp)) {
+               printf("Got line: %s", buf);
+       }
+
+       status = pclose_noshell(&pclose_arg);
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose_noshell()");
+       } else {
+               printf("The status of the child is %d. Note that this is not only the exit code. See man waitpid().\n", status);
+       }
+}
+
+void example_writing(int use_compat) {
+       FILE *fp;
+       int status;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+
+       char *cmd = "tee -a /tmp/popen-noshell.txt"; /* used only by popen_noshell_compat(), we discourage this type of providing a command */
+
+       /* the command arguments used by popen_noshell() */
+       char *exec_file = "tee";
+       char *arg1 = "-a";
+       char *arg2 = "/tmp/popen-noshell.txt";
+       char *arg3 = (char *) NULL; /* last element */
+       char *argv[] = {exec_file, arg1, arg2, arg3}; /* NOTE! The first argv[] must be the executed *exec_file itself */
+       
+       if (use_compat) {
+               fp = popen_noshell_compat(cmd, "w", &pclose_arg);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell_compat()");
+               }
+       } else {
+               fp = popen_noshell(exec_file, (const char * const *)argv, "w", &pclose_arg, 0);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell()");
+               }
+       }
+
+       if (fprintf(fp, "This is a test line, my pid is %d\n", (int)getpid()) < 0) {
+               err(EXIT_FAILURE, "fprintf()");
+       }
+
+       status = pclose_noshell(&pclose_arg);
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose_noshell()");
+       } else {
+               printf("The status of the child is %d. Note that this is not only the exit code. See man waitpid().\n", status);
+       }
+       
+       printf("Done, you can see the results by executing: cat %s\n", arg2);
+}
+
+int main() {
+       int try_compat;
+       int try_read;
+
+       /*
+        * Tune these options as you need.
+        */
+       try_compat = 0; /* or the more secure, but incompatible version of popen_noshell */
+       try_read = 1;   /* or write */
+
+       if (try_read) {
+               example_reading(try_compat);
+       } else {
+               example_writing(try_compat);
+       }
+
+       satisfy_open_FDs_leak_detection_and_exit();
+
+       return 0;
+}
diff --git a/third-party/popen-noshell/popen_noshell_tests.c b/third-party/popen-noshell/popen_noshell_tests.c
new file mode 100644 (file)
index 0000000..cde0bc6
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Version: 1.0
+ *
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#include "popen_noshell.h"
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+
+/***************************************************
+ * popen_noshell C unit test and use-case examples *
+ ***************************************************
+ *
+ * Compile and test via:
+ *     gcc -Wall popen_noshell.c popen_noshell_tests.c -o popen_noshell_tests && ./popen_noshell_tests
+ *
+ * Compile for debugging by Valgrind via:
+ *     gcc -Wall -g -DPOPEN_NOSHELL_VALGRIND_DEBUG popen_noshell.c popen_noshell_tests.c -o popen_noshell_tests
+ * Then start under Valgrind via:
+ *     valgrind -q --tool=memcheck --leak-check=yes --show-reachable=yes --track-fds=yes ./popen_noshell_tests
+ * If you want to filter Valgrind false reports about 0 opened file descriptors, add the following at the end:
+ *     2>&1|egrep -v '^==[[:digit:]]{1,5}==( | FILE DESCRIPTORS: 0 open at exit.)$'
+ */
+
+int do_unit_tests_ignore_stderr;
+
+void satisfy_open_FDs_leak_detection_and_exit() {
+       /* satisfy Valgrind FDs leak detection for the parent process */
+       if (fflush(stdout) != 0) err(EXIT_FAILURE, "fflush(stdout)");
+       if (fflush(stderr) != 0) err(EXIT_FAILURE, "fflush(stderr)");
+       close(STDIN_FILENO);
+       close(STDOUT_FILENO);
+       close(STDERR_FILENO);
+
+       exit(0);
+}
+
+void assert_string(char *expected, char *got, const char *assert_desc) {
+       if (strcmp(expected, got) != 0) errx(EXIT_FAILURE, "%s: Expected '%s', got '%s'", assert_desc, expected, got);
+}
+
+void assert_int(int expected, int got, const char *assert_desc) {
+       if (expected != got) errx(EXIT_FAILURE, "%s: Expected %d, got %d", assert_desc, expected, got);
+}
+
+void assert_status_not_internal_error(int status) {
+       assert_int(1, status >= 0, "assert_status_not_internal_error");
+}
+
+void assert_status_signal(int signal, int status) {
+       assert_status_not_internal_error(status);
+       assert_int(signal, status & 127, "assert_status_signal");
+}
+
+void assert_status_exit_code(int code, int status) {
+       assert_status_not_internal_error(status);
+       assert_status_signal(0, status);
+       assert_int(code, status >> 8, "assert_status_exit_code");
+}
+
+void example_reading(int use_compat) {
+       FILE *fp;
+       char buf[256];
+       int status;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+
+       char *cmd = "ls -la /proc/self/fd"; /* used only by popen_noshell_compat(), we discourage this type of providing a command */
+
+       /* the command arguments used by popen_noshell() */
+       char *exec_file = "ls";
+       char *arg1 = "-la";
+       char *arg2 = "/proc/self/fd";
+       char *arg3 = (char *) NULL; /* last element */
+       char *argv[] = {exec_file, arg1, arg2, arg3}; /* NOTE! The first argv[] must be the executed *exec_file itself */
+       
+       if (use_compat) {
+               fp = popen_noshell_compat(cmd, "r", &pclose_arg);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell_compat()");
+               }
+       } else {
+               fp = popen_noshell(exec_file, (const char * const *)argv, "r", &pclose_arg, 0);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell()");
+               }
+       }
+
+       while (fgets(buf, sizeof(buf)-1, fp)) {
+               printf("Got line: %s", buf);
+       }
+
+       status = pclose_noshell(&pclose_arg);
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose_noshell()");
+       } else {
+               printf("The status of the child is %d. Note that this is not only the exit code. See man waitpid().\n", status);
+       }
+}
+
+void example_writing(int use_compat) {
+       FILE *fp;
+       int status;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+
+       char *cmd = "tee -a /tmp/popen-noshell.txt"; /* used only by popen_noshell_compat(), we discourage this type of providing a command */
+
+       /* the command arguments used by popen_noshell() */
+       char *exec_file = "tee";
+       char *arg1 = "-a";
+       char *arg2 = "/tmp/popen-noshell.txt";
+       char *arg3 = (char *) NULL; /* last element */
+       char *argv[] = {exec_file, arg1, arg2, arg3}; /* NOTE! The first argv[] must be the executed *exec_file itself */
+       
+       if (use_compat) {
+               fp = popen_noshell_compat(cmd, "w", &pclose_arg);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell_compat()");
+               }
+       } else {
+               fp = popen_noshell(exec_file, (const char * const *)argv, "w", &pclose_arg, 0);
+               if (!fp) {
+                       err(EXIT_FAILURE, "popen_noshell()");
+               }
+       }
+
+       if (fprintf(fp, "This is a test line, my pid is %d\n", (int)getpid()) < 0) {
+               err(EXIT_FAILURE, "fprintf()");
+       }
+
+       status = pclose_noshell(&pclose_arg);
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose_noshell()");
+       } else {
+               printf("The status of the child is %d. Note that this is not only the exit code. See man waitpid().\n", status);
+       }
+       
+       printf("Done, you can see the results by executing: cat %s\n", arg2);
+}
+
+void unit_test(int reading, char *argv[], char *expected_string, int expected_signal, int expected_exit_code) {
+       FILE *fp;
+       char buf[256];
+       int status;
+       struct popen_noshell_pass_to_pclose pclose_arg;
+       char *received;
+       size_t received_size;
+
+       fp = popen_noshell(argv[0], (const char * const *)argv, reading ? "r" : "w", &pclose_arg, do_unit_tests_ignore_stderr);
+       if (!fp) err(EXIT_FAILURE, "popen_noshell");
+
+       if (reading) {
+               received_size = strlen(expected_string) + 256; // so that we can store a bit longer strings that we expected and discover the mismatch
+               received = alloca(received_size); // use alloca() or else the fork()'ed child will generate a Valgrind memory leak warning if exec() fails
+               if (!received) err(EXIT_FAILURE, "alloca");
+               memset(received, 0, received_size); // ensure a terminating null
+
+               while (fgets(buf, sizeof(buf) - 1, fp)) {
+                       strncat(received, buf, received_size - strlen(received) - 2);
+               }
+
+               assert_string(expected_string, received, "Received string");
+       }
+
+       status = pclose_noshell(&pclose_arg);
+       if (status == -1) {
+               err(EXIT_FAILURE, "pclose_noshell()");
+       } else {
+               if (expected_signal != 0) {
+                       assert_status_signal(expected_signal, status);
+               } else {
+                       assert_status_exit_code(expected_exit_code, status);
+               }
+       }
+
+       //free(received); // memory allocated by alloca() cannot be free()'d
+}
+
+void do_unit_tests() {
+       int test_num = 0;
+       int more_to_test = 1;
+       char *bin_bash = "/bin/bash";
+       char *bin_true = "/bin/true";
+       char *bin_cat  = "/bin/cat";
+       char *bin_echo = "/bin/echo";
+
+       do {
+               ++test_num;
+               switch (test_num) {
+                       case 1: {
+                               char *argv[] = {"/", NULL};
+                               unit_test(1, argv, "", 0, 255); // failed to execute binary (status code is -1, STDOUT is empty, STDERR text)
+                               break;
+                       }
+                       case 2: {
+                               char *argv[] = {bin_bash, "-c", "ulimit -t 1 && while [ 1 ]; do let COUNTER=COUNTER+1; done;", NULL};
+                               unit_test(1, argv, "", 9, 5); // process signalled with 9 due to CPU limit (STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 3: {
+                               char *argv[] = {bin_bash, "-c", "sleep 1; exit 1", NULL};
+                               unit_test(1, argv, "", 0, 1); // process exited with value 1   (STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 4: {
+                               char *argv[] = {bin_bash, "-c", "exit 255", NULL};
+                               unit_test(1, argv, "", 0, 255); // process exited with value 255 (STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 5: {
+                               char *argv[] = {bin_bash, "-c", "echo \"some err string\" 1>&2; exit 111", NULL};
+                               unit_test(1, argv, "", 0, 111); // process exited with value 111 (STDERR text, STDOUT is empty)
+                               break;
+                       }
+                       case 6: {
+                               char *argv[] = {bin_bash, "-c", "echo -en \"some err\\nstring v2\" 1>&2; echo -en \"some\\ngood text\"; exit 0", NULL};
+                               unit_test(1, argv, "some\ngood text", 0, 0); // process exited with value 0 (STDERR text, STDOUT text)
+                               break;
+                       }
+                       case 7: {
+                               char *argv[] = {bin_bash, "-c", "echo -e \"\" 1>&2; echo -e \"\"; exit 3", NULL};
+                               unit_test(1, argv, "\n", 0, 3); // process exited with value 3 (STDERR text, STDOUT text)
+                               break;
+                       }
+                       case 8: {
+                               char *argv[] = {bin_bash, NULL};
+                               unit_test(1, argv, "", 0, 0); // process exited with value 0 (single argument, STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 9: {
+                               char *argv[] = {bin_true, NULL};
+                               unit_test(1, argv, "", 0, 0); // process exited with value 0 (single argument, STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 10: {
+                               char *argv[] = {bin_cat, NULL}; // cat expects an input from STDIN
+                               unit_test(1, argv, "", 0, 0); // process exited with value 0 (single argument, STDOUT/ERR are empty)
+                               break;
+                       }
+                       case 11: {
+                               char *argv[] = {bin_echo, NULL};
+                               unit_test(1, argv, "\n", 0, 0); // process exited with value 0 (single argument, STDERR is empty, STDOUT text)
+                               break;
+                       }
+                       default:
+                               more_to_test = 0;
+                               break;
+               }
+       } while (more_to_test);
+
+       assert_int(11, test_num - 1, "Test count");
+}
+
+void proceed_to_unit_tests_and_exit() {
+       popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_CLONE); /* the default one */
+       do_unit_tests();
+       popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_FORK);
+       do_unit_tests();
+
+       printf("Tests passed OK.\n");
+
+       satisfy_open_FDs_leak_detection_and_exit();
+}
+
+int main() {
+       do_unit_tests_ignore_stderr = 1; /* do we ignore STDERR from the executed commands? */
+       proceed_to_unit_tests_and_exit();
+       satisfy_open_FDs_leak_detection_and_exit();
+
+       return 0;
+}
diff --git a/third-party/popen-noshell/popen_noshell_tests.cpp b/third-party/popen-noshell/popen_noshell_tests.cpp
new file mode 100644 (file)
index 0000000..f6ab754
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * popen_noshell: A faster implementation of popen() and system() for Linux.
+ * Copyright (c) 2009 Ivan Zahariev (famzah)
+ * Credits for the C++ test cases go to David Coz
+ * Version: 1.0
+ * 
+ * This program 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; under 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ */
+
+#include "popen_noshell.h"
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+
+/*****************************************************
+ * popen_noshell C++ unit test and use-case examples *
+ *****************************************************
+ *
+ * Compile and test via:
+ *     g++ -Wall popen_noshell.c popen_noshell_tests.cpp -o popen_noshell_tests_cpp && ./popen_noshell_tests_cpp
+ */
+
+#define DUMMY_SIZE 10000
+
+// Just a dummy structure that allocates memory at startup and releases it 
+// when exit() is called.
+
+struct Dummy {
+  Dummy() {
+    val = new char[DUMMY_SIZE];
+  }
+  ~Dummy() {
+    delete[] val;
+    val = NULL;
+  }
+  char* val;
+};
+
+static Dummy dummy;
+
+int main() {
+  FILE *fp;
+  char buf[256];
+  int status;
+  struct popen_noshell_pass_to_pclose pclose_arg;
+
+  popen_noshell_set_fork_mode(POPEN_NOSHELL_MODE_CLONE);
+
+  // We provide an invalid command, so that the child calls exit().
+  // Child's exit() will result in destruction of global objects, while these
+  // objects belong to the parent!
+  // Therefore, if the parent uses them after child's exit(), it is likely to
+  // lead to a crash.
+  
+  //char *exec_file = (char *) "ls";
+  char *exec_file = (char *) "lsaaa";
+  char *arg1 = (char *) "-la";
+  char *arg2 = (char *) "/proc/self/fd";
+  char *arg3 = (char *) NULL; /* last element */
+  char *argv[] = {exec_file, arg1, arg2, arg3}; /* NOTE! The first argv[] must be the executed *exec_file itself */
+
+  fp = popen_noshell(argv[0], (const char * const *)argv, "r", &pclose_arg, 0);
+  if (!fp) {
+    err(EXIT_FAILURE, "popen_noshell()");
+  }
+
+  while (fgets(buf, sizeof(buf)-1, fp)) {
+    printf("Got line: %s", buf);
+  }
+
+  status = pclose_noshell(&pclose_arg);
+  if (status == -1) {
+    err(EXIT_FAILURE, "pclose_noshell()");
+  } else {
+    printf("The status of the child is %d. Note that this is not only the exit code. See man waitpid().\n", status);
+  }
+
+  // Trying to access our global variable stuff.
+  // If exit() is used in the child process, dummy.val = 0 and we have a crash.
+  // With _exit(), dummy.val is still valid.
+
+  // printf("Accessing dummy stuff. dummy.val=%p\n", dummy.val);
+  memset(dummy.val, 42, DUMMY_SIZE);
+  printf("\nTests passed OK.\n");
+
+  return 0;
+}