From 26e10c60692b17fbab75325c21a1ac1aead98b44 Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Tue, 2 Oct 2012 02:16:22 +0200 Subject: [PATCH] Fix input device closing, and fix lipc-wait-event handling (using popen-noshell from http://code.google.com/p/popen-noshell/) Conflicts: input.c --- Makefile | 8 +- input.c | 112 +++++-- kpdfview.c | 17 +- launchpad/kpdf.sh | 4 - popen-noshell/README | 21 ++ popen-noshell/popen_noshell.c | 550 ++++++++++++++++++++++++++++++++++ popen-noshell/popen_noshell.h | 69 +++++ 7 files changed, 746 insertions(+), 35 deletions(-) create mode 100644 popen-noshell/README create mode 100644 popen-noshell/popen_noshell.c create mode 100644 popen-noshell/popen_noshell.h diff --git a/Makefile b/Makefile index 970259610..dc673dfc2 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ CRENGINEDIR=$(KPVCRLIBDIR)/crengine FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.10 LFSDIR=luafilesystem +POPENNSDIR=popen-noshell + # must point to directory with *.ttf fonts for crengine TTF_FONTS_DIR=$(MUPDFDIR)/fonts @@ -94,7 +96,7 @@ LUALIB := $(LUADIR)/src/libluajit.a all:kpdfview -kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) +kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o popen_noshell.o input.o util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) $(CC) \ $(CFLAGS) \ kpdfview.o \ @@ -102,6 +104,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft pdf.o \ blitbuffer.o \ drawcontext.o \ + popen_noshell.o \ input.o \ util.o \ ft.o \ @@ -136,6 +139,9 @@ cre.o: %.o: %.cpp lfs.o: $(LFSDIR)/src/lfs.c $(CC) -c $(CFLAGS) -I$(LUADIR)/src -I$(LFSDIR)/src $(LFSDIR)/src/lfs.c -o $@ +popen_noshell.o: $(POPENNSDIR)/popen_noshell.c + $(CC) -c $(CFLAGS) -I$(POPENNSDIR) $(POPENNSDIR)/popen_noshell.c -o $@ + fetchthirdparty: -rm -Rf mupdf/thirdparty test -d mupdf && (cd mupdf; git checkout .) || echo warn: mupdf folder not found diff --git a/input.c b/input.c index 87a1123fd..9176659f3 100644 --- a/input.c +++ b/input.c @@ -15,7 +15,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ + +#include "popen-noshell/popen_noshell.h" +#include #include +#include #include #include #include @@ -31,7 +35,6 @@ #include #include -#define OUTPUT_SIZE 21 #define CODE_IN_SAVER 10000 #define CODE_OUT_SAVER 10001 #define CODE_USB_PLUG_IN 10010 @@ -41,7 +44,18 @@ #define NUM_FDS 4 int inputfds[4] = { -1, -1, -1, -1 }; -int slider_pid = -1; +pid_t slider_pid = -1; +struct popen_noshell_pass_to_pclose pclose_arg; + +void slider_handler(int sig) +{ + int status; + /* Kill lipc-wait-event properly on exit */ + if(pclose_arg.pid != 0) { + status = kill(pclose_arg.pid, SIGTERM); + printf("[SIGTERM] kill returned %d\n", status); + } +} int findFreeFdSlot() { int i; @@ -63,7 +77,9 @@ static int openInputDevice(lua_State *L) { return luaL_error(L, "no free slot for new input device <%s>", inputdevice); } - if(!strcmp("fake_events",inputdevice)) { + printf("Opening input device <%s> in slot %d.\n", inputdevice, fd); + + if(!strcmp("slider",inputdevice)) { /* special case: the power slider */ int pipefd[2]; int childpid; @@ -73,10 +89,13 @@ static int openInputDevice(lua_State *L) { return luaL_error(L, "cannot fork() slider event listener"); } if(childpid == 0) { + // Setup signal handler, to cleanup on exit + signal(SIGTERM, slider_handler); + FILE *fp; - char std_out[OUTPUT_SIZE] = ""; + char std_out[256]; + int status; struct input_event ev; - int ret; __u16 key_code = 10000; close(pipefd[0]); @@ -86,16 +105,27 @@ static int openInputDevice(lua_State *L) { ev.value = 1; /* listen power slider events */ - while(1) { - fp = popen("exec lipc-wait-event com.lab126.powerd goingToScreenSaver,outOfScreenSaver,charging,notCharging", "r"); - /* @TODO 07.06 2012 (houqp) - * plugin and out event can only be watched by: - lipc-wait-event com.lab126.hal usbPlugOut,usbPlugIn - */ - if(fgets(std_out, OUTPUT_SIZE, fp) == NULL) { - break; - } - pclose(fp); + char *exec_file = "lipc-wait-event"; + char *arg1 = "-m"; // Hang for ever, don't exit on the first one, we're in a dedicated child, and we'll be closed on exit, so we don't care + char *arg2 = "-s"; + char *arg3 = "0"; + char *arg4 = "com.lab126.powerd"; + char *arg5 = "goingToScreenSaver,outOfScreenSaver,charging,notCharging"; + char *arg6 = (char *) NULL; + char *argv[] = {exec_file, arg1, arg2, arg3, arg4, arg5, arg6}; + /* @TODO 07.06 2012 (houqp) + * plugin and out event can only be watched by: + lipc-wait-event com.lab126.hal usbPlugOut,usbPlugIn + */ + + fp = popen_noshell(exec_file, (const char * const *)argv, "r", &pclose_arg, 0); + if (!fp) { + err(EXIT_FAILURE, "popen_noshell()"); + } + + printf("PID of our child is: %d (ours: %d)\n", (int)pclose_arg.pid, (int)getpid()); + + while(fgets(std_out, sizeof(std_out)-1, fp)) { if(std_out[0] == 'g') { ev.code = CODE_IN_SAVER; } else if(std_out[0] == 'o') { @@ -116,14 +146,32 @@ static int openInputDevice(lua_State *L) { /* generate event */ if(write(pipefd[1], &ev, sizeof(struct input_event)) == -1) { - break; + printf("Failed to generate event.\n"); } } - exit(0); /* cannot be reached?! */ + + status = pclose_noshell(&pclose_arg); + if (status == -1) { + err(EXIT_FAILURE, "pclose_noshell()"); + } else { + printf("Power slider event listener child exited with status %d.\n", status); + + if WIFEXITED(status) { + printf("Child exited normally with status: %d.\n", WEXITSTATUS(status)); + } + if WIFSIGNALED(status) { + printf("Child terminated by signal: %d.\n", WTERMSIG(status)); + } + } + + // We're done, go away :). + _exit(EXIT_SUCCESS); } else { + printf("Slider pipe close in slot %d [inputfd[fd]: %d].\n", fd, pipefd[0]); close(pipefd[1]); inputfds[fd] = pipefd[0]; slider_pid = childpid; + printf("slider_pid is: %d\n", (int)slider_pid); } } else { inputfds[fd] = open(inputdevice, O_RDONLY | O_NONBLOCK, 0); @@ -138,23 +186,32 @@ static int openInputDevice(lua_State *L) { if(SDL_Init(SDL_INIT_VIDEO) < 0) { return luaL_error(L, "cannot initialize SDL."); } + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); return 0; #endif } static int closeInputDevices(lua_State *L) { + printf("closeInputDevices(): BEGIN\n"); #ifndef EMULATE_READER - int i; + int i, ret; for(i=0; i secs * 1000 + usecs/1000) + if (SDL_GetTicks()-ticks > usecs/1000) return luaL_error(L, "Waiting for input failed: timeout\n"); } switch(event.type) { diff --git a/kpdfview.c b/kpdfview.c index 4fa85e940..9c7915d46 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -15,9 +15,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include +#include +#include +#include #include #include -#include +#include #include #include @@ -79,7 +83,7 @@ static int docall(lua_State *L, int narg, int clear) } int main(int argc, char **argv) { - int i, err; + int i; if(argc < 2) { fprintf(stderr, "needs config file as first argument.\n"); @@ -119,6 +123,15 @@ int main(int argc, char **argv) { } } + /* Make popen_noshell & valgrind happy */ + 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); + return 0; } diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index 173e5f7e4..b82412e0f 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -31,7 +31,3 @@ fi # always try to continue cvm killall -cont cvm || /etc/init.d/framework start - -# cleanup hanging process -killall lipc-wait-event - diff --git a/popen-noshell/README b/popen-noshell/README new file mode 100644 index 000000000..7b829d130 --- /dev/null +++ b/popen-noshell/README @@ -0,0 +1,21 @@ +/* + * 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. + +Taken from http://code.google.com/p/popen-noshell/ diff --git a/popen-noshell/popen_noshell.c b/popen-noshell/popen_noshell.c new file mode 100644 index 000000000..ed37d07d5 --- /dev/null +++ b/popen-noshell/popen_noshell.c @@ -0,0 +1,550 @@ +/* + * 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 _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "popen_noshell.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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/popen-noshell/popen_noshell.h b/popen-noshell/popen_noshell.h new file mode 100644 index 000000000..a2eeb3c6e --- /dev/null +++ b/popen-noshell/popen_noshell.h @@ -0,0 +1,69 @@ +/* + * 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 + +#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); + +#endif