Bugfixes.

This commit is contained in:
Gunnar Beutner 2012-06-24 18:44:07 +02:00
parent b414d5a952
commit 3d6df6611c
22 changed files with 38 additions and 1297 deletions

View File

@ -246,8 +246,6 @@ int TlsClient::SSLVerifyCertificate(int ok, X509_STORE_CTX *x509Context)
SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(x509Context, SSL_get_ex_data_X509_STORE_CTX_idx());
TlsClient *client = (TlsClient *)SSL_get_ex_data(ssl, m_SSLIndex);
assert(client->GetMutex().active_count);
if (client == NULL)
return 0;

View File

@ -44,10 +44,10 @@ void NagiosCheckTask::CheckThreadProc(void)
mutex::scoped_lock lock(m_Mutex);
map<int, NagiosCheckTask::Ptr> tasks;
const int maxTasks = 16;
const int maxTasks = 128;
for (;;) {
while (m_Tasks.empty() || tasks.size() >= maxTasks) {
while (m_Tasks.empty() || tasks.size() >= MaxChecksPerThread) {
lock.unlock();
map<int, NagiosCheckTask::Ptr>::iterator it, prev;
@ -202,7 +202,10 @@ void NagiosCheckTask::Register(void)
{
CheckTask::RegisterType("nagios", NagiosCheckTask::CreateTask, NagiosCheckTask::FlushQueue);
int numThreads = max(4, boost::thread::hardware_concurrency());
int numThreads = boost::thread::hardware_concurrency();
if (numThreads < 4)
numThreads = 4;
for (int i = 0; i < numThreads; i++) {
thread t(&NagiosCheckTask::CheckThreadProc);

View File

@ -10,6 +10,8 @@ public:
typedef shared_ptr<NagiosCheckTask> Ptr;
typedef weak_ptr<NagiosCheckTask> WeakPtr;
static const int MaxChecksPerThread = 128;
NagiosCheckTask(const Service& service);
virtual void Enqueue(void);

View File

@ -1,6 +1,7 @@
/*
* popen_noshell: A faster implementation of popen() and system() for Linux.
* Copyright (c) 2009 Ivan Zahariev (famzah)
* Copyright (c) 2012 Gunnar Beutner
* Version: 1.0
*
* This program is free software; you can redistribute it and/or modify
@ -22,9 +23,5 @@ 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.

View File

@ -1,290 +0,0 @@
/*
* 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;
}

View File

@ -1 +0,0 @@
../popen_noshell.c

View File

@ -1 +0,0 @@
../popen_noshell.h

View File

@ -1,180 +0,0 @@
#!/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";
}

View File

@ -1,3 +0,0 @@
#!/bin/sh
nasm -f elf tiny.asm
ld -s -o tiny2 tiny.o

View File

@ -1,37 +0,0 @@
%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

View File

@ -1,18 +0,0 @@
; 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

View File

@ -1,3 +0,0 @@
#!/bin/bash
nasm -f elf tiny2.asm && ld -s -o tiny2 tiny2.o

View File

@ -1,30 +0,0 @@
; 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

View File

@ -1 +0,0 @@
tiny/linux/tiny2

View File

@ -1,6 +1,7 @@
/*
* popen_noshell: A faster implementation of popen() and system() for Linux.
* Copyright (c) 2009 Ivan Zahariev (famzah)
* Copyright (c) 2012 Gunnar Beutner
* Version: 1.0
*
* This program is free software; you can redistribute it and/or modify
@ -29,12 +30,6 @@
#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)
@ -55,12 +50,6 @@
_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;
@ -80,7 +69,7 @@ int popen_noshell_reopen_fd_to_dev_null(int fd) {
return 0;
}
int _popen_noshell_close_and_dup(int pipefd[2], int closed_pipefd, int target_fd) {
static 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
@ -102,25 +91,10 @@ int _popen_noshell_close_and_dup(int pipefd[2], int closed_pipefd, int target_fd
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) {
static void popen_noshell_child_process(
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;
@ -142,8 +116,8 @@ void _popen_noshell_child_process(
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);
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);
@ -152,14 +126,6 @@ void _popen_noshell_child_process(
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);
@ -169,15 +135,6 @@ void _popen_noshell_child_process(
_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;
@ -206,69 +163,6 @@ char ** popen_noshell_copy_argv(const char * const *argv_orig) {
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.
*
@ -309,40 +203,12 @@ FILE *popen_noshell(const char *file, const char * const *argv, const char *type
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()
pid = vfork();
if (pid == -1) return NULL;
if (pid == 0) {
popen_noshell_child_process(pipefd[0], pipefd[1], read_pipe, ignore_stderr, file, argv);
errx(EXIT_FAILURE, "This must never happen");
} // child life ends here, for sure
/* parent process */
@ -373,7 +239,7 @@ int popen_noshell_add_ptr_to_argv(char ***argv, int *count, char *start) {
return 0;
}
int _popen_noshell_add_token(char ***argv, int *count, char *start, char *command, int *j) {
static 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;
@ -387,7 +253,7 @@ int _popen_noshell_add_token(char ***argv, int *count, char *start, char *comman
return 0;
}
#define _popen_noshell_split_return_NULL { if (argv != NULL) free(argv); if (command != NULL) free(command); return NULL; }
#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;
@ -395,7 +261,7 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
char c;
char **argv = NULL;
int count = 0;
const char _popen_bash_meta_characters[] = "!\\$`\n|&;()<>";
const char popen_bash_meta_characters[] = "!\\$`\n|&;()<>";
int in_sq = 0;
int in_dq = 0;
int j = 0;
@ -404,7 +270,7 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
#endif
command = (char *)calloc(strlen(command_original) + 1, sizeof(char));
if (!command) _popen_noshell_split_return_NULL;
if (!command) popen_noshell_split_return_NULL;
*free_this_buf = command;
@ -414,9 +280,9 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
if (!start) start = command + j;
c = command_original[i];
if (index(_popen_bash_meta_characters, c) != NULL) {
if (index(popen_bash_meta_characters, c) != NULL) {
errno = EINVAL;
_popen_noshell_split_return_NULL;
popen_noshell_split_return_NULL;
}
if (c == ' ' || c == '\t') {
@ -426,8 +292,8 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
}
// new token
if (_popen_noshell_add_token(&argv, &count, start, command, &j) != 0) {
_popen_noshell_split_return_NULL;
if (popen_noshell_add_token(&argv, &count, start, command, &j) != 0) {
popen_noshell_split_return_NULL;
}
start = NULL;
continue;
@ -446,20 +312,20 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
}
if (in_sq || in_dq) { // unmatched single/double quote
errno = EINVAL;
_popen_noshell_split_return_NULL;
popen_noshell_split_return_NULL;
}
if (_popen_noshell_add_token(&argv, &count, start, command, &j) != 0) {
_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;
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;
popen_noshell_split_return_NULL;
}
#ifdef POPEN_NOSHELL_DEBUG
@ -491,7 +357,7 @@ char ** popen_noshell_split_command_to_argv(const char *command_original, char *
* 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 "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.
@ -542,10 +408,5 @@ int pclose_noshell(struct popen_noshell_pass_to_pclose *arg) {
return -1;
}
if (arg->free_clone_mem) {
free(arg->stack);
_pclose_noshell_free_clone_arg_memory(arg->func_args);
}
return status;
}

View File

@ -1,6 +1,7 @@
/*
* popen_noshell: A faster implementation of popen() and system() for Linux.
* Copyright (c) 2009 Ivan Zahariev (famzah)
* Copyright (c) 2012 Gunnar Beutner
* Version: 1.0
*
* This program is free software; you can redistribute it and/or modify
@ -27,28 +28,9 @@ extern "C" {
#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;
};
/***************************
@ -64,12 +46,6 @@ FILE *popen_noshell_compat(const char *command, const char *type, struct popen_n
/* 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 */

View File

@ -1,147 +0,0 @@
/*
* 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;
}

View File

@ -1,286 +0,0 @@
/*
* 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;
}

View File

@ -1,99 +0,0 @@
/*
* 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;
}