From: Gunnar Beutner Date: Tue, 19 Jun 2012 17:05:24 +0000 (+0200) Subject: Use popen_noshell() instead of popen(). X-Git-Tag: v0.0.1~397 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1ce92cf29a4647a5158b229fef962586a03e0b62;p=icinga2 Use popen_noshell() instead of popen(). --- diff --git a/components/checker/checkercomponent.cpp b/components/checker/checkercomponent.cpp index c0cfa3ac8..2dd515407 100644 --- a/components/checker/checkercomponent.cpp +++ b/components/checker/checkercomponent.cpp @@ -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 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()); } diff --git a/configure.ac b/configure.ac index e5d9e2584..34e431f3a 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/icinga/Makefile.am b/icinga/Makefile.am index f5f19acb1..641c596a8 100644 --- a/icinga/Makefile.am +++ b/icinga/Makefile.am @@ -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 diff --git a/icinga/nagioschecktask.cpp b/icinga/nagioschecktask.cpp index 1b67bc17e..1bf3c44a5 100644 --- a/icinga/nagioschecktask.cpp +++ b/icinga/nagioschecktask.cpp @@ -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 diff --git a/third-party/Makefile.am b/third-party/Makefile.am index 8befe30dd..2e4968a84 100644 --- a/third-party/Makefile.am +++ b/third-party/Makefile.am @@ -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 index 000000000..f73162a44 --- /dev/null +++ b/third-party/popen-noshell/Makefile.am @@ -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 index 000000000..586f8781d --- /dev/null +++ b/third-party/popen-noshell/README @@ -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 . + */ + +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 index 000000000..7a3d8ba4c 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 index 000000000..f9d577b4e --- /dev/null +++ b/third-party/popen-noshell/performance_tests/fork-performance.c @@ -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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 000000000..10c53de12 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/popen_noshell.c @@ -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 index 000000000..4dc8458f3 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/popen_noshell.h @@ -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 index 000000000..00a202813 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/run-tests.pl @@ -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 . +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 index 000000000..2cbc39355 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny/freebsd/make.sh @@ -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 index 000000000..d2345d750 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny/freebsd/system.inc @@ -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 index 000000000..ef9070466 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny/freebsd/tiny.asm @@ -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 index 000000000..93656c51b 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 index 000000000..4524164c6 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny/linux/make.sh @@ -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 index 000000000..3893c34ce 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 index 000000000..99d17548a --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny/linux/tiny2.asm @@ -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 index 000000000..e22eb9bf7 --- /dev/null +++ b/third-party/popen-noshell/performance_tests/tiny2 @@ -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 index 000000000..312514429 --- /dev/null +++ b/third-party/popen-noshell/popen_noshell.c @@ -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 . + */ + +#include "popen_noshell.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include + +/* + * 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 index 000000000..795dc8ad0 --- /dev/null +++ b/third-party/popen-noshell/popen_noshell.h @@ -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 . + */ + +#ifndef POPEN_NOSHELL_H +#define POPEN_NOSHELL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include + +/* 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 index 000000000..b2b20e40b --- /dev/null +++ b/third-party/popen-noshell/popen_noshell_examples.c @@ -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 . + */ + +#include "popen_noshell.h" +#include +#include +#include +#include +#include + +/*********************************** + * 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 index 000000000..cde0bc6a1 --- /dev/null +++ b/third-party/popen-noshell/popen_noshell_tests.c @@ -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 . + */ + +#include "popen_noshell.h" +#include +#include +#include +#include +#include + +/*************************************************** + * 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 index 000000000..f6ab7541b --- /dev/null +++ b/third-party/popen-noshell/popen_noshell_tests.cpp @@ -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 . + */ + +#include "popen_noshell.h" +#include +#include +#include +#include +#include + +/***************************************************** + * 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; +}