├── tests ├── cmd ├── README.md ├── cmd.c ├── test.test ├── test-generator.py └── test.json ├── docker ├── systemf-sleep-infinity ├── systemf-check ├── systemf-build ├── gdb-entitlement.xml ├── Dockerfile ├── systemf-gdbserv ├── docker-compose.yml └── systemf-release-build ├── ChangeLog ├── COPYING ├── autogen.sh ├── NEWS ├── .gitignore ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── AUTHORS ├── .github └── workflows │ └── c-cpp.yml ├── m4 ├── ltversion.m4 ├── ax_prog_bison.m4 ├── ax_check_gnu_make.m4 ├── ltsugar.m4 ├── lt~obsolete.m4 ├── ax_valgrind_check.m4 ├── ax_code_coverage.m4 └── ltoptions.m4 ├── src ├── systemf.h ├── close.c ├── systemf.c ├── pid-chain.c ├── systemf-internal.h ├── lexer.l ├── parser.y ├── derived-parser.h ├── file-sandbox-check.c ├── parser-support.c ├── task.c └── derived-lexer.h ├── config.h.in ├── Makefile.am ├── DEVELOP.md ├── configure.ac ├── LICENSE └── README.md /tests/cmd: -------------------------------------------------------------------------------- 1 | ../cmd -------------------------------------------------------------------------------- /docker/systemf-sleep-infinity: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec sleep infinity 3 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | All changes are tracked in github: 2 | 3 | wwwin-github.cisco.com/ASIG/systemf 4 | -------------------------------------------------------------------------------- /docker/systemf-check: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /systemf 3 | /usr/bin/systemf-build 4 | make check-code-coverage 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2019 by Cisco Systems Inc. 2 | 3 | Currently this is restricted to Cisco use only, but the goal would be 4 | to distribute this to the world. 5 | -------------------------------------------------------------------------------- /docker/systemf-build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /systemf 3 | 4 | # autoreconf -i # Just use the current config file. 5 | ./configure CFLAGS="-g -O0" --enable-code-coverage 6 | make 7 | -------------------------------------------------------------------------------- /docker/gdb-entitlement.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.debugger 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build -t meklund/systemf-gh-action:$(date +%Y-%m-%d) . 2 | # docker push meklund/systemf-gh-action 3 | FROM ubuntu 4 | RUN apt-get update 5 | RUN apt-get update 6 | RUN apt-get install -y git build-essential autotools-dev automake libtool bison flex lcov python3 gdbserver 7 | COPY systemf-build systemf-gdbserv systemf-check systemf-release-build systemf-sleep-infinity /usr/bin/ 8 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # As seen on 3 | # Autotools A Practitioner's Guide to GNU Autoconf, 4 | # Automake, and Libtool by John Calcote 5 | # 6 | # autoreconf runs all the autotool configuration tools in the right order 7 | # and will avoid regenerating files. 8 | # 9 | autoreconf --install --make # install missing files 10 | # automake --add-missing --copy >/dev/null 2>&1 # add install-sh 11 | -------------------------------------------------------------------------------- /docker/systemf-gdbserv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /systemf 3 | /usr/bin/systemf-build 4 | 5 | export SHOULD_NOT_FORK=true 6 | export SHOULD_NOT_CAPTURE_STDOUT_STDERR=true 7 | export LD_LIBRARY_PATH=/systemf/.libs/ 8 | 9 | GDBSERVER_TARGET=${GDBSERVER_TARGET:-../.libs/test-runner} 10 | GDBSERVER_PORT=${GDBSERVER_PORT:-2345} 11 | 12 | cd tests 13 | while true ; do 14 | gdbserver :$GDBSERVER_PORT $GDBSERVER_TARGET 15 | done 16 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Read GNU Coding Standards 6.7 2 | http://www.gnu.org/prep/standards/html_node/NEWS-File.html#NEWS-File 3 | 4 | Example: http://git.savannah.gnu.org/cgit/make.git/tree/NEWS 5 | 6 | YourProject NEWS 7 | History of user-visible changes 8 | 25 Jan 2015 9 | 10 | See the end of this file for copyrights and conditions. 11 | 12 | 13 | Version 1.0.0 14 | 15 | * New features: XXXX 16 | Description of the feature 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.lo 3 | *.la 4 | *.gcno 5 | build-aux/ 6 | *~ 7 | .DS_Store 8 | 9 | *.swp 10 | .libs/ 11 | Makefile 12 | autom4te.cache/ 13 | config.h 14 | config.log 15 | config.status 16 | libtool 17 | myexecutable 18 | .deps/ 19 | .dirstamp 20 | src/derived-parser.output 21 | stamp-h1 22 | test-suite.log 23 | tests/test.log 24 | tests/test.trs 25 | test-runner 26 | test-runner.c 27 | cmd 28 | tests/tmp/ 29 | systemf-coverage* 30 | 31 | systemf-*.tar.gz 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabCompletion": "onlySnippets", 3 | "editor.quickSuggestions": false, 4 | "search.exclude": { 5 | "src/derived-*": true 6 | }, 7 | "files.exclude": { 8 | "src/derived-*": true 9 | }, 10 | "editor.renderLineHighlight": "gutter", 11 | "files.associations": { 12 | "__bit_reference": "c", 13 | "algorithm": "c", 14 | "bitset": "c", 15 | "chrono": "c", 16 | "unordered_map": "c", 17 | "cstring": "c", 18 | "locale": "c" 19 | }, 20 | "lcov.path": "./systemf-0.9-coverage.info" 21 | } 22 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | systemf-test: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile 7 | image: meklund/systemf-gh-action 8 | volumes: 9 | - type: bind 10 | source: ../ 11 | target: /systemf 12 | environment: 13 | - GDBSERVER_PORT=${GDBSERVER_PORT:-2345} 14 | - GDBSERVER_TARGET=${GDBSERVER_TARGET:-../.libs/test-runner} 15 | - LD_LIBRARY_PATH=/systemf/.libs/ 16 | cap_add: 17 | - SYS_PTRACE 18 | security_opt: 19 | - seccomp=unconfined 20 | ports: 21 | - "${GDBSERVER_LOCAL_PORT:-2345}:${GDBSERVER_PORT:-2345}" 22 | command: ["${COMMAND:-/usr/bin/systemf-sleep-infinity}"] 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | systemf code by: 3 | Mark Eklund 4 | James Spadaro 5 | 6 | ------------------------------------------------------------------------------- 7 | Automake Templates are based on https://github.com/ecerulm/autotools-template 8 | The Contents of that AUTHORS file were: 9 | 10 | GNU Coding Standards 6.3: Recording Contributors 11 | http://www.gnu.org/prep/maintain/html_node/Recording-Contributors.html 12 | 13 | First version of all files by Ruben Laguna 14 | 15 | ------------------------------------------------------------------------------- 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | container: meklund/systemf-gh-action:2020-06-22 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: autoreconf 18 | run: autoreconf -i 19 | - name: configure 20 | run: ./configure --enable-code-coverage 21 | - name: make 22 | run: make 23 | - name: make check-code-coverage 24 | run: make check-code-coverage 25 | - name: make print-code-coverage 26 | run: make print-code-coverage 27 | - name: show test failure log 28 | if: ${{ failure() }} 29 | run: cat tests/test.log 30 | -------------------------------------------------------------------------------- /m4/ltversion.m4: -------------------------------------------------------------------------------- 1 | # ltversion.m4 -- version numbers -*- Autoconf -*- 2 | # 3 | # Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. 4 | # Written by Scott James Remnant, 2004 5 | # 6 | # This file is free software; the Free Software Foundation gives 7 | # unlimited permission to copy and/or distribute it, with or without 8 | # modifications, as long as this notice is preserved. 9 | 10 | # @configure_input@ 11 | 12 | # serial 4179 ltversion.m4 13 | # This file is part of GNU Libtool 14 | 15 | m4_define([LT_PACKAGE_VERSION], [2.4.6]) 16 | m4_define([LT_PACKAGE_REVISION], [2.4.6]) 17 | 18 | AC_DEFUN([LTVERSION_VERSION], 19 | [macro_version='2.4.6' 20 | macro_revision='2.4.6' 21 | _LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) 22 | _LT_DECL(, macro_revision, 0) 23 | ]) 24 | -------------------------------------------------------------------------------- /src/systemf.h: -------------------------------------------------------------------------------- 1 | #ifndef __systemf_h__ 2 | #define __systemf_h__ 3 | #include 4 | 5 | extern int systemf1(const char *fmt, ...); 6 | 7 | /* 8 | * Debug Flags used with the global systemf1_debug_set() and systemf1_debug_get() 9 | * Flags starting with SYSTEMF1_DBG_DBG_ only work if systemf is configured with --enhanced-debug 10 | */ 11 | enum { 12 | SYSTEMF1_DBG_ERRORS = 0x01, // Print a debug any time a nonzero value would be returned. 13 | SYSTEMF1_DBG_EXEC = 0x02, // Print the command and arguments being launched. 14 | SYSTEMF1_DBG_DBG_LEX = 0x04, 15 | SYSTEMF1_DBG_DBG_PARSER = 0x08, 16 | }; 17 | extern int systemf1_debug_set(int flags); 18 | extern int systemf1_debug_get(); 19 | extern FILE *systemf1_debug_file_set(FILE *file); 20 | extern FILE *systemf1_debug_file_get(); 21 | 22 | #endif /* __systemf_h__ */ 23 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Running Tests 2 | 3 | To run tests, either: 4 | 1. `make check` to run the automated framework 5 | 2. `python3 tests/test.test` to do a manual run 6 | 7 | ## JSON File Spec 8 | 9 | 10 | test.json is a list of tests in JSON format that will run. 11 | 12 | A test is defined as a JSON object looking like this: 13 | ``` 14 | { 15 | "description": "*test description*", 16 | "command": [ "*binary*", "*arg1*", "*arg2*" ], 17 | "stdout": ["*operator*", "*expected stdout including newlines*"], 18 | "stderr": null, 19 | "return_code": null 20 | } 21 | ``` 22 | 23 | Note that "stdout", "stderr", and "return_code" can all be null if it doesn't matter 24 | what they are. Otherwise, they should be an array of "operator", "expected 25 | value". 26 | 27 | Operator can be: "==", "!=", ">", "<", "contains" 28 | 29 | These objects are defined in a JSON list. 30 | 31 | -------------------------------------------------------------------------------- /src/close.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "systemf-internal.h" 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * Closes all other files but stdin, stdout, and stderr. 9 | * 10 | * All documentation advises that all open files should be closed 11 | * after a fork. But there is no consistent advice on how to close 12 | * these files. It is expected that this implementation will become 13 | * several different versions detecting on what operating system 14 | * ./configure detects. 15 | * 16 | * This does not close fd 0, 1, and 2 (stdin, stdout, and stderr) 17 | */ 18 | void _sf1_close_upper_fd() { 19 | struct rlimit rlim; 20 | int prev_errno; 21 | 22 | // if getrlimit ever fails, we need to figure out why and then properly handle it. 23 | assert(!getrlimit(RLIMIT_NOFILE, &rlim)); 24 | 25 | prev_errno = errno; 26 | for (int i = 3; i < rlim.rlim_cur; i++) { 27 | close(i); 28 | } 29 | errno = prev_errno; 30 | } 31 | -------------------------------------------------------------------------------- /docker/systemf-release-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fail() { 4 | echo ERROR: "$*" >&1 5 | exit 1 6 | } 7 | 8 | cd /systemf 9 | 10 | test -z "$(git status -s | grep -v src/derived-)" || fail Checked out or untracked files found in \"git status -s\". 11 | version=$(cat configure.ac | tr '[]' ' ' | awk '/AC_INIT/ {print $4}') 12 | test -n "$version" || fail Empty version detected. 13 | git tag | grep "^V$version\$" >/dev/null && fail "$version" already exists in git. If this is a rebuild then you may \"git tag -d V$version\". 14 | 15 | set -e -x 16 | rm -f src/derived-* 17 | autoreconf -if 18 | ./configure 19 | make clean 20 | make 21 | make check 22 | set +e +x 23 | 24 | test -z "$(git status -s)" || fail Build created new code. Audit, commit if good, and rerun release. 25 | 26 | set -e -x 27 | make dist 28 | cd .. 29 | tar xzf systemf/systemf-$version.tar.gz 30 | cd systemf-$version 31 | ./configure 32 | make check 33 | make install 34 | set +e +x 35 | 36 | echo '#######' 37 | echo SUCCESS 38 | echo '#######' 39 | 40 | echo Please do this when ready: git tag -a V$version -m "$version" 41 | 42 | -------------------------------------------------------------------------------- /src/systemf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "derived-parser.h" 6 | #include "derived-lexer.h" 7 | #include "systemf-internal.h" 8 | 9 | int _sf1_yyerror(_SF1_YYLTYPE *locp, yyscan_t scanner, _sf1_parse_args *result, const char *msg) { 10 | fprintf(stderr, "ERROR: %d:%d:%s\n", locp->first_line, locp->first_column, msg); 11 | return 1; 12 | } 13 | 14 | int systemf1(const char *fmt, ...) 15 | { 16 | // extern int _sf1_yydebug; _sf1_yydebug = 1; // for debugging issues 17 | va_list argp; 18 | yyscan_t scanner; 19 | _sf1_parse_args *result = calloc(1, sizeof(_sf1_parse_args)); 20 | 21 | va_start(argp, fmt); 22 | result->argpp = &argp; 23 | 24 | if (_sf1_yylex_init(&scanner)) { 25 | fprintf(stderr, "systemf: Unexpected failure in yylex_init()."); 26 | return -1; 27 | } 28 | YY_BUFFER_STATE buf = _sf1_yy_scan_string(fmt, scanner); 29 | if (_sf1_yyparse(scanner, result)) { 30 | free(result); 31 | return -1; 32 | } 33 | va_end(argp); 34 | _sf1_yy_delete_buffer(buf, scanner); 35 | _sf1_yylex_destroy(scanner); 36 | 37 | int ret = _sf1_tasks_run(result->tasks); 38 | _sf1_task_free(result->tasks); 39 | free(result); 40 | 41 | return (ret); 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | // https://code.visualstudio.com/docs/cpp/launch-json-reference 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "(lldb) test-runner", 10 | "type": "cppdbg", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/.libs/test-runner", 13 | "args": ["30"], 14 | "environment": [ 15 | {"name": "DYLD_LIBRARY_PATH", "value": "${workspaceFolder}/.libs:${env:DYLD_LIBRARY_PATH}"}], 16 | "stopAtEntry": false, 17 | "cwd": "${workspaceFolder}/tests/", 18 | "externalConsole": false, 19 | "MIMode": "lldb" 20 | }, 21 | { 22 | "name": "Docker gdb", 23 | "type": "cppdbg", 24 | "request": "launch", 25 | "program": ".libs/test-runner", 26 | "miDebuggerServerAddress": "localhost:2345", 27 | "debugServerArgs": "30", 28 | "args": ["30"], 29 | "stopAtEntry": false, 30 | "cwd": "${workspaceRoot}", 31 | "environment": [], 32 | "externalConsole": false, 33 | "linux": { 34 | "MIMode": "gdb" 35 | }, 36 | "osx": { 37 | "MIMode": "gdb" 38 | }, 39 | "windows": { 40 | "MIMode": "gdb" 41 | } 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "Make check", 7 | "command": "/usr/bin/make", 8 | "args": [ 9 | "check" 10 | ], 11 | "options": { 12 | "cwd": "${workspaceFolder}", 13 | "environment": [ 14 | { 15 | "name": "CFLAGS", 16 | "value": "-gO0" 17 | } 18 | ] 19 | }, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "problemMatcher": [ 25 | "$gcc" 26 | ] 27 | }, 28 | { 29 | "type": "shell", 30 | "label": "Docker check", 31 | "command": "/usr/local/bin/docker-compose", 32 | "args": [ 33 | "up", 34 | "--build", 35 | "--force-recreate" 36 | ], 37 | "options": { 38 | "cwd": "${workspaceFolder}/docker", 39 | "env": { 40 | "COMMAND": "/usr/bin/systemf-check" 41 | } 42 | }, 43 | "group": { 44 | "kind": "build", 45 | "isDefault": true 46 | }, 47 | "problemMatcher": [ 48 | "$gcc" 49 | ] 50 | }, 51 | { 52 | "type": "shell", 53 | "label": "Docker gdbserver", 54 | "command": "/usr/local/bin/docker-compose", 55 | "args": [ 56 | "up", 57 | "--build", 58 | "--force-recreate" 59 | ], 60 | "options": { 61 | "cwd": "${workspaceFolder}/docker", 62 | "env": { 63 | "COMMAND": "/usr/bin/systemf-gdbserv" 64 | } 65 | }, 66 | "group": { 67 | "kind": "build", 68 | "isDefault": true 69 | }, 70 | "problemMatcher": [ 71 | "$gcc" 72 | ] 73 | } 74 | ] 75 | } -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Define to 1 if you have the header file. */ 4 | #undef HAVE_DLFCN_H 5 | 6 | /* Define to 1 if you have the header file. */ 7 | #undef HAVE_INTTYPES_H 8 | 9 | /* Define to 1 if you have the header file. */ 10 | #undef HAVE_MEMORY_H 11 | 12 | /* Define to 1 if you have the header file. */ 13 | #undef HAVE_STDINT_H 14 | 15 | /* Define to 1 if you have the header file. */ 16 | #undef HAVE_STDLIB_H 17 | 18 | /* Define to 1 if you have the header file. */ 19 | #undef HAVE_STRINGS_H 20 | 21 | /* Define to 1 if you have the header file. */ 22 | #undef HAVE_STRING_H 23 | 24 | /* Define to 1 if you have the header file. */ 25 | #undef HAVE_SYS_STAT_H 26 | 27 | /* Define to 1 if you have the header file. */ 28 | #undef HAVE_SYS_TYPES_H 29 | 30 | /* Define to 1 if you have the header file. */ 31 | #undef HAVE_UNISTD_H 32 | 33 | /* Define to the sub-directory where libtool stores uninstalled libraries. */ 34 | #undef LT_OBJDIR 35 | 36 | /* myfeature is enabled */ 37 | #undef MYFEATURE 38 | 39 | /* Define to 1 if assertions should be disabled. */ 40 | #undef NDEBUG 41 | 42 | /* Define to 1 if your C compiler doesn't accept -c and -o together. */ 43 | #undef NO_MINUS_C_MINUS_O 44 | 45 | /* Name of package */ 46 | #undef PACKAGE 47 | 48 | /* Define to the address where bug reports for this package should be sent. */ 49 | #undef PACKAGE_BUGREPORT 50 | 51 | /* Define to the full name of this package. */ 52 | #undef PACKAGE_NAME 53 | 54 | /* Define to the full name and version of this package. */ 55 | #undef PACKAGE_STRING 56 | 57 | /* Define to the one symbol short name of this package. */ 58 | #undef PACKAGE_TARNAME 59 | 60 | /* Define to the home page for this package. */ 61 | #undef PACKAGE_URL 62 | 63 | /* Define to the version of this package. */ 64 | #undef PACKAGE_VERSION 65 | 66 | /* Define to 1 if you have the ANSI C header files. */ 67 | #undef STDC_HEADERS 68 | 69 | /* Version number of package */ 70 | #undef VERSION 71 | -------------------------------------------------------------------------------- /src/pid-chain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "systemf-internal.h" 5 | 6 | /* 7 | * _sf1_pid_chain_wait - Waits for all processes in a chain of pids to finish. 8 | * 9 | * stat_loc - The stat of the last pid in the pid_chain. 10 | * 11 | * returns - The pid of the last pid in the chain or 0 on error. 12 | */ 13 | pid_t _sf1_pid_chain_waitpids(_sf1_pid_chain_t *pid_chain, int *stat_loc, int options) { 14 | // Currently this is always used in a run-to-completion, so don't support all waitpid options 15 | assert(options == 0); 16 | assert(stat_loc != NULL); 17 | pid_t pid = 0; 18 | 19 | for (int i = 0; i < pid_chain->size; i++) { 20 | pid = waitpid(pid_chain->pids[i], stat_loc, 0); 21 | // This should never fail. 22 | assert(pid == pid_chain->pids[i]); 23 | } 24 | return pid; 25 | } 26 | 27 | size_t _sf1_pid_chain_size(int capacity) { 28 | return sizeof(_sf1_pid_chain_t) + sizeof(pid_t) * capacity; 29 | } 30 | 31 | /* 32 | * Adds a pid to the pid_chain. If pid_chain is NULL, it allocates. If needs to be increased, 33 | * it reallocates. On failure, errno is set and NULL is returned. 34 | */ 35 | _sf1_pid_chain_t *_sf1_pid_chain_add(_sf1_pid_chain_t *pid_chain, pid_t pid) { 36 | const int cap_steps = 4; 37 | 38 | if (!pid_chain) { 39 | pid_chain = malloc(_sf1_pid_chain_size(cap_steps)); 40 | if (!pid_chain) { 41 | return NULL; 42 | } 43 | pid_chain->capacity = cap_steps; 44 | pid_chain->size = 0; 45 | } 46 | 47 | if (pid_chain->capacity == pid_chain->size) { 48 | _sf1_pid_chain_t *save = pid_chain; 49 | 50 | pid_chain->capacity += cap_steps; 51 | pid_chain = realloc(pid_chain, _sf1_pid_chain_size(pid_chain->capacity)); 52 | if (!pid_chain) { 53 | free(save); 54 | return NULL; 55 | } 56 | } 57 | 58 | pid_chain->pids[pid_chain->size] = pid; 59 | pid_chain->size += 1; 60 | return pid_chain; 61 | } 62 | 63 | /* 64 | * Removes memory for the pid_chain. Always returns NULL. 65 | */ 66 | void _sf1_pid_chain_free(_sf1_pid_chain_t *pid_chain) { 67 | free(pid_chain); 68 | } 69 | 70 | /* 71 | * Resets the pid_chain to zero length. 72 | */ 73 | void _sf1_pid_chain_clear(_sf1_pid_chain_t *pid_chain) { 74 | pid_chain->size = 0; 75 | } 76 | -------------------------------------------------------------------------------- /tests/cmd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * cmd - Used by the test harness to have a consistent 3 | * test command with no reliance on any external files. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static void cat(void) { 12 | char buf[257]; 13 | while (1) { 14 | int count = read(0, buf, 256); 15 | if (count < 1) { 16 | break; 17 | } 18 | buf[count] = 0; 19 | printf("%s", buf); 20 | } 21 | } 22 | 23 | int main(int argc, char *argv[]) { 24 | int retval = 0; 25 | 26 | if (argc == 1) { 27 | printf("%s", 28 | "stdout: echo '1' to the stdout with no linefeed.\n" 29 | "stderr: echo '2' to the stderr with no linefeed.\n" 30 | "cat: cat the stdin to the stdout (does not take arguments)\n" 31 | "{}: wrap the stdin with {}\n" 32 | "incr: read the first integer from the stdin, add 1, and print to stdout.\n" 33 | "comma: read the rest of the arguments and print to the stdout comma separated.\n" 34 | "true: set the return value to 0 (the default).\n" 35 | "false: set the return value to 1.\n" 36 | "count: countinuously count from 1 to infinity to stdout with a newline.\n" 37 | "return: set the return value to the next argument\n"); 38 | return retval; 39 | } 40 | 41 | for (int argi = 1; argi < argc; argi++) { 42 | if (!strcmp("stdout", argv[argi])) { 43 | printf("1"); 44 | } else if (!strcmp("stderr", argv[argi])) { 45 | fprintf(stderr, "2"); 46 | } else if (!strcmp("cat", argv[argi])) { 47 | cat(); 48 | } else if (!strcmp("{}", argv[argi])) { 49 | printf("{"); 50 | cat(); 51 | printf("}"); 52 | } else if (!strcmp("incr", argv[argi])) { 53 | int i = 0; 54 | if (scanf("%d", &i)) { 55 | i = i + 1; 56 | } 57 | printf("%d", i); 58 | } else if (!strcmp("comma", argv[argi])) { 59 | char *delim=""; 60 | for (argi++; argi < argc; argi++) { 61 | printf("%s%s", delim, argv[argi]); 62 | delim = ","; 63 | } 64 | } else if (!strcmp("true", argv[argi])) { 65 | retval = 0; 66 | } else if (!strcmp("false", argv[argi])) { 67 | retval = 1; 68 | } else if (!strcmp("count", argv[argi])) { 69 | for (int i=1; 1; i += 1) { 70 | printf("%d\n", i); 71 | } 72 | } else if (!strcmp("return", argv[argi])) { 73 | argi++; 74 | if (argi < argc) { 75 | retval = atoi(argv[argi]); 76 | } 77 | } else { 78 | printf("\nInvalid option %s\n", argv[argi]); 79 | } 80 | } 81 | return retval; 82 | } 83 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = # CFLAGS applicable to all executables (products) 2 | AM_CPPFLAGS = -I$(top_srcdir)/src # so that tests also find header files 3 | AM_YFLAGS = -d 4 | EXTRA_DIST := README.md \ 5 | tests/README.md \ 6 | tests/test.test \ 7 | tests/test.json \ 8 | tests/test-generator.py \ 9 | tests/cmd \ 10 | src/parser.y \ 11 | src/lexer.l 12 | 13 | lib_LTLIBRARIES = libsystemf.la 14 | libsystemf_la_SOURCES := \ 15 | src/close.c \ 16 | src/derived-lexer.c \ 17 | src/derived-lexer.h \ 18 | src/derived-parser.c \ 19 | src/derived-parser.h \ 20 | src/file-sandbox-check.c \ 21 | src/parser-support.c \ 22 | src/pid-chain.c \ 23 | src/systemf.c \ 24 | src/task.c \ 25 | src/systemf-internal.h 26 | 27 | include_HEADERS := src/systemf.h 28 | libsystemf_la_LDFLAGS = -avoid-version -shared $(CODE_COVERAGE_LDFLAGS) 29 | libsystemf_la_CFLAGS := -D_FORTIFY_SOURCE=2 -Wall -Werror $(CODE_COVERAGE_CFLAGS) 30 | 31 | src/derived-lexer.c src/derived-lexer.h: src/lexer.l src/derived-parser.c 32 | $(LEX) --version | grep flex || (echo Flex is required ; false) 33 | flex src/lexer.l 34 | 35 | src/derived-parser.c src/derived-parser.h: src/parser.y 36 | $(BISON) --version | grep 'bison.* 3' || (echo Bison 3 is required ; false) 37 | bison -v src/parser.y 38 | 39 | # Anything ending in .test should be a runnable script that produces TAP output 40 | # Example Output, with 2 tests: 41 | # 1..2 42 | # ok 1 - message 43 | # not ok 2 - message 44 | # See http://www.gnu.org/software/automake/manual/html_node/Use-TAP-with-the-Automake-test-harness.html 45 | # 46 | # Run tests with 'make check' 47 | 48 | check_PROGRAMS = test-runner cmd 49 | 50 | test_runner_SOURCES = tests/test-runner.c 51 | test_runner_LDADD = libsystemf.la 52 | 53 | test_runner_CFLAGS = $(AM_CFLAGS) 54 | 55 | cmd_SOURCES = tests/cmd.c 56 | cmd_CFLAGS = $(AM_CFLAGS) 57 | cmd_LDFLAGS = $(CODE_COVERAGE_LDFLAGS) 58 | 59 | tests/test-runner.c: tests/test.json tests/test-generator.py 60 | python3 ./tests/test-generator.py 61 | 62 | TESTS = tests/test.test 63 | TESTS_ENVIRONMENT = 64 | TEST_EXTENSIONS = .test 65 | 66 | TEST_LOG_COMPILE = $(PYTHON) 67 | TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh 68 | 69 | 70 | @VALGRIND_CHECK_RULES@ 71 | @CODE_COVERAGE_RULES@ 72 | 73 | # See all code_coverage_* options in the Makefile or m4/ax_code_coverage.m4 74 | CODE_COVERAGE_OUTPUT_FILE = $(PACKAGE_NAME)-coverage.info 75 | CODE_COVERAGE_OUTPUT_DIRECTORY = $(PACKAGE_NAME)-coverage 76 | CODE_COVERAGE_IGNORE_PATTERN = $(abs_top_builddir)/tests/* 77 | CODE_COVERAGE_IGNORE_PATTERN += $(abs_top_builddir)/src/derived* 78 | CODE_COVERAGE_IGNORE_PATTERN += /usr/include/* 79 | CODE_COVERAGE_IGNORE_PATTERN += /usr/include/*/bits/* 80 | 81 | # Hack where there is much likely something better: 82 | print-code-coverage: 83 | @ grep -A3 Lines systemf-coverage/index.html | sed -e's/.*".//' -e 's/<.*//' | fmt -w80 | sed -e's/ /_/' -e 's, ,/,' -e 's/ / = /' -e 's/_/ /' 84 | -------------------------------------------------------------------------------- /m4/ax_prog_bison.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_prog_bison.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_PROG_BISON(ACTION-IF-TRUE,ACTION-IF-FALSE) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether bison is the parser generator. Run ACTION-IF-TRUE if 12 | # successful, ACTION-IF-FALSE otherwise 13 | # 14 | # LICENSE 15 | # 16 | # Copyright (c) 2009 Francesco Salvestrini 17 | # Copyright (c) 2010 Diego Elio Petteno` 18 | # 19 | # This program is free software; you can redistribute it and/or modify it 20 | # under the terms of the GNU General Public License as published by the 21 | # Free Software Foundation; either version 2 of the License, or (at your 22 | # option) any later version. 23 | # 24 | # This program is distributed in the hope that it will be useful, but 25 | # WITHOUT ANY WARRANTY; without even the implied warranty of 26 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 27 | # Public License for more details. 28 | # 29 | # You should have received a copy of the GNU General Public License along 30 | # with this program. If not, see . 31 | # 32 | # As a special exception, the respective Autoconf Macro's copyright owner 33 | # gives unlimited permission to copy, distribute and modify the configure 34 | # scripts that are the output of Autoconf when processing the Macro. You 35 | # need not follow the terms of the GNU General Public License when using 36 | # or distributing such scripts, even though portions of the text of the 37 | # Macro appear in them. The GNU General Public License (GPL) does govern 38 | # all other use of the material that constitutes the Autoconf Macro. 39 | # 40 | # This special exception to the GPL applies to versions of the Autoconf 41 | # Macro released by the Autoconf Archive. When you make and distribute a 42 | # modified version of the Autoconf Macro, you may extend this special 43 | # exception to the GPL to apply to your modified version as well. 44 | 45 | #serial 10 46 | 47 | AC_DEFUN([AX_PROG_BISON], [ 48 | AC_REQUIRE([AC_PROG_YACC]) 49 | AC_REQUIRE([AC_PROG_EGREP]) 50 | 51 | AC_CACHE_CHECK([if bison is the parser generator],[ax_cv_prog_bison],[ 52 | AS_IF([$YACC --version 2>/dev/null | $EGREP -q '^bison '], 53 | [ax_cv_prog_bison=yes], [ax_cv_prog_bison=no]) 54 | ]) 55 | AS_IF([test "$ax_cv_prog_bison" = "yes"], [ 56 | dnl replace the yacc-compatible compiler with the real bison, as 57 | dnl otherwise autoconf limits us to the POSIX yacc. 58 | dnl We also change the generated filename to the old one, so that 59 | dnl automake's ylwrap can deal with it. 60 | YACC="${YACC% -y} -o y.tab.c" 61 | ] m4_ifnblank([$1], [[$1]]), 62 | m4_ifnblank([$2], [[$2]]) 63 | ) 64 | ]) 65 | -------------------------------------------------------------------------------- /src/systemf-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef __systemf_internal_h__ 2 | #define __systemf_internal_h__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum { 9 | _SF1_STDIN, 10 | _SF1_STDOUT, 11 | _SF1_SHARE, // If in use FD or out. If out, use FD of in. 12 | _SF1_STDERR, 13 | _SF1_PIPE, 14 | _SF1_FILE, 15 | } _sf1_stream; 16 | 17 | typedef enum { 18 | _SF1_RUN_ALWAYS = 0, 19 | _SF1_RUN_IF_PREV_FAILED = 1, 20 | _SF1_RUN_IF_PREV_SUCCEEDED = 2, 21 | } _sf1_run_if; 22 | 23 | typedef struct _sf1_task_arg_ { 24 | struct _sf1_task_arg_ *next; 25 | int is_glob; 26 | char *text; 27 | char *trusted_path; 28 | glob_t glob; 29 | } _sf1_task_arg; 30 | 31 | typedef struct _sf1_redirect_ { 32 | struct _sf1_redirect_ *next; 33 | _sf1_stream stream; // May only be STDIN, STDOUT, or STDERR 34 | _sf1_stream target; 35 | char *text; 36 | char *trusted_path; 37 | int append; 38 | } _sf1_redirect; 39 | 40 | typedef struct _sf1_task_ { 41 | char **argv; 42 | _sf1_run_if run_if; 43 | _sf1_redirect *redirects; 44 | _sf1_task_arg *args; 45 | struct _sf1_task_ *next; 46 | } _sf1_task; 47 | 48 | typedef struct _sf1_task_files_ { 49 | int in; 50 | int out; 51 | int err; 52 | int out_rd_pipe; 53 | } _sf1_task_files; 54 | 55 | typedef struct { 56 | _sf1_task *tasks; 57 | va_list *argpp; 58 | } _sf1_parse_args ; 59 | 60 | typedef enum { 61 | SYL_ESCAPE_GLOB=1, 62 | SYL_IS_GLOB=2, 63 | SYL_IS_FILE=4, 64 | SYL_IS_TRUSTED=8, 65 | } _sf1_syl_flags; 66 | 67 | typedef struct _sf1_syllable_ { 68 | struct _sf1_syllable_ *next; 69 | struct _sf1_syllable_ *next_word; 70 | _sf1_syl_flags flags; 71 | char text[]; 72 | } _sf1_syllable; 73 | 74 | typedef struct { 75 | int size; 76 | int capacity; 77 | pid_t pids[]; 78 | } _sf1_pid_chain_t; 79 | 80 | #ifndef YY_TYPEDEF_YY_SCANNER_T 81 | #define YY_TYPEDEF_YY_SCANNER_T 82 | typedef void* yyscan_t; 83 | #endif 84 | 85 | extern _sf1_redirect *_sf1_merge_redirects(_sf1_redirect *left, _sf1_redirect *right); 86 | extern _sf1_redirect *_sf1_create_redirect(_sf1_stream stream, _sf1_stream target, int append, _sf1_syllable *file_syllables); 87 | extern _sf1_task *_sf1_create_cmd(_sf1_syllable *syllables, _sf1_redirect *redirects); 88 | extern void _sf1_create_redirect_pipe (_sf1_task *left, _sf1_task *right); 89 | 90 | extern int _sf1_file_sandbox_check(char *trusted_path, char *path); 91 | 92 | extern pid_t _sf1_pid_chain_waitpids(_sf1_pid_chain_t *pid_chain, int *stat_loc, int options); 93 | extern _sf1_pid_chain_t *_sf1_pid_chain_add(_sf1_pid_chain_t *pid_chain, pid_t pid); 94 | extern void _sf1_pid_chain_free(_sf1_pid_chain_t *pid_chain); 95 | extern void _sf1_pid_chain_clear(_sf1_pid_chain_t *pid_chain); 96 | 97 | extern _sf1_task *_sf1_task_create(); 98 | extern int _sf1_tasks_run(_sf1_task *task); 99 | extern _sf1_task_arg *_sf1_task_add_arg(_sf1_task *task, char *text, char *trusted_path, int is_glob); 100 | extern void _sf1_task_add_redirects(_sf1_task *task, _sf1_redirect *redirect); 101 | extern void _sf1_task_free(_sf1_task *task); 102 | extern char *_sf1_stream_name(_sf1_stream); 103 | extern void _sf1_close_upper_fd(void); 104 | 105 | #endif /* __systemf_internal_h__ */ 106 | -------------------------------------------------------------------------------- /src/lexer.l: -------------------------------------------------------------------------------- 1 | %option outfile="src/derived-lexer.c" header-file="src/derived-lexer.h" 2 | %option reentrant 3 | %option bison-bridge 4 | %option noyywrap nounput noinput 5 | %option prefix="_sf1_yy" 6 | 7 | %{ 8 | #include 9 | #include "derived-parser.h" 10 | 11 | static void yy_user_action(_SF1_YYLTYPE *yylloc, const char *my_yytext) 12 | { 13 | yylloc->first_line = yylloc->last_line; 14 | yylloc->first_column = yylloc->last_column; 15 | for(int i = 0; my_yytext[i] != '\0'; i++) { 16 | if(my_yytext[i] == '\n') { 17 | yylloc->last_line++; 18 | yylloc->last_column = 0; 19 | } else { 20 | yylloc->last_column++; 21 | } 22 | } 23 | } 24 | 25 | #define YY_USER_ACTION yy_user_action(yylloc, yytext); 26 | 27 | static _sf1_syllable *syl (_sf1_parse_args *results, char *text, int flags) { 28 | size_t bufsize = strlen(text) + 1; 29 | _sf1_syllable *syl = malloc(sizeof(*syl) + bufsize); 30 | syl->flags = flags; 31 | memcpy(syl->text, text, bufsize); 32 | syl->next = NULL; 33 | syl->next_word = NULL; 34 | return syl; 35 | } 36 | static _sf1_syllable *syl_s (_sf1_parse_args *results) { 37 | return syl(results, va_arg(*results->argpp, char *), SYL_ESCAPE_GLOB); 38 | } 39 | static _sf1_syllable *syl_file (_sf1_parse_args *results) { 40 | return syl(results, va_arg(*results->argpp, char *), SYL_IS_FILE|SYL_ESCAPE_GLOB); 41 | } 42 | static _sf1_syllable *syl_glob (_sf1_parse_args *results) { 43 | return syl(results, va_arg(*results->argpp, char *), SYL_IS_FILE|SYL_IS_GLOB); 44 | } 45 | static _sf1_syllable *syl_trusted_file (_sf1_parse_args *results) { 46 | return syl(results, va_arg(*results->argpp, char *), SYL_IS_FILE|SYL_IS_TRUSTED|SYL_ESCAPE_GLOB); 47 | } 48 | static _sf1_syllable *syl_d (_sf1_parse_args *results) { 49 | char text[20]; 50 | int val = va_arg(*results->argpp, int); 51 | snprintf(text, sizeof(text), "%d", val); 52 | return syl(results, text, 0); 53 | } 54 | 55 | %} 56 | 57 | 58 | %% 59 | 60 | [[:alnum:]/_.-]+ { yylval->SYLLABLE = syl(results, yytext, SYL_IS_TRUSTED); return SYLLABLE; } 61 | \[[[:alnum:]/_.-]+\] { yylval->SYLLABLE = syl(results, yytext, SYL_IS_FILE|SYL_IS_GLOB|SYL_IS_TRUSTED); return SYLLABLE; } 62 | [\*\?] { yylval->SYLLABLE = syl(results, yytext, SYL_IS_FILE|SYL_IS_GLOB|SYL_IS_TRUSTED); return SYLLABLE; } 63 | %s { yylval->SYLLABLE = syl_s(results); return SYLLABLE; } 64 | %p { yylval->SYLLABLE = syl_file(results); return SYLLABLE; } 65 | %!p { yylval->SYLLABLE = syl_trusted_file(results); return SYLLABLE; } 66 | %\*p { yylval->SYLLABLE = syl_glob(results); return SYLLABLE; } 67 | %d { yylval->SYLLABLE = syl_d(results); return SYLLABLE; } 68 | [ \t]+ { return SPACE;} 69 | [ \t]*\<[ \t]* { return LESSER; } 70 | [ \t]*2>&1 { return TWO_GREATER_AND_ONE; } 71 | [ \t]*2>[ \t]* { return TWO_GREATER; } 72 | [ \t]*&>[ \t]* { return AND_GREATER; } 73 | [ \t]*>[ \t]* { return GREATER; } 74 | [ \t]*>&2 { return GREATER_AND_TWO; } 75 | [ \t]*2>>[ \t]* { return TWO_GREATER_GREATER; } 76 | [ \t]*&>>[ \t]* { return AND_GREATER_GREATER; } 77 | [ \t]*>>[ \t]* { return GREATER_GREATER; } 78 | [ \t]*&&[ \t]* { return AND_AND; } 79 | [ \t]*\|[ \t]* { return OR; } 80 | [ \t]*\|\|[ \t]* { return OR_OR; } 81 | [ \t]*;[ \t]* { return SEMICOLON; } 82 | .|\n { return yytext[0]; } 83 | 84 | %% 85 | 86 | -------------------------------------------------------------------------------- /src/parser.y: -------------------------------------------------------------------------------- 1 | %output "src/derived-parser.c" 2 | %defines "src/derived-parser.h" 3 | %define api.pure full 4 | %define api.prefix {_sf1_yy} 5 | %define api.value.type union 6 | %locations 7 | %lex-param { yyscan_t scanner } 8 | %lex-param { _sf1_parse_args *results } 9 | %parse-param { yyscan_t scanner } 10 | %parse-param { _sf1_parse_args *results } 11 | 12 | 13 | %code requires { 14 | #if 1 // This compiles in debug code enabled by _sf1_yydebug; 15 | #undef SYSTEMF1_YYDEBUG 16 | #define SYSTEMF1_YYDEBUG 1 17 | extern int _sf1_yydebug; 18 | #endif 19 | 20 | #include "systemf-internal.h" 21 | 22 | } 23 | 24 | %code provides 25 | { 26 | #define YYSTYPE _SF1_YYSTYPE 27 | #define YYLTYPE _SF1_YYLTYPE 28 | #define YY_DECL int _sf1_yylex(YYSTYPE * yylval_param , YYLTYPE *yylloc, yyscan_t yyscanner, _sf1_parse_args *results) 29 | extern YY_DECL; 30 | 31 | int _sf1_yyerror(_SF1_YYLTYPE *locp, yyscan_t scanner, _sf1_parse_args *results, const char *msg); 32 | } 33 | 34 | %code { 35 | /* 36 | * Put the parser code in a file that IDE's better understand, but 37 | * don't expose the statics to the world. 38 | */ 39 | #include "systemf-internal.h" 40 | } 41 | 42 | %token <_sf1_syllable *> SYLLABLE 43 | %token SPACE QUOTE LESSER TWO_GREATER_AND_ONE TWO_GREATER AND_GREATER GREATER TWO_GREATER_GREATER AND_GREATER_GREATER 44 | %token GREATER_GREATER GREATER_AND_TWO AND_AND OR_OR SEMICOLON OR 45 | %type <_sf1_syllable *> syllables words 46 | %type <_sf1_task *> cmd cmds 47 | %type <_sf1_redirect *> redirect redirects 48 | 49 | %% 50 | 51 | cmds: 52 | cmd { results->tasks = $1; } 53 | | cmd SEMICOLON cmds { results->tasks = $1; $1->next = $3; $3->run_if = _SF1_RUN_ALWAYS; } 54 | | cmd OR_OR cmds { results->tasks = $1; $1->next = $3; $3->run_if = _SF1_RUN_IF_PREV_FAILED; } 55 | | cmd AND_AND cmds { results->tasks = $1; $1->next = $3; $3->run_if = _SF1_RUN_IF_PREV_SUCCEEDED; } 56 | | cmd OR cmds { results->tasks = $1; $1->next = $3; $3->run_if = _SF1_RUN_ALWAYS; 57 | _sf1_create_redirect_pipe($1, $3); } 58 | 59 | cmd: 60 | words redirects { $$ = _sf1_create_cmd($1, $2); } 61 | 62 | redirects: 63 | redirect redirects { $$ = _sf1_merge_redirects($1, $2); } 64 | | redirect { $$ = $1; } 65 | | /* empty */ { $$ = NULL; } 66 | 67 | redirect: 68 | LESSER syllables { $$ = _sf1_create_redirect(_SF1_STDIN, _SF1_FILE, 0, $2); } 69 | | GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDOUT, _SF1_FILE, 0, $2); } 70 | | GREATER_GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDOUT, _SF1_FILE, 1, $2); } 71 | | GREATER_AND_TWO { $$ = _sf1_create_redirect(_SF1_STDOUT, _SF1_SHARE, 0, NULL); } 72 | | TWO_GREATER_AND_ONE { $$ = _sf1_create_redirect(_SF1_STDERR, _SF1_SHARE, 0, NULL); } 73 | | TWO_GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDERR, _SF1_FILE, 0, $2); } 74 | | TWO_GREATER_GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDERR, _SF1_FILE, 1, $2); } 75 | | AND_GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDERR, _SF1_SHARE, 0, NULL); 76 | $$->next = _sf1_create_redirect(_SF1_STDOUT, _SF1_FILE, 0, $2); } 77 | | AND_GREATER_GREATER syllables { $$ = _sf1_create_redirect(_SF1_STDERR, _SF1_SHARE, 1, NULL); 78 | $$->next = _sf1_create_redirect(_SF1_STDOUT, _SF1_FILE, 1, $2); } 79 | 80 | words: 81 | syllables { $$ = $1; } 82 | | syllables SPACE words { $1->next_word = $3; $$ = $1; } 83 | | error { YYABORT; } 84 | 85 | syllables: 86 | SYLLABLE { $$ = $1; } 87 | | SYLLABLE syllables { $1->next = $2; $$ = $1; } 88 | 89 | 90 | %% 91 | 92 | -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | # Developer notes 2 | 3 | This uses gnu AutoTools to generate a configure and makefile. 4 | To remove that dependency and allow building directly from a 5 | git pull, the files generated from these are checked into this 6 | repository. 7 | 8 | ## OSX installation from Scratch 9 | ``` 10 | brew update 11 | brew install autoconf automake 12 | autoreconf -i 13 | ./configure && make 14 | make checks 15 | ``` 16 | 17 | **Note:** There is a configure option of --enable-code-coverage, 18 | but currently the `make check` complains, `ld: library not found for -lgcov` 19 | Instead, we are calculating code coverage using the docker build with 20 | [github actions](https://github.com/yonhan3/systemf/actions). 21 | 22 | ## Ubuntu Installation from Scratch 23 | 24 | The easiest way to use ubuntu-like is to use the latest docker meklund/systemf-gh-action. 25 | 26 | If you want to build from scratch, use the files in Docker for guidence: 27 | 28 | For candidate RPMS, look at the Dockerfile: 29 | ``` 30 | grep apt-get docker/Dockerfile 31 | ``` 32 | For building for development, either run `docker/systemf-build` or use it as guidence. 33 | 34 | ## Code Coverage 35 | 36 | Code coverage is only currently working in the ubuntu docker 37 | container. OSX has issues. To run code coverage: 38 | 39 | ``` 40 | ./configure --enable-code-coverage 41 | make check-code-coverage 42 | ``` 43 | Alternatively: 44 | ``` 45 | (cd docker && COMMAND=/usr/bin/systemf-check docker-compose up) 46 | ``` 47 | The resulting html will be printed, but it will likely 48 | point to [here](systemf-coverage/index.html) 49 | (this is not checked into git). 50 | 51 | ## Visual Studio and GDB in OSX Docker container 52 | 53 | ### OSX Installation 54 | 55 | ``` 56 | brew install gdb 57 | ``` 58 | * [Follow these signing instructions.](https://sourceware.org/gdb/wiki/PermissionsDarwin) 59 | * Note that there is a ./docker/gdb-entitlement.xml for that step in the instructions. 60 | 61 | > Special thanks to [Spencer Elliott](https://medium.com/@spe_) for his blog post on [Debugging C/C++ Programs Remotely Using Visual Studio Code and gdbserver](https://medium.com/@spe_/debugging-c-c-programs-remotely-using-visual-studio-code-and-gdbserver-559d3434fb78) 62 | 63 | ## How to Create a Release 64 | 65 | Currently creating a release is a manual process. Once a consistent 66 | process is created, a github action will be created. 67 | 68 | 1. Update AC_INIT in configure.ac with the [semantic version](http://semver.org/). 69 | 2. Start the docker container with latest code: 70 | ``` 71 | docker-compose -f docker/docker-compose.yml up --build -d 72 | ``` 73 | 3. Run the script to do most of the work in the container: 74 | ``` 75 | docker-compose -f docker/docker-compose.yml exec systemf-test systemf-release-build 76 | ``` 77 | 4. Tag this release and push the tag. 78 | For example: 79 | ``` 80 | git tag -a V0.9.0 -m 0.9.0 81 | git push --follow-tags 82 | ``` 83 | *The last line output from step 3 gives the exact command to use.* 84 | 5. You may have to get your branch merged from a fork into master. Push your branch to cisco. 85 | ``` 86 | git remote add cisco git@github.com:cisco/systemf.git 87 | git push --follow-tags cisco 88 | ``` 89 | 6. Create a [github release](https://github.com/cisco/systemf/releases/new) 90 | 1. Enter your tag version in `Tag Version`. 91 | 2. For `Release title` enter a title with a format of "Release 0.9.0". 92 | 3. For the description, give a bulleted list of all major changes. 93 | 4. Attach the systemf-$version.tar.gz 94 | 5. Click 'this is a pre-release' if it isn't yet ready for general consumption. 95 | 6. `Publish release` 96 | 97 | 98 | ## Appendix A - printf options 99 | 100 | Some of these options were used as the basis for the format strings 101 | in the systemf() code. They are here for reference. 102 | 103 | | option | data type | option | data type | 104 | | ------ | --------- | ------ | --------- | 105 | | d, i, o, u, x, X | int | e, E, f, F, g, G, a A | double | 106 | | c | char | s | string | 107 | | C, S, | Not C99, "Don't use" | p | pointer | 108 | | n | count of chars written so far | m | GlibC strerror(errno) | 109 | | % | % | | | 110 | 111 | -------------------------------------------------------------------------------- /tests/test.test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import os 4 | import sys 5 | import shutil 6 | from subprocess import run, PIPE 7 | 8 | from multiprocessing import Process, SimpleQueue 9 | 10 | def get_tests() -> list: 11 | with open('test.json', 'r') as json_file: 12 | try: 13 | return json.load(json_file) 14 | except Exception as e: 15 | eprint(f'# Error loading json: {str(e)}') 16 | sys.exit(-1) 17 | 18 | def run_command(index): 19 | # Run these from the location of this file. 20 | return run(['../test-runner', str(index + 1)], stdout=PIPE, stderr=PIPE) 21 | 22 | def fixup_compare_value(compare_value): 23 | """Convert systemf() int return value to process return value 24 | 25 | systemf() returns an int, but the harness can only return a uchar. 26 | This converts the expected int to the uchar returned. This may 27 | be platform-specific and will be enhanced when differences are discovered. 28 | """ 29 | if compare_value < 0: 30 | return 256 + compare_value 31 | else: 32 | return compare_value 33 | 34 | # Run a comparison operation on return_code, stdout, or stderr 35 | def do_comparison(i, operator, real_value, compare_value): 36 | # Everything we get from systemf() will be bytes 37 | 38 | if type(compare_value) is int: 39 | compare_value = fixup_compare_value(compare_value) 40 | 41 | if type(compare_value) is str: 42 | compare_value = str.encode(compare_value.replace("#", str(i+1))) 43 | 44 | if operator == '==': 45 | return real_value == compare_value 46 | elif operator == '!=': 47 | return real_value != compare_value 48 | elif operator == '>': 49 | return real_value > compare_value 50 | elif operator == '<': 51 | return real_value < compare_value 52 | elif operator == 'contains': 53 | return compare_value in real_value 54 | 55 | # Run this from within the tests directory 56 | os.chdir(os.path.dirname(sys.argv[0])) 57 | if os.path.isdir('tmp'): 58 | shutil.rmtree('tmp') 59 | os.mkdir('tmp') 60 | 61 | # Pull in the JSON test data 62 | # See readme for test data json specification 63 | tests = get_tests() 64 | 65 | # Tell the test framework how many tests we'll have 66 | print(f'1..{len(tests)}') 67 | 68 | # Go through each test 69 | for i in range(len(tests)): 70 | test = tests[i] 71 | # Test description 72 | errlogs = '' 73 | description = test['description'] 74 | 75 | print() 76 | print(f'# Running test {i+1} - {description}') 77 | 78 | setup = test.get('setup') 79 | if setup: 80 | # Oh the irony of calling system here. 81 | os.system(setup.replace("#", str(i+1))) 82 | 83 | # Command to run (array) 84 | command = test['command'] 85 | # Either 'null' or ['operator', 'value'] 86 | test_return_code = test['return_code'] 87 | # Either 'null' or ['operator', 'value'] 88 | test_stdout = test['stdout'] 89 | # Either 'null' or ['operator', 'value'] 90 | test_stderr = test['stderr'] 91 | 92 | returned = run_command(i) 93 | return_code = returned.returncode 94 | stdout = returned.stdout 95 | stderr = returned.stderr 96 | 97 | result = 'ok' 98 | 99 | if test_return_code != None and not do_comparison(i, test_return_code[0], return_code, test_return_code[1]): 100 | print(f'# Failed: {{"return_code": {json.dumps(test_return_code)}}}') 101 | result = 'not ok' 102 | if test_stdout != None and not do_comparison(i, test_stdout[0], stdout, test_stdout[1]): 103 | print(f'# Failed: {{"stdout": {json.dumps(test_stdout)}}}') 104 | result = 'not ok' 105 | if test_stderr != None and not do_comparison(i, test_stderr[0], stderr, test_stderr[1]): 106 | print(f'# Failed: {{"stderr": {json.dumps(test_stderr)}}}') 107 | result = 'not ok' 108 | 109 | if result == 'not ok': 110 | print('#') 111 | print(f'# return_code = {return_code}') 112 | for io in (("STDOUT", stdout), ("STDERR", stderr)): 113 | print('#') 114 | print(f'# {io[0]}:') 115 | for line in io[1].decode(errors="replace").split("\n"): 116 | print(f'# {line}') 117 | 118 | print(f'{result} {i+1} - {description}') 119 | 120 | print('exit status: 1') 121 | -------------------------------------------------------------------------------- /m4/ax_check_gnu_make.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_GNU_MAKE([run-if-true],[run-if-false]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro searches for a GNU version of make. If a match is found: 12 | # 13 | # * The makefile variable `ifGNUmake' is set to the empty string, otherwise 14 | # it is set to "#". This is useful for including a special features in a 15 | # Makefile, which cannot be handled by other versions of make. 16 | # * The makefile variable `ifnGNUmake' is set to #, otherwise 17 | # it is set to the empty string. This is useful for including a special 18 | # features in a Makefile, which can be handled 19 | # by other versions of make or to specify else like clause. 20 | # * The variable `_cv_gnu_make_command` is set to the command to invoke 21 | # GNU make if it exists, the empty string otherwise. 22 | # * The variable `ax_cv_gnu_make_command` is set to the command to invoke 23 | # GNU make by copying `_cv_gnu_make_command`, otherwise it is unset. 24 | # * If GNU Make is found, its version is extracted from the output of 25 | # `make --version` as the last field of a record of space-separated 26 | # columns and saved into the variable `ax_check_gnu_make_version`. 27 | # * Additionally if GNU Make is found, run shell code run-if-true 28 | # else run shell code run-if-false. 29 | # 30 | # Here is an example of its use: 31 | # 32 | # Makefile.in might contain: 33 | # 34 | # # A failsafe way of putting a dependency rule into a makefile 35 | # $(DEPEND): 36 | # $(CC) -MM $(srcdir)/*.c > $(DEPEND) 37 | # 38 | # @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND))) 39 | # @ifGNUmake@ include $(DEPEND) 40 | # @ifGNUmake@ else 41 | # fallback code 42 | # @ifGNUmake@ endif 43 | # 44 | # Then configure.in would normally contain: 45 | # 46 | # AX_CHECK_GNU_MAKE() 47 | # AC_OUTPUT(Makefile) 48 | # 49 | # Then perhaps to cause gnu make to override any other make, we could do 50 | # something like this (note that GNU make always looks for GNUmakefile 51 | # first): 52 | # 53 | # if ! test x$_cv_gnu_make_command = x ; then 54 | # mv Makefile GNUmakefile 55 | # echo .DEFAULT: > Makefile ; 56 | # echo \ $_cv_gnu_make_command \$@ >> Makefile; 57 | # fi 58 | # 59 | # Then, if any (well almost any) other make is called, and GNU make also 60 | # exists, then the other make wraps the GNU make. 61 | # 62 | # LICENSE 63 | # 64 | # Copyright (c) 2008 John Darrington 65 | # Copyright (c) 2015 Enrico M. Crisostomo 66 | # 67 | # Copying and distribution of this file, with or without modification, are 68 | # permitted in any medium without royalty provided the copyright notice 69 | # and this notice are preserved. This file is offered as-is, without any 70 | # warranty. 71 | 72 | #serial 12 73 | 74 | AC_DEFUN([AX_CHECK_GNU_MAKE],dnl 75 | [AC_PROG_AWK 76 | AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl 77 | _cv_gnu_make_command="" ; 78 | dnl Search all the common names for GNU make 79 | for a in "$MAKE" make gmake gnumake ; do 80 | if test -z "$a" ; then continue ; fi ; 81 | if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then 82 | _cv_gnu_make_command=$a ; 83 | AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make") 84 | ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }') 85 | break ; 86 | fi 87 | done ;]) 88 | dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise 89 | AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])]) 90 | AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifnGNUmake], [""])], [AS_VAR_SET([ifnGNUmake], ["#"])]) 91 | AS_VAR_IF([_cv_gnu_make_command], [""], [AS_UNSET(ax_cv_gnu_make_command)], [AS_VAR_SET([ax_cv_gnu_make_command], [${_cv_gnu_make_command}])]) 92 | AS_VAR_IF([_cv_gnu_make_command], [""],[$2],[$1]) 93 | AC_SUBST([ifGNUmake]) 94 | AC_SUBST([ifnGNUmake]) 95 | ]) 96 | -------------------------------------------------------------------------------- /m4/ltsugar.m4: -------------------------------------------------------------------------------- 1 | # ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- 2 | # 3 | # Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software 4 | # Foundation, Inc. 5 | # Written by Gary V. Vaughan, 2004 6 | # 7 | # This file is free software; the Free Software Foundation gives 8 | # unlimited permission to copy and/or distribute it, with or without 9 | # modifications, as long as this notice is preserved. 10 | 11 | # serial 6 ltsugar.m4 12 | 13 | # This is to help aclocal find these macros, as it can't see m4_define. 14 | AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) 15 | 16 | 17 | # lt_join(SEP, ARG1, [ARG2...]) 18 | # ----------------------------- 19 | # Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their 20 | # associated separator. 21 | # Needed until we can rely on m4_join from Autoconf 2.62, since all earlier 22 | # versions in m4sugar had bugs. 23 | m4_define([lt_join], 24 | [m4_if([$#], [1], [], 25 | [$#], [2], [[$2]], 26 | [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) 27 | m4_define([_lt_join], 28 | [m4_if([$#$2], [2], [], 29 | [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) 30 | 31 | 32 | # lt_car(LIST) 33 | # lt_cdr(LIST) 34 | # ------------ 35 | # Manipulate m4 lists. 36 | # These macros are necessary as long as will still need to support 37 | # Autoconf-2.59, which quotes differently. 38 | m4_define([lt_car], [[$1]]) 39 | m4_define([lt_cdr], 40 | [m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], 41 | [$#], 1, [], 42 | [m4_dquote(m4_shift($@))])]) 43 | m4_define([lt_unquote], $1) 44 | 45 | 46 | # lt_append(MACRO-NAME, STRING, [SEPARATOR]) 47 | # ------------------------------------------ 48 | # Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. 49 | # Note that neither SEPARATOR nor STRING are expanded; they are appended 50 | # to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). 51 | # No SEPARATOR is output if MACRO-NAME was previously undefined (different 52 | # than defined and empty). 53 | # 54 | # This macro is needed until we can rely on Autoconf 2.62, since earlier 55 | # versions of m4sugar mistakenly expanded SEPARATOR but not STRING. 56 | m4_define([lt_append], 57 | [m4_define([$1], 58 | m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) 59 | 60 | 61 | 62 | # lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) 63 | # ---------------------------------------------------------- 64 | # Produce a SEP delimited list of all paired combinations of elements of 65 | # PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list 66 | # has the form PREFIXmINFIXSUFFIXn. 67 | # Needed until we can rely on m4_combine added in Autoconf 2.62. 68 | m4_define([lt_combine], 69 | [m4_if(m4_eval([$# > 3]), [1], 70 | [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl 71 | [[m4_foreach([_Lt_prefix], [$2], 72 | [m4_foreach([_Lt_suffix], 73 | ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, 74 | [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) 75 | 76 | 77 | # lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) 78 | # ----------------------------------------------------------------------- 79 | # Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited 80 | # by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. 81 | m4_define([lt_if_append_uniq], 82 | [m4_ifdef([$1], 83 | [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], 84 | [lt_append([$1], [$2], [$3])$4], 85 | [$5])], 86 | [lt_append([$1], [$2], [$3])$4])]) 87 | 88 | 89 | # lt_dict_add(DICT, KEY, VALUE) 90 | # ----------------------------- 91 | m4_define([lt_dict_add], 92 | [m4_define([$1($2)], [$3])]) 93 | 94 | 95 | # lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) 96 | # -------------------------------------------- 97 | m4_define([lt_dict_add_subkey], 98 | [m4_define([$1($2:$3)], [$4])]) 99 | 100 | 101 | # lt_dict_fetch(DICT, KEY, [SUBKEY]) 102 | # ---------------------------------- 103 | m4_define([lt_dict_fetch], 104 | [m4_ifval([$3], 105 | m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), 106 | m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) 107 | 108 | 109 | # lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) 110 | # ----------------------------------------------------------------- 111 | m4_define([lt_if_dict_fetch], 112 | [m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], 113 | [$5], 114 | [$6])]) 115 | 116 | 117 | # lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) 118 | # -------------------------------------------------------------- 119 | m4_define([lt_dict_filter], 120 | [m4_if([$5], [], [], 121 | [lt_join(m4_quote(m4_default([$4], [[, ]])), 122 | lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), 123 | [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl 124 | ]) 125 | -------------------------------------------------------------------------------- /src/derived-parser.h: -------------------------------------------------------------------------------- 1 | /* A Bison parser, made by GNU Bison 3.5.1. */ 2 | 3 | /* Bison interface for Yacc-like parsers in C 4 | 5 | Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, 6 | Inc. 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . */ 20 | 21 | /* As a special exception, you may create a larger work that contains 22 | part or all of the Bison parser skeleton and distribute that work 23 | under terms of your choice, so long as that work isn't itself a 24 | parser generator using the skeleton or a modified version thereof 25 | as a parser skeleton. Alternatively, if you modify or redistribute 26 | the parser skeleton itself, you may (at your option) remove this 27 | special exception, which will cause the skeleton and the resulting 28 | Bison output files to be licensed under the GNU General Public 29 | License without this special exception. 30 | 31 | This special exception was added by the Free Software Foundation in 32 | version 2.2 of Bison. */ 33 | 34 | /* Undocumented macros, especially those whose name start with YY_, 35 | are private implementation details. Do not rely on them. */ 36 | 37 | #ifndef YY__SF1_YY_SRC_DERIVED_PARSER_H_INCLUDED 38 | # define YY__SF1_YY_SRC_DERIVED_PARSER_H_INCLUDED 39 | /* Debug traces. */ 40 | #ifndef _SF1_YYDEBUG 41 | # if defined YYDEBUG 42 | #if YYDEBUG 43 | # define _SF1_YYDEBUG 1 44 | # else 45 | # define _SF1_YYDEBUG 0 46 | # endif 47 | # else /* ! defined YYDEBUG */ 48 | # define _SF1_YYDEBUG 0 49 | # endif /* ! defined YYDEBUG */ 50 | #endif /* ! defined _SF1_YYDEBUG */ 51 | #if _SF1_YYDEBUG 52 | extern int _sf1_yydebug; 53 | #endif 54 | /* "%code requires" blocks. */ 55 | #line 13 "src/parser.y" 56 | 57 | #if 1 // This compiles in debug code enabled by _sf1_yydebug; 58 | #undef SYSTEMF1_YYDEBUG 59 | #define SYSTEMF1_YYDEBUG 1 60 | extern int _sf1_yydebug; 61 | #endif 62 | 63 | #include "systemf-internal.h" 64 | 65 | 66 | #line 67 "src/derived-parser.h" 67 | 68 | /* Token type. */ 69 | #ifndef _SF1_YYTOKENTYPE 70 | # define _SF1_YYTOKENTYPE 71 | enum _sf1_yytokentype 72 | { 73 | SYLLABLE = 258, 74 | SPACE = 259, 75 | QUOTE = 260, 76 | LESSER = 261, 77 | TWO_GREATER_AND_ONE = 262, 78 | TWO_GREATER = 263, 79 | AND_GREATER = 264, 80 | GREATER = 265, 81 | TWO_GREATER_GREATER = 266, 82 | AND_GREATER_GREATER = 267, 83 | GREATER_GREATER = 268, 84 | GREATER_AND_TWO = 269, 85 | AND_AND = 270, 86 | OR_OR = 271, 87 | SEMICOLON = 272, 88 | OR = 273 89 | }; 90 | #endif 91 | 92 | /* Value type. */ 93 | #if ! defined _SF1_YYSTYPE && ! defined _SF1_YYSTYPE_IS_DECLARED 94 | union _SF1_YYSTYPE 95 | { 96 | 97 | /* redirects */ 98 | _sf1_redirect * redirects; 99 | /* redirect */ 100 | _sf1_redirect * redirect; 101 | /* SYLLABLE */ 102 | _sf1_syllable * SYLLABLE; 103 | /* words */ 104 | _sf1_syllable * words; 105 | /* syllables */ 106 | _sf1_syllable * syllables; 107 | /* cmds */ 108 | _sf1_task * cmds; 109 | /* cmd */ 110 | _sf1_task * cmd; 111 | #line 112 "src/derived-parser.h" 112 | 113 | }; 114 | typedef union _SF1_YYSTYPE _SF1_YYSTYPE; 115 | # define _SF1_YYSTYPE_IS_TRIVIAL 1 116 | # define _SF1_YYSTYPE_IS_DECLARED 1 117 | #endif 118 | 119 | /* Location type. */ 120 | #if ! defined _SF1_YYLTYPE && ! defined _SF1_YYLTYPE_IS_DECLARED 121 | typedef struct _SF1_YYLTYPE _SF1_YYLTYPE; 122 | struct _SF1_YYLTYPE 123 | { 124 | int first_line; 125 | int first_column; 126 | int last_line; 127 | int last_column; 128 | }; 129 | # define _SF1_YYLTYPE_IS_DECLARED 1 130 | # define _SF1_YYLTYPE_IS_TRIVIAL 1 131 | #endif 132 | 133 | 134 | 135 | int _sf1_yyparse (yyscan_t scanner, _sf1_parse_args *results); 136 | /* "%code provides" blocks. */ 137 | #line 25 "src/parser.y" 138 | 139 | #define YYSTYPE _SF1_YYSTYPE 140 | #define YYLTYPE _SF1_YYLTYPE 141 | #define YY_DECL int _sf1_yylex(YYSTYPE * yylval_param , YYLTYPE *yylloc, yyscan_t yyscanner, _sf1_parse_args *results) 142 | extern YY_DECL; 143 | 144 | int _sf1_yyerror(_SF1_YYLTYPE *locp, yyscan_t scanner, _sf1_parse_args *results, const char *msg); 145 | 146 | #line 147 "src/derived-parser.h" 147 | 148 | #endif /* !YY__SF1_YY_SRC_DERIVED_PARSER_H_INCLUDED */ 149 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | # 4 | # From https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/autoconf.html: 5 | # 6 | # Files used in preparing a software package for distribution, when using just Autoconf: 7 | # 8 | # your source files --> [autoscan*] --> [configure.scan] --> configure.ac 9 | # 10 | # configure.ac --. 11 | # | .------> autoconf* -----> configure 12 | # [aclocal.m4] --+---+ 13 | # | `-----> [autoheader*] --> [config.h.in] 14 | # [acsite.m4] ---' 15 | # 16 | # Makefile.in 17 | # Additionally, if you use Automake, the following additional productions come into play: 18 | # 19 | # [acinclude.m4] --. 20 | # | 21 | # [local macros] --+--> aclocal* --> aclocal.m4 22 | # | 23 | # configure.ac ----' 24 | # 25 | # configure.ac --. 26 | # +--> automake* --> Makefile.in 27 | # Makefile.am ---' 28 | # Files used in configuring a software package: 29 | # 30 | # .-------------> [config.cache] 31 | # configure* ------------+-------------> config.log 32 | # | 33 | # [config.h.in] -. v .-> [config.h] -. 34 | # +--> config.status* -+ +--> make* 35 | # Makefile.in ---' `-> Makefile ---' 36 | 37 | AC_PREREQ([2.69]) 38 | AC_INIT([systemf], [1.0.0], [systemf@patnan.com]) 39 | AC_CONFIG_AUX_DIR([build-aux]) 40 | AM_INIT_AUTOMAKE([foreign subdir-objects]) # Does not require NEWS, COPYING, AUTHORS, ChangeLog or README 41 | 42 | # silent make https://autotools.io/automake/silent.html 43 | # silent rules enabled by default with 'yes' 44 | # disable silent runles with ./configure --disable-silent-rules 45 | AM_SILENT_RULES([yes]) # less verbose make output 46 | # AM_SILENT_RULES() # use make -s to get silent output 47 | 48 | AC_CONFIG_SRCDIR([src/systemf.c]) 49 | AC_CONFIG_HEADERS([config.h]) # use config.h instead of passing -D in the command line 50 | AC_CONFIG_MACRO_DIR([m4]) 51 | 52 | AC_LANG([C]) # Use C not C++ 53 | 54 | # Checks for programs. 55 | AC_PROG_CC 56 | 57 | # In case that you want to check for specific versions of gcc 58 | # For example in case that you need C11 support you want to 59 | # check for gcc-4.9 60 | #AC_PROG_CC([gcc-4.9 gcc cc]) 61 | 62 | AC_PROG_CC_C99 # or AC_PROG_CC_89 to force C89 mode or AC_PROG_CC_STDC to go to latest supported standard (currently C99) 63 | 64 | AC_PROG_INSTALL 65 | AC_PROG_CC_C_O # Need to have per product flags myexecutable_CFLAG 66 | AC_PROG_RANLIB # Need for to create libraries: .a 67 | 68 | # Need libtool init to make .so's 69 | LT_INIT 70 | 71 | dnl For now, use src/makefile to compile with bison and flex. 72 | dnl AX_PROG_BISON() 73 | dnl AM_PROG_LEX 74 | dnl AC_PROG_YACC 75 | 76 | # Checks for libraries. 77 | 78 | # Found libraries are automatically addded to LIBS 79 | # AC_SEARCH_LIBS([pthread_cond_wait], [pthread],[],[ 80 | # AC_MSG_ERROR([You need to install pthreads library.]) 81 | # ]) 82 | 83 | # AC_SEARCH_LIBS([g_test_init], [glib-2.0],[],[ 84 | # AC_MSG_ERROR([You need to install glib-2.0 library.]) 85 | # ]) 86 | 87 | # Checks for header files. 88 | AC_HEADER_ASSERT # ./configure --disable-assert to define NDEBUG 89 | AC_CHECK_HEADER([stdlib.h]) 90 | 91 | # Check for C11's optional Atomic operations library 92 | # AC_CHECK_HEADER([stdatomic.h], [], [ 93 | # AC_MSG_ERROR([C11 with atomic support needed.]) 94 | # ]) 95 | 96 | # Checks for typedefs, structures, and compiler characteristics. 97 | 98 | # Checks for library functions. 99 | 100 | # The following statement will use pkg-config --cflags --libs 101 | # to find out CFLAGS and -l options required to build a target that 102 | # it's going to link against glib2.0. 103 | # The required CFLAGS and -l options are available as DEPS_CFLAGS 104 | # and DEPS_LIBS in Makefile.am 105 | # PKG_CHECK_MODULES([DEPS], [glib-2.0 >= 2.24.1]) 106 | 107 | AC_ARG_ENABLE([myfeature], 108 | AS_HELP_STRING([--disable-myfeature],[disable myfeature to get remove support for this and that]), 109 | [enable_myfeature=${enableval}],[enable_myfeature=yes]) 110 | 111 | if test "x${enable_myfeature}" == "xyes"; then 112 | AC_DEFINE([MYFEATURE], 1, [myfeature is enabled]) 113 | else 114 | AC_MSG_WARN([ 115 | ----------------------------------------------------------------------------------- 116 | Are you sure that you want to have myfeature disabled? You will lose this and that. 117 | ----------------------------------------------------------------------------------- 118 | ]) 119 | fi 120 | 121 | AC_CONFIG_FILES([ 122 | Makefile 123 | ]) 124 | AC_REQUIRE_AUX_FILE([tap-driver.sh]) 125 | AX_VALGRIND_CHECK # http://www.gnu.org/software/autoconf-archive/ax_valgrind_check.html - make check-valgrind 126 | AX_CODE_COVERAGE # http://www.gnu.org/software/autoconf-archive/ax_code_coverage.html#ax_code_coverage - make check-code-coverage generates coverage report 127 | AC_OUTPUT 128 | -------------------------------------------------------------------------------- /tests/test-generator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from typing import Any 4 | import string 5 | import json 6 | 7 | template = """ 8 | /* 9 | * DO NOT EDIT THIS FILE. 10 | * This files was generated by test-generator.py. 11 | * Any changes should be done there. 12 | * DO NOT EDIT THIS FILE. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "../src/systemf.h" 22 | 23 | static void sigsegv_handler(int signo) {{ 24 | fputs("Test aborted due to a SEGV\\n", stderr); 25 | exit(EXIT_FAILURE); 26 | }} 27 | 28 | static void sanity_check_tests_dir(void) {{ 29 | const size_t bufsize = 4096; 30 | char *buf = malloc(bufsize); 31 | if (!buf) {{ 32 | fprintf(stderr, "Test aborted because %zu bytes were not available.\\n", bufsize); 33 | exit(EXIT_FAILURE); 34 | }} 35 | 36 | char *cwd = getcwd(buf, bufsize); 37 | if (!cwd) {{ 38 | fputs("Test aborted because current working directory could not be extracted\\n", stderr); 39 | exit(EXIT_FAILURE); 40 | }} 41 | 42 | char *tests = NULL; 43 | for (char *s = strstr(cwd, "tests"); s; s = strstr(s+5, "tests")) {{ 44 | tests = s; 45 | }} 46 | if (!tests || (tests - cwd) < (strlen(cwd) - 6)) {{ 47 | fputs("This command must be run in the tests directory.\\n", stderr); 48 | exit(EXIT_FAILURE); 49 | }} 50 | }} 51 | 52 | {test_funcs} 53 | 54 | {test_funcs_table} 55 | 56 | static void usage() {{ 57 | puts("usage: test-runner "); 58 | puts(""); 59 | puts("testnum: Test Description"); 60 | {usage_tests} 61 | }} 62 | 63 | int main(int argc, const char *argv[]) {{ 64 | int index = 0; 65 | if (argc == 2) {{ 66 | index = atoi(argv[1]); 67 | }} 68 | 69 | if (index == 0 || index > {max_tests}) {{ 70 | usage(); 71 | exit(EXIT_FAILURE); 72 | }} 73 | 74 | sanity_check_tests_dir(); 75 | 76 | // Set above function as signal handler for the SIGINT signal: 77 | if (signal(SIGSEGV, sigsegv_handler) == SIG_ERR) {{ 78 | fputs("An error occurred while setting a signal handler.\\n", stderr); 79 | return EXIT_FAILURE; 80 | }} 81 | 82 | return test_func_table[index - 1](); 83 | }} 84 | """ 85 | 86 | def eprint(s: str): 87 | print(s, file=sys.stderr) 88 | 89 | def str2func(index: int, s: str) -> str: 90 | """Creates a unique c-function name from a string. 91 | Credit: https://stackoverflow.com/questions/34544784/arbitrary-string-to-valid-python-name 92 | """ 93 | VALID_NAME_CHARACTERS = string.ascii_letters + string.digits 94 | PLACEHOLDER = "_" 95 | prefix = f"test_{index + 1}_" 96 | sym = ''.join(c.lower() if c in VALID_NAME_CHARACTERS else PLACEHOLDER for c in s) 97 | return prefix + sym 98 | 99 | def cstr_escape(s: str) -> str: 100 | return s.replace("\"", "\\\"") 101 | 102 | test_func_template = """ 103 | static int {test_name}() {{ 104 | return systemf1({test_args}); 105 | }} 106 | """ 107 | 108 | def generate_test_func(index: int, test: dict) -> str: 109 | command = test['command'] 110 | test_name = str2func(index, test['description']) 111 | test_args = [] 112 | 113 | for arg in command: 114 | if type(arg) is str: 115 | # Replace ['#'] arguments with the string representation of this test number. 116 | arg = arg.replace("#", str(index+1)) 117 | test_args.append(f'"{cstr_escape(arg)}"') 118 | elif type(arg) is int: 119 | test_args.append(f'{arg}') 120 | else: 121 | eprint(f'# Unknown arg type, aborting: {arg}') 122 | sys.exit(1) 123 | test_args = ", ".join(test_args) 124 | return test_func_template.format(test_name=test_name, test_args=test_args) 125 | 126 | def generate_test_funcs(tests: list) -> str: 127 | return "".join([generate_test_func(i, f) for i, f in enumerate(tests)]) 128 | 129 | def generate_usage_tests(tests: list) -> str: 130 | result = "" 131 | for index, test in enumerate(tests): 132 | line = f"{index + 1:7}: {test['description']}" 133 | result += f' puts("{cstr_escape(line)}");\n' 134 | return result 135 | 136 | test_func_table_template = """ 137 | int (*(test_func_table[]))(void) = {{ 138 | {test_functions} 139 | }}; 140 | """ 141 | 142 | def generate_test_func_table(tests: list) -> str: 143 | test_functions = "\n".join([f' {str2func(i, t["description"])},' for i, t in enumerate(tests)]) 144 | return(test_func_table_template.format(test_functions=test_functions)) 145 | 146 | def generate_kwargs(tests: list) -> str: 147 | kwargs = {} 148 | kwargs["usage_tests"] = generate_usage_tests(tests) 149 | kwargs["test_funcs"] = generate_test_funcs(tests) 150 | kwargs["test_funcs_table"] = generate_test_func_table(tests) 151 | kwargs["max_tests"] = len(tests) 152 | return kwargs 153 | 154 | def get_tests() -> list: 155 | current_dir = os.path.dirname(sys.argv[0]) 156 | TEST_JSON = os.path.join(current_dir, 'test.json') 157 | with open(TEST_JSON, 'r') as json_file: 158 | try: 159 | return json.load(json_file) 160 | except Exception as e: 161 | eprint(f'# Error loading json: {str(e)}') 162 | sys.exit(-1) 163 | 164 | def main() -> int: 165 | tests = get_tests() 166 | kwargs = generate_kwargs(tests) 167 | 168 | with open("tests/test-runner.c", "w") as t: 169 | t.write(template.format(**kwargs)) 170 | return 0 171 | 172 | sys.exit(main()) -------------------------------------------------------------------------------- /m4/lt~obsolete.m4: -------------------------------------------------------------------------------- 1 | # lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- 2 | # 3 | # Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software 4 | # Foundation, Inc. 5 | # Written by Scott James Remnant, 2004. 6 | # 7 | # This file is free software; the Free Software Foundation gives 8 | # unlimited permission to copy and/or distribute it, with or without 9 | # modifications, as long as this notice is preserved. 10 | 11 | # serial 5 lt~obsolete.m4 12 | 13 | # These exist entirely to fool aclocal when bootstrapping libtool. 14 | # 15 | # In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), 16 | # which have later been changed to m4_define as they aren't part of the 17 | # exported API, or moved to Autoconf or Automake where they belong. 18 | # 19 | # The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN 20 | # in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us 21 | # using a macro with the same name in our local m4/libtool.m4 it'll 22 | # pull the old libtool.m4 in (it doesn't see our shiny new m4_define 23 | # and doesn't know about Autoconf macros at all.) 24 | # 25 | # So we provide this file, which has a silly filename so it's always 26 | # included after everything else. This provides aclocal with the 27 | # AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything 28 | # because those macros already exist, or will be overwritten later. 29 | # We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. 30 | # 31 | # Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. 32 | # Yes, that means every name once taken will need to remain here until 33 | # we give up compatibility with versions before 1.7, at which point 34 | # we need to keep only those names which we still refer to. 35 | 36 | # This is to help aclocal find these macros, as it can't see m4_define. 37 | AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) 38 | 39 | m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) 40 | m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) 41 | m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) 42 | m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) 43 | m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) 44 | m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) 45 | m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) 46 | m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) 47 | m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) 48 | m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) 49 | m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) 50 | m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) 51 | m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) 52 | m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) 53 | m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) 54 | m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) 55 | m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) 56 | m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) 57 | m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) 58 | m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) 59 | m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) 60 | m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) 61 | m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) 62 | m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) 63 | m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) 64 | m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) 65 | m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) 66 | m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) 67 | m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) 68 | m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) 69 | m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) 70 | m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) 71 | m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) 72 | m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) 73 | m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) 74 | m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) 75 | m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) 76 | m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) 77 | m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) 78 | m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) 79 | m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) 80 | m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) 81 | m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) 82 | m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) 83 | m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) 84 | m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) 85 | m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) 86 | m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) 87 | m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) 88 | m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) 89 | m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) 90 | m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) 91 | m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) 92 | m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) 93 | m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) 94 | m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) 95 | m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) 96 | m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) 97 | m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) 98 | m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) 99 | m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) 100 | -------------------------------------------------------------------------------- /src/file-sandbox-check.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * The dir structure is used in walking directories. For these two cases, 9 | * these map to directory tuples: {start, end, depth}: 10 | * char *a = "/job/one" {&a[0], &a[1], 1} {&a[1], &a[5], 2} {&a[5], &a[8], 3} {&a[8], &a[8], 4}} 11 | * char *b = "job/one" {&a[0], &a[4], 1} {&a[4], &a[7], 2} {&a[7], &a[7], 3} 12 | * char *c = "/job/" {&a[0], &a[1], 1} {&a[1], &a[5], 2} {&a[5], &a[5], 3} 13 | * 14 | * In addition there is a zero {NULL, &a[0], 0} and and EOF {e, e, end+1) where e=&a[strlen(a)) 15 | */ 16 | struct dir { 17 | char *buffer; 18 | char *start; 19 | char *end; 20 | int depth; 21 | }; 22 | 23 | static int is_dot_dot(struct dir *path) { 24 | const char *s = path->start; 25 | return ((s[0] == '.') && (s[1] == '.') && ((s[2] == '/') || (!s[2]))); 26 | } 27 | 28 | static int is_one_dot(struct dir *path) { 29 | const char *s = path->start; 30 | return ((s[0] == '.') && ((s[1] == '/') || (!s[1]))); 31 | } 32 | 33 | static struct dir zero_dir(char *buffer) { 34 | struct dir dir; 35 | dir.buffer = buffer; 36 | dir.start = NULL; 37 | dir.end = buffer; 38 | dir.depth = 0; 39 | return dir; 40 | } 41 | 42 | static int is_zero_dir(struct dir *dir) { 43 | return dir->start == NULL; 44 | } 45 | 46 | static int is_first_dir(struct dir *dir) { 47 | return (dir->start == dir->buffer); 48 | } 49 | 50 | static int eof_dir(struct dir *dir) { 51 | // Have we even started processing and if so, are we at the end. 52 | return (dir->start != NULL) && !dir->start[0]; 53 | } 54 | 55 | static int next_dir(struct dir *dir) { 56 | // We are already at the end. 57 | if (eof_dir(dir)) { 58 | return 0; 59 | } 60 | 61 | // Special case. A leading '/' is a dir all by itself. 62 | if (is_zero_dir(dir) && dir->buffer[0] == '/') { 63 | dir->start = dir->buffer; 64 | dir->end = dir->buffer; 65 | dir->depth = 1; 66 | return 1; 67 | } 68 | 69 | // Walk over all leading '/' 70 | dir->start = dir->end + strspn(dir->end, "/"); 71 | 72 | // Walk over non '/' 73 | dir->end = dir->start + strcspn(dir->start, "/"); 74 | 75 | // Include the trailing '/' if it is present 76 | if (dir->end[0]) { 77 | dir->end += 1; 78 | } 79 | 80 | if (is_dot_dot(dir)) { 81 | dir->depth -= 1; 82 | } else if (!is_one_dot(dir)) { 83 | dir->depth += 1; 84 | } 85 | 86 | return 1; 87 | } 88 | 89 | static int prev_dir(struct dir *dir) { 90 | if (is_zero_dir(dir)) { 91 | return 0; 92 | } 93 | 94 | if (is_first_dir(dir)) { 95 | *dir = zero_dir(dir->buffer); 96 | return 1; 97 | } 98 | 99 | if (dir->start - 1 == dir->buffer) { 100 | // Special case where previous is the root '/' 101 | dir->start = dir->buffer; 102 | dir->end = &dir->buffer[1]; 103 | return 1; 104 | } 105 | 106 | // Move dir->end and then walk over any doubled "//" 107 | for (dir->end = dir->start; dir->end[-1] == '/' && dir->end[-2] == '/'; dir->end -= 1); 108 | 109 | // Walk dir->start back 110 | for (dir->start = dir->end - 1; 111 | (dir->start[-1] != '/') && (dir->start != dir->buffer); 112 | dir->start--); 113 | 114 | if (is_dot_dot(dir)) { 115 | dir->depth += 1; 116 | } else if (!is_one_dot(dir)) { 117 | dir->depth -=1; 118 | } 119 | return 1; 120 | } 121 | 122 | static size_t path_copy(char *dest, struct dir *source) { 123 | size_t slen = source->end - source->start; 124 | memcpy(dest, source->start, slen); 125 | dest[slen] = 0; 126 | return slen; 127 | } 128 | 129 | static char *extract_simplified_path(char *path) { 130 | char *simple_path; 131 | struct dir simple_dir; 132 | struct dir cursor; 133 | 134 | simple_path = malloc(strlen(path)+1); 135 | if (!simple_path) { 136 | return NULL; 137 | } 138 | simple_dir = zero_dir(simple_path); 139 | 140 | cursor = zero_dir(path); 141 | while (next_dir(&cursor)) { 142 | if (is_one_dot(&cursor)) { 143 | continue; 144 | } 145 | if (is_dot_dot(&cursor) && !is_zero_dir(&simple_dir) && !is_dot_dot(&simple_dir)) { 146 | // This destroys the previous dir. 147 | prev_dir(&simple_dir); 148 | simple_dir.end[0] = 0; 149 | continue; 150 | } 151 | 152 | // Copy the data and move forward. 153 | path_copy(simple_dir.end, &cursor); 154 | next_dir(&simple_dir); 155 | } 156 | return (simple_path); 157 | } 158 | 159 | /* 160 | * simple_sandbox_check assumes that the trusted_path is the preamble 161 | * to the path (that is how systemf works) and it never has enough .. to 162 | * go into the trusted path". 163 | */ 164 | static int simple_sandbox_check(char *trusted_path, char *path) { 165 | char *untrusted_path = path + strlen(trusted_path); 166 | struct dir up = zero_dir(untrusted_path); 167 | 168 | while (next_dir(&up)) { 169 | if (up.depth < 0) { 170 | return(EACCES); 171 | } 172 | } 173 | return 0; 174 | } 175 | 176 | /* 177 | * complex_sandbox_check converts the paths to simplified paths 178 | * and then compares those resulting paths. 179 | */ 180 | static int complex_sandbox_check(char *trusted_path, char *path) { 181 | char *tp = extract_simplified_path(trusted_path); 182 | char *p = extract_simplified_path(path); 183 | int retval; 184 | 185 | if (p && tp) { 186 | size_t slen = strlen(tp) - 1; 187 | assert(tp[slen] == '/'); 188 | 189 | if (strncmp(p, tp, slen)) { 190 | // No match even before the trailing slash. 191 | retval = EACCES; 192 | } else if (p[slen] && p[slen] != '/') { 193 | // EG: tp = /hope/ and p = /hopeless 194 | retval = EACCES; 195 | } else { 196 | retval = 0; 197 | } 198 | } else { 199 | retval = ENOMEM; 200 | } 201 | free(tp); 202 | free(p); 203 | 204 | return retval; 205 | } 206 | 207 | /* 208 | * Checks if the path is under the trusted_path. 209 | * Returns 0 on good and EACCES on bad or ENOMEM if unable to make check. 210 | */ 211 | int _sf1_file_sandbox_check(char *trusted_path, char *path) { 212 | int retval; 213 | 214 | if (0 == simple_sandbox_check(trusted_path, path)) { 215 | retval = 0; 216 | } else if (!trusted_path[0]) { 217 | // Fail: Trusted is current directory and simple check was EACCES. 218 | retval = EACCES; 219 | } else { 220 | retval = complex_sandbox_check(trusted_path, path); 221 | } 222 | 223 | if (retval) { 224 | fprintf(stderr, "systemf: %s: sandboxing %s\n", strerror(retval), path); 225 | } 226 | 227 | return retval; 228 | } -------------------------------------------------------------------------------- /m4/ax_valgrind_check.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_valgrind_check.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_VALGRIND_CHECK() 8 | # 9 | # DESCRIPTION 10 | # 11 | # Checks whether Valgrind is present and, if so, allows running `make 12 | # check` under a variety of Valgrind tools to check for memory and 13 | # threading errors. 14 | # 15 | # Defines VALGRIND_CHECK_RULES which should be substituted in your 16 | # Makefile; and $enable_valgrind which can be used in subsequent configure 17 | # output. VALGRIND_ENABLED is defined and substituted, and corresponds to 18 | # the value of the --enable-valgrind option, which defaults to being 19 | # enabled if Valgrind is installed and disabled otherwise. 20 | # 21 | # If unit tests are written using a shell script and automake's 22 | # LOG_COMPILER system, the $(VALGRIND) variable can be used within the 23 | # shell scripts to enable Valgrind, as described here: 24 | # 25 | # https://www.gnu.org/software/gnulib/manual/html_node/Running-self_002dtests-under-valgrind.html 26 | # 27 | # Usage example: 28 | # 29 | # configure.ac: 30 | # 31 | # AX_VALGRIND_CHECK 32 | # 33 | # Makefile.am: 34 | # 35 | # @VALGRIND_CHECK_RULES@ 36 | # VALGRIND_SUPPRESSIONS_FILES = my-project.supp 37 | # EXTRA_DIST = my-project.supp 38 | # 39 | # This results in a "check-valgrind" rule being added to any Makefile.am 40 | # which includes "@VALGRIND_CHECK_RULES@" (assuming the module has been 41 | # configured with --enable-valgrind). Running `make check-valgrind` in 42 | # that directory will run the module's test suite (`make check`) once for 43 | # each of the available Valgrind tools (out of memcheck, helgrind, drd and 44 | # sgcheck), and will output results to test-suite-$toolname.log for each. 45 | # The target will succeed if there are zero errors and fail otherwise. 46 | # 47 | # The macro supports running with and without libtool. 48 | # 49 | # LICENSE 50 | # 51 | # Copyright (c) 2014, 2015 Philip Withnall 52 | # 53 | # Copying and distribution of this file, with or without modification, are 54 | # permitted in any medium without royalty provided the copyright notice 55 | # and this notice are preserved. This file is offered as-is, without any 56 | # warranty. 57 | 58 | #serial 3 59 | 60 | AC_DEFUN([AX_VALGRIND_CHECK],[ 61 | dnl Check for --enable-valgrind 62 | AC_MSG_CHECKING([whether to enable Valgrind on the unit tests]) 63 | AC_ARG_ENABLE([valgrind], 64 | [AS_HELP_STRING([--enable-valgrind], [Whether to enable Valgrind on the unit tests])], 65 | [enable_valgrind=$enableval],[enable_valgrind=]) 66 | 67 | # Check for Valgrind. 68 | AC_CHECK_PROG([VALGRIND],[valgrind],[valgrind]) 69 | 70 | AS_IF([test "$enable_valgrind" = "yes" -a "$VALGRIND" = ""],[ 71 | AC_MSG_ERROR([Could not find valgrind; either install it or reconfigure with --disable-valgrind]) 72 | ]) 73 | AS_IF([test "$enable_valgrind" != "no"],[enable_valgrind=yes]) 74 | 75 | AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"]) 76 | AC_SUBST([VALGRIND_ENABLED],[$enable_valgrind]) 77 | AC_MSG_RESULT([$enable_valgrind]) 78 | 79 | # Check for Valgrind tools we care about. 80 | m4_define([valgrind_tool_list],[[memcheck], [helgrind], [drd], [exp-sgcheck]]) 81 | 82 | AS_IF([test "$VALGRIND" != ""],[ 83 | m4_foreach([vgtool],[valgrind_tool_list],[ 84 | m4_define([vgtooln],AS_TR_SH(vgtool)) 85 | m4_define([ax_cv_var],[ax_cv_valgrind_tool_]vgtooln) 86 | AC_CACHE_CHECK([for Valgrind tool ]vgtool,ax_cv_var,[ 87 | ax_cv_var= 88 | AS_IF([`$VALGRIND --tool=vgtool --help 2&>/dev/null`],[ 89 | ax_cv_var="vgtool" 90 | ]) 91 | ]) 92 | 93 | AC_SUBST([VALGRIND_HAVE_TOOL_]vgtooln,[$ax_cv_var]) 94 | ]) 95 | ]) 96 | 97 | VALGRIND_CHECK_RULES=' 98 | # Valgrind check 99 | # 100 | # Optional: 101 | # - VALGRIND_SUPPRESSIONS_FILES: Space-separated list of Valgrind suppressions 102 | # files to load. (Default: empty) 103 | # - VALGRIND_FLAGS: General flags to pass to all Valgrind tools. 104 | # (Default: --num-callers=30) 105 | # - VALGRIND_$toolname_FLAGS: Flags to pass to Valgrind $toolname (one of: 106 | # memcheck, helgrind, drd, sgcheck). (Default: various) 107 | 108 | # Optional variables 109 | VALGRIND_SUPPRESSIONS ?= $(addprefix --suppressions=,$(VALGRIND_SUPPRESSIONS_FILES)) 110 | VALGRIND_FLAGS ?= --num-callers=30 111 | VALGRIND_memcheck_FLAGS ?= --leak-check=full --show-reachable=no 112 | VALGRIND_helgrind_FLAGS ?= --history-level=approx 113 | VALGRIND_drd_FLAGS ?= 114 | VALGRIND_sgcheck_FLAGS ?= 115 | 116 | # Internal use 117 | valgrind_tools = memcheck helgrind drd sgcheck 118 | valgrind_log_files = $(addprefix test-suite-,$(addsuffix .log,$(valgrind_tools))) 119 | 120 | valgrind_memcheck_flags = --tool=memcheck $(VALGRIND_memcheck_FLAGS) 121 | valgrind_helgrind_flags = --tool=helgrind $(VALGRIND_helgrind_FLAGS) 122 | valgrind_drd_flags = --tool=drd $(VALGRIND_drd_FLAGS) 123 | valgrind_sgcheck_flags = --tool=exp-sgcheck $(VALGRIND_sgcheck_FLAGS) 124 | 125 | valgrind_quiet = $(valgrind_quiet_$(V)) 126 | valgrind_quiet_ = $(valgrind_quiet_$(AM_DEFAULT_VERBOSITY)) 127 | valgrind_quiet_0 = --quiet 128 | 129 | # Support running with and without libtool. 130 | ifneq ($(LIBTOOL),) 131 | valgrind_lt = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=execute 132 | else 133 | valgrind_lt = 134 | endif 135 | 136 | # Use recursive makes in order to ignore errors during check 137 | check-valgrind: 138 | ifeq ($(VALGRIND_ENABLED),yes) 139 | -$(foreach tool,$(valgrind_tools), \ 140 | $(if $(VALGRIND_HAVE_TOOL_$(tool))$(VALGRIND_HAVE_TOOL_exp_$(tool)), \ 141 | $(MAKE) $(AM_MAKEFLAGS) -k check-valgrind-tool VALGRIND_TOOL=$(tool); \ 142 | ) \ 143 | ) 144 | else 145 | @echo "Need to reconfigure with --enable-valgrind" 146 | endif 147 | 148 | # Valgrind running 149 | VALGRIND_TESTS_ENVIRONMENT = \ 150 | $(TESTS_ENVIRONMENT) \ 151 | env VALGRIND=$(VALGRIND) \ 152 | G_SLICE=always-malloc,debug-blocks \ 153 | G_DEBUG=fatal-warnings,fatal-criticals,gc-friendly 154 | 155 | VALGRIND_LOG_COMPILER = \ 156 | $(valgrind_lt) \ 157 | $(VALGRIND) $(VALGRIND_SUPPRESSIONS) --error-exitcode=1 $(VALGRIND_FLAGS) 158 | 159 | check-valgrind-tool: 160 | ifeq ($(VALGRIND_ENABLED),yes) 161 | $(MAKE) check-TESTS \ 162 | TESTS_ENVIRONMENT="$(VALGRIND_TESTS_ENVIRONMENT)" \ 163 | LOG_COMPILER="$(VALGRIND_LOG_COMPILER)" \ 164 | LOG_FLAGS="$(valgrind_$(VALGRIND_TOOL)_flags)" \ 165 | TEST_SUITE_LOG=test-suite-$(VALGRIND_TOOL).log 166 | else 167 | @echo "Need to reconfigure with --enable-valgrind" 168 | endif 169 | 170 | DISTCHECK_CONFIGURE_FLAGS ?= 171 | DISTCHECK_CONFIGURE_FLAGS += --disable-valgrind 172 | 173 | MOSTLYCLEANFILES ?= 174 | MOSTLYCLEANFILES += $(valgrind_log_files) 175 | 176 | .PHONY: check-valgrind check-valgrind-tool 177 | ' 178 | 179 | AC_SUBST([VALGRIND_CHECK_RULES]) 180 | m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([VALGRIND_CHECK_RULES])]) 181 | ]) 182 | -------------------------------------------------------------------------------- /src/parser-support.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "systemf-internal.h" 12 | 13 | #define DEBUG 0 14 | #define VA_ARGS(...) , ##__VA_ARGS__ 15 | #define DBG(fmt, ...) if (DEBUG) { printf("%s:%-3d:%24s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__ VA_ARGS(__VA_ARGS__)); } 16 | 17 | void _sf1_merge_and_free_syllables (_sf1_syllable *syl, char **text_pp, char **trusted_path_pp, int *is_glob_p) { 18 | int is_glob = 0; 19 | int is_file = 0; 20 | int is_trusted = 1; 21 | size_t escapes = 0; 22 | size_t slen = 0; 23 | size_t sandbox_len = 0; 24 | size_t sandbox_candidate = 0; 25 | size_t sandbox_index = 0; 26 | int doing_sandbox_detection = 1; 27 | const char glob_chars[] = "?*[]"; // Globs supported by glob() 28 | char *text, *cursor, *trusted_path; 29 | 30 | /* 31 | * First, walk all the syllables. Determine the following of the merged argument: 32 | * is_glob: Glob processing should be run because one syllable is a glob. 33 | * is_file: Filename sandboxing candidate was detected. 34 | * is_trusted: No syllables are not trusted. (%s, %p, %*p, %i) 35 | * escapes: Number of globlike characters that need escaping if is_glob is detected. 36 | * slen: String length of all syllables concatenated (not including escapes) 37 | * sandbox_len: Longest span of trusted bytes ending with '/' 38 | */ 39 | DBG("begin") 40 | for (_sf1_syllable *s = syl; s != NULL; s = s->next) 41 | { 42 | int syl_is_glob = s->flags & SYL_IS_GLOB; 43 | int syl_escape_glob = s->flags & SYL_ESCAPE_GLOB; 44 | int syl_is_file = s->flags & SYL_IS_FILE; 45 | int syl_is_trusted = s->flags & SYL_IS_TRUSTED; 46 | 47 | DBG("SYL - %-8s ig %d, eg %d, if %d, it %d", 48 | s->text, syl_is_glob, syl_escape_glob, syl_is_file, syl_is_trusted); 49 | 50 | if (syl_escape_glob) { 51 | int i; 52 | 53 | // look for glob characters and if detected, count how many need to be escaped. 54 | for (i = strcspn(s->text, glob_chars); s->text[i]; i += strcspn(s->text + i, glob_chars)) { 55 | escapes += 1; 56 | i += 1; 57 | } 58 | slen += i; 59 | } else { 60 | slen += strlen(s->text); 61 | } 62 | 63 | // Scan for fmt string glob patterns and set as globbed file path if detected 64 | if (syl_is_file) { 65 | is_file = 1; 66 | } 67 | if (syl_is_glob) { 68 | is_glob = 1; 69 | } 70 | if (!syl_is_trusted) { 71 | is_trusted = 0; 72 | } 73 | 74 | if (doing_sandbox_detection) { 75 | if (syl_is_trusted && !syl_is_glob) { 76 | int i; 77 | char *cursor = s->text; 78 | // search for directory separators adding spans including them as we go 79 | for (cursor = s->text; *cursor; cursor += i) { 80 | i = strcspn(cursor, "/"); 81 | 82 | if (cursor[i]) { 83 | i += 1; 84 | sandbox_len += sandbox_candidate + i; 85 | sandbox_candidate = 0; 86 | } else { 87 | sandbox_candidate += i; 88 | } 89 | } 90 | } else { 91 | doing_sandbox_detection = 0; 92 | } 93 | } 94 | } 95 | 96 | DBG("ig %d, if %d, it %d, es %lu, sl %lu, snl %lu, snc %lu, sni %lu, dsd %d", 97 | is_glob, is_file, is_trusted, escapes, slen, sandbox_len, sandbox_candidate, 98 | sandbox_index & 0, doing_sandbox_detection); 99 | 100 | if (is_glob) { 101 | slen += escapes; 102 | } 103 | 104 | // Allocate memory for the trusted path. 105 | if (is_file && !is_trusted) { 106 | if (sandbox_len) { 107 | trusted_path = malloc(sandbox_len + 1); 108 | trusted_path[sandbox_len] = 0; 109 | } else { 110 | trusted_path = strdup(""); 111 | } 112 | // FIXME: handle NULL path 113 | } else { 114 | sandbox_len = 0; 115 | trusted_path = NULL; 116 | } 117 | text = malloc(slen + 1); 118 | cursor = text; 119 | 120 | /* 121 | * Now walk the syllables. 122 | * * Filling in the sandbox_path if needed. 123 | * * Filling in text (with escaped glob patterns when needed). 124 | */ 125 | for (_sf1_syllable *s = syl; s != NULL;) 126 | { 127 | int syl_escape_glob = s->flags & SYL_ESCAPE_GLOB; 128 | _sf1_syllable *save_next; 129 | 130 | for (int i = 0; (sandbox_index < sandbox_len) && s->text[i]; i+=1, sandbox_index +=1) { 131 | trusted_path[sandbox_index] = s->text[i]; 132 | } 133 | 134 | if (is_glob && syl_escape_glob) { 135 | // look for glob characters and if detected, count how many need to be escaped. 136 | char *start = s->text; 137 | while (*start) { 138 | int span = strcspn(start, glob_chars); 139 | memcpy(cursor, start, span); 140 | cursor += span; 141 | start += span; 142 | if (*start) { 143 | cursor[0] = '\\'; 144 | cursor[1] = *start; 145 | start += 1; 146 | cursor += 2; 147 | } 148 | } 149 | } else { 150 | cursor = stpcpy(cursor, s->text); 151 | } 152 | save_next = s->next; 153 | free(s); 154 | s = save_next; 155 | cursor[0] = 0; 156 | } 157 | cursor[0] = 0; 158 | assert(cursor == text + slen); 159 | 160 | *text_pp = text; 161 | *trusted_path_pp = trusted_path; 162 | *is_glob_p = is_glob; 163 | DBG("end: trusted_path=%s", trusted_path) 164 | return; 165 | } 166 | 167 | 168 | _sf1_redirect *_sf1_merge_redirects (_sf1_redirect *left, _sf1_redirect *right) { 169 | _sf1_redirect *cursor; 170 | for (cursor = left; cursor->next; cursor = cursor->next); 171 | cursor->next = right; 172 | return left; 173 | } 174 | 175 | _sf1_redirect *_sf1_create_redirect(_sf1_stream stream, _sf1_stream target, int append, _sf1_syllable *file_syllables) 176 | { 177 | _sf1_redirect *redirect = calloc(1, sizeof(*redirect)); 178 | // FIXME: Handle malloc error 179 | redirect->stream = stream; 180 | redirect->target = target; 181 | redirect->append = append; 182 | DBG("begin: stream %d, target %d, append %d, file %p", stream, target, append, file_syllables); 183 | if (file_syllables) { 184 | int is_glob; 185 | 186 | _sf1_merge_and_free_syllables(file_syllables, &redirect->text, &redirect->trusted_path, &is_glob); 187 | // FIXME: This needs to be cleanly handled similar to a syntax error. 188 | // Currently we don't support globs in file targets. 189 | assert(!is_glob); 190 | } 191 | DBG("end"); 192 | 193 | return redirect; 194 | } 195 | 196 | static void append_redirect(_sf1_task *task, _sf1_redirect *redirect) { 197 | _sf1_redirect **next_pp = &(task->redirects); 198 | while (*next_pp) { 199 | next_pp = &((*next_pp)->next); 200 | } 201 | *next_pp = redirect; 202 | } 203 | 204 | void _sf1_create_redirect_pipe (_sf1_task *left, _sf1_task *right) { 205 | append_redirect(left, _sf1_create_redirect(_SF1_STDOUT, _SF1_PIPE, 0, NULL)); 206 | append_redirect(right, _sf1_create_redirect(_SF1_STDIN, _SF1_PIPE, 0, NULL)); 207 | } 208 | 209 | _sf1_task *_sf1_create_cmd (_sf1_syllable *syllables, _sf1_redirect *redirects) { 210 | int is_glob; 211 | char *text; 212 | char *trusted_path; 213 | _sf1_syllable *next; 214 | _sf1_task *task; 215 | 216 | task = _sf1_task_create(); 217 | 218 | while (syllables) { 219 | next = syllables->next_word; 220 | _sf1_merge_and_free_syllables(syllables, &text, &trusted_path, &is_glob); 221 | _sf1_task_add_arg(task, text, trusted_path, is_glob); 222 | syllables = next; 223 | } 224 | 225 | _sf1_task_add_redirects(task, redirects); 226 | 227 | return task; 228 | } 229 | -------------------------------------------------------------------------------- /m4/ax_code_coverage.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_code_coverage.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CODE_COVERAGE() 8 | # 9 | # DESCRIPTION 10 | # 11 | # Defines CODE_COVERAGE_CFLAGS and CODE_COVERAGE_LDFLAGS which should be 12 | # included in the CFLAGS and LIBS/LDFLAGS variables of every build target 13 | # (program or library) which should be built with code coverage support. 14 | # Also defines CODE_COVERAGE_RULES which should be substituted in your 15 | # Makefile; and $enable_code_coverage which can be used in subsequent 16 | # configure output. CODE_COVERAGE_ENABLED is defined and substituted, and 17 | # corresponds to the value of the --enable-code-coverage option, which 18 | # defaults to being disabled. 19 | # 20 | # Test also for gcov program and create GCOV variable that could be 21 | # substituted. 22 | # 23 | # Note that all optimisation flags in CFLAGS must be disabled when code 24 | # coverage is enabled. 25 | # 26 | # Usage example: 27 | # 28 | # configure.ac: 29 | # 30 | # AX_CODE_COVERAGE 31 | # 32 | # Makefile.am: 33 | # 34 | # @CODE_COVERAGE_RULES@ 35 | # my_program_LIBS = ... $(CODE_COVERAGE_LDFLAGS) ... 36 | # my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... 37 | # 38 | # This results in a "check-code-coverage" rule being added to any 39 | # Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module 40 | # has been configured with --enable-code-coverage). Running `make 41 | # check-code-coverage` in that directory will run the module's test suite 42 | # (`make check`) and build a code coverage report detailing the code which 43 | # was touched, then print the URI for the report. 44 | # 45 | # This code was derived from Makefile.decl in GLib, originally licenced 46 | # under LGPLv2.1+. 47 | # 48 | # LICENSE 49 | # 50 | # Copyright (c) 2012 Philip Withnall 51 | # Copyright (c) 2012 Xan Lopez 52 | # Copyright (c) 2012 Christian Persch 53 | # Copyright (c) 2012 Paolo Borelli 54 | # Copyright (c) 2012 Dan Winship 55 | # Copyright (c) 2015 Bastien ROUCARIES 56 | # 57 | # This library is free software; you can redistribute it and/or modify it 58 | # under the terms of the GNU Lesser General Public License as published by 59 | # the Free Software Foundation; either version 2.1 of the License, or (at 60 | # your option) any later version. 61 | # 62 | # This library is distributed in the hope that it will be useful, but 63 | # WITHOUT ANY WARRANTY; without even the implied warranty of 64 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 65 | # General Public License for more details. 66 | # 67 | # You should have received a copy of the GNU Lesser General Public License 68 | # along with this program. If not, see . 69 | 70 | #serial 5 71 | 72 | AC_DEFUN([AX_CODE_COVERAGE],[ 73 | dnl Check for --enable-code-coverage 74 | AC_REQUIRE([AC_PROG_SED]) 75 | 76 | # allow to override gcov location 77 | AC_ARG_WITH([gcov], 78 | [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], 79 | [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], 80 | [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) 81 | 82 | AC_MSG_CHECKING([whether to build with code coverage support]) 83 | AC_ARG_ENABLE([code-coverage], 84 | AS_HELP_STRING([--enable-code-coverage], 85 | [Whether to enable code coverage support]),, 86 | enable_code_coverage=no) 87 | 88 | AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes]) 89 | AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) 90 | AC_MSG_RESULT($enable_code_coverage) 91 | 92 | AS_IF([ test "$enable_code_coverage" = "yes" ], [ 93 | # check for gcov 94 | AC_CHECK_TOOL([GCOV], 95 | [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], 96 | [:]) 97 | AS_IF([test "X$GCOV" = "X:"], 98 | [AC_MSG_ERROR([gcov is needed to do coverage])]) 99 | AC_SUBST([GCOV]) 100 | 101 | dnl Check if gcc is being used 102 | AS_IF([ test "$GCC" = "no" ], [ 103 | AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) 104 | ]) 105 | 106 | # List of supported lcov versions. 107 | lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.14" 108 | 109 | AC_CHECK_PROG([LCOV], [lcov], [lcov]) 110 | AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) 111 | 112 | AS_IF([ test "$LCOV" ], [ 113 | AC_CACHE_CHECK([for lcov version], ax_cv_lcov_version, [ 114 | ax_cv_lcov_version=invalid 115 | lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'` 116 | for lcov_check_version in $lcov_version_list; do 117 | if test "$lcov_version" = "$lcov_check_version"; then 118 | ax_cv_lcov_version="$lcov_check_version (ok)" 119 | fi 120 | done 121 | ]) 122 | ], [ 123 | lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list" 124 | AC_MSG_ERROR([$lcov_msg]) 125 | ]) 126 | 127 | case $ax_cv_lcov_version in 128 | ""|invalid[)] 129 | lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)." 130 | AC_MSG_ERROR([$lcov_msg]) 131 | LCOV="exit 0;" 132 | ;; 133 | esac 134 | 135 | AS_IF([ test -z "$GENHTML" ], [ 136 | AC_MSG_ERROR([Could not find genhtml from the lcov package]) 137 | ]) 138 | 139 | dnl Build the code coverage flags 140 | CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" 141 | CODE_COVERAGE_LDFLAGS="-lgcov" 142 | 143 | AC_SUBST([CODE_COVERAGE_CFLAGS]) 144 | AC_SUBST([CODE_COVERAGE_LDFLAGS]) 145 | ]) 146 | 147 | CODE_COVERAGE_RULES=' 148 | # Code coverage 149 | # 150 | # Optional: 151 | # - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. 152 | # (Default: $(top_builddir)) 153 | # - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated 154 | # by lcov for code coverage. (Default: 155 | # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info) 156 | # - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage 157 | # reports to be created. (Default: 158 | # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage) 159 | # - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov 160 | # - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the lcov instance. 161 | # (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) 162 | # - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the lcov instance. 163 | # (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) 164 | # - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml 165 | # instance. (Default: empty) 166 | # - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore 167 | # 168 | # The generated report will be titled using the $(PACKAGE_NAME) and 169 | # $(PACKAGE_VERSION). In order to add the current git hash to the title, 170 | # use the git-version-gen script, available online. 171 | 172 | # Optional variables 173 | CODE_COVERAGE_DIRECTORY ?= $(top_builddir) 174 | CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info 175 | CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage 176 | CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" 177 | CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) 178 | CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) 179 | CODE_COVERAGE_GENHTML_OPTIONS ?= 180 | CODE_COVERAGE_IGNORE_PATTERN ?= 181 | 182 | code_coverage_quiet = $(code_coverage_quiet_$(V)) 183 | code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) 184 | code_coverage_quiet_0 = --quiet 185 | 186 | # Use recursive makes in order to ignore errors during check 187 | check-code-coverage: 188 | ifeq ($(CODE_COVERAGE_ENABLED),yes) 189 | -$(MAKE) $(AM_MAKEFLAGS) -k check 190 | $(MAKE) $(AM_MAKEFLAGS) code-coverage-capture 191 | else 192 | @echo "Need to reconfigure with --enable-code-coverage" 193 | endif 194 | 195 | # Capture code coverage data 196 | code-coverage-capture: code-coverage-capture-hook 197 | ifeq ($(CODE_COVERAGE_ENABLED),yes) 198 | $(LCOV) $(code_coverage_quiet) --directory $(CODE_COVERAGE_DIRECTORY) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(PACKAGE_NAME)-$(PACKAGE_VERSION)" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_OPTIONS) 199 | $(LCOV) $(code_coverage_quiet) --directory $(CODE_COVERAGE_DIRECTORY) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" 200 | -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp 201 | LANG=C $(GENHTML) $(code_coverage_quiet) --prefix $(CODE_COVERAGE_DIRECTORY) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS) 202 | @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html" 203 | else 204 | @echo "Need to reconfigure with --enable-code-coverage" 205 | endif 206 | 207 | # Hook rule executed before code-coverage-capture, overridable by the user 208 | code-coverage-capture-hook: 209 | 210 | ifeq ($(CODE_COVERAGE_ENABLED),yes) 211 | clean: code-coverage-clean 212 | code-coverage-clean: 213 | -$(LCOV) --directory $(top_builddir) -z 214 | -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY) 215 | -find . -name "*.gcda" -o -name "*.gcov" -delete 216 | endif 217 | 218 | GITIGNOREFILES ?= 219 | GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) 220 | 221 | DISTCHECK_CONFIGURE_FLAGS ?= 222 | DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage 223 | 224 | .PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean 225 | ' 226 | 227 | AC_SUBST([CODE_COVERAGE_RULES]) 228 | m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])]) 229 | ]) 230 | -------------------------------------------------------------------------------- /tests/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Test simple command", 4 | "command": [ "./cmd stdout stderr return 20" ], 5 | "stdout": ["==", "1"], 6 | "stderr": ["==", "2"], 7 | "return_code": ["==", 20] 8 | }, 9 | { 10 | "description": "Test a non-existant binary", 11 | "command": [ "binarydoesntexist" ], 12 | "stdout": null, 13 | "stderr": null, 14 | "return_code": ["!=", 0] 15 | }, 16 | { 17 | "description": "%s", 18 | "command": [ "./cmd %s", "stdout" ], 19 | "stdout": ["==", "1"], 20 | "stderr": ["==", ""], 21 | "return_code": ["==", 0] 22 | }, 23 | { 24 | "description": "%d", 25 | "command": [ "./cmd comma %d %d %d%d", 1, 2, 3, 4 ], 26 | "stdout": ["==", "1,2,34"], 27 | "stderr": ["==", ""], 28 | "return_code": ["==", 0] 29 | }, 30 | { 31 | "description": "\\n is not allowed", 32 | "command": [ "./cmd true\\n./cmd true" ], 33 | "stdout": ["==", ""], 34 | "stderr": ["contains", "ERROR: 1:11:syntax error"], 35 | "return_code": ["==", -1] 36 | }, 37 | { 38 | "description": "Test 'ls' without absolute path (Check PATH functionality) # TODO", 39 | "command": [ "ls" ], 40 | "stdout": null, 41 | "stderr": null, 42 | "return_code": ["==", 0] 43 | }, 44 | { 45 | "description": "fmt glob '*'", 46 | "command": [ "./cmd comma REA*ME.md" ], 47 | "stdout": ["==", "README.md"], 48 | "stderr": ["==", ""], 49 | "return_code": ["==", 0] 50 | }, 51 | { 52 | "description": "fmt glob '?'", 53 | "command": [ "./cmd comma REA?ME.md" ], 54 | "stdout": ["==", "README.md"], 55 | "stderr": ["==", ""], 56 | "return_code": ["==", 0] 57 | }, 58 | { 59 | "description": "param '%p'", 60 | "command": [ "./cmd comma %p", "bubba" ], 61 | "stdout": ["==", "bubba"], 62 | "stderr": ["==", ""], 63 | "return_code": ["==", 0] 64 | }, 65 | { 66 | "description": "param '%*p'", 67 | "setup": "cd tmp; mkdir #; touch #/'XYZOOc{3,4}'", 68 | "command": [ "./cmd comma tmp/#/%*p", "?Y*[abcd]{3,4}" ], 69 | "stdout": ["==", "tmp/#/XYZOOc{3,4}"], 70 | "stderr": ["==", ""], 71 | "return_code": ["==", 0] 72 | }, 73 | { 74 | "description": "param '%p' doesn't match", 75 | "setup": "cd tmp; mkdir #; touch #/'XYZOOc{3,4}'", 76 | "command": [ "./cmd comma tmp/#/%p", "?Y*[abcd]{3,4}" ], 77 | "stdout": ["==", "tmp/#/?Y*[abcd]{3,4}"], 78 | "stderr": ["==", ""], 79 | "return_code": ["==", 0] 80 | }, 81 | { 82 | "description": "param '%p' glob escape success", 83 | "setup": "cd tmp; mkdir #; touch #/'XY*?[abcd]{3,4}'", 84 | "command": [ "./cmd comma tmp/#/?%p", "Y*?[abcd]{3,4}" ], 85 | "stdout": ["==", "tmp/#/XY*?[abcd]{3,4}"], 86 | "stderr": ["==", ""], 87 | "return_code": ["==", 0] 88 | }, 89 | { 90 | "description": "param '%!p'", 91 | "command": [ "./cmd comma %!p", "../bubba" ], 92 | "stdout": ["==", "../bubba"], 93 | "stderr": ["==", ""], 94 | "return_code": ["==", 0] 95 | }, 96 | { 97 | "description": "param '%*p' with no match", 98 | "command": [ "./cmd comma %*p", "/etc/passw?thisdoesnnotexist" ], 99 | "stdout": ["==", ""], 100 | "stderr": ["==", "systemf: no matches found: /etc/passw?thisdoesnnotexist\n"], 101 | "return_code": ["==", -1] 102 | }, 103 | { 104 | "description": "Bad Redirects - (multiple stdin)", 105 | "command": [ "./cmd true &1 2>/dev/null" ], 113 | "stdout": null, 114 | "stderr": ["contains", "ERROR: There should only be one stderr per command."], 115 | "return_code": ["==", -1] 116 | }, 117 | { 118 | "description": "Bad Redirects - (multiple stderr 2)", 119 | "command": [ "./cmd true 2>/dev/null 2>/tmp" ], 120 | "stdout": null, 121 | "stderr": ["contains", "ERROR: There should only be one stderr per command."], 122 | "return_code": ["==", -1] 123 | }, 124 | { 125 | "description": "Bad Redirects - (stderr + stdout append + stdout)", 126 | "command": [ "./cmd true &>>/dev/null >/tmp" ], 127 | "stdout": null, 128 | "stderr": ["contains", "ERROR: There should only be one stdout per command."], 129 | "return_code": ["==", -1] 130 | }, 131 | { 132 | "description": "Bad Redirects - (stderr + 2 stdout", 133 | "command": [ "./cmd true &>/dev/null >/tmp" ], 134 | "stdout": null, 135 | "stderr": ["contains", "ERROR: There should only be one stdout per command."], 136 | "return_code": ["==", -1] 137 | }, 138 | { 139 | "description": "Bad Redirects - (stdout append + stdout)", 140 | "command": [ "./cmd >>/dev/null >/tmp" ], 141 | "stdout": null, 142 | "stderr": ["contains", "ERROR: There should only be one stdout per command."], 143 | "return_code": ["==", -1] 144 | }, 145 | { 146 | "description": "'$' is illegal in fmt", 147 | "command": [ "./cmd comma $HOME" ], 148 | "stdout": ["==", ""], 149 | "stderr": ["contains", "ERROR: 1:13:syntax error"], 150 | "return_code": ["==", -1] 151 | }, 152 | { 153 | "description": "pipe '|'", 154 | "command": [ "./cmd stdout | ./cmd incr | ./cmd incr" ], 155 | "stdout": ["==", "3"], 156 | "stderr": null, 157 | "return_code": ["==", 0] 158 | }, 159 | { 160 | "description": "redirect '<'", 161 | "command": [ "./cmd cat < README.md" ], 162 | "stdout": ["contains", "JSON"], 163 | "stderr": null, 164 | "return_code": ["==", 0] 165 | }, 166 | { 167 | "description": "redirect '>'", 168 | "command": [ "./cmd stdout stderr > tmp/#.txt && ./cmd incr < tmp/#.txt" ], 169 | "stdout": ["==", "2"], 170 | "stderr": ["==", "2"], 171 | "return_code": ["==", 0] 172 | }, 173 | { 174 | "description": "redirect '>>'", 175 | "command": [ "./cmd stdout >> tmp/#.txt && ./cmd stdout >> tmp/#.txt && ./cmd incr < tmp/#.txt" ], 176 | "stdout": ["==", "12"], 177 | "stderr": ["==", ""], 178 | "return_code": ["==", 0] 179 | }, 180 | { 181 | "description": "redirect '2>'", 182 | "command": [ "./cmd stdout >> tmp/#.txt && ./cmd stdout >> tmp/#.txt && ./cmd incr < tmp/#.txt" ], 183 | "stdout": ["==", "12"], 184 | "stderr": ["==", ""], 185 | "return_code": ["==", 0] 186 | }, 187 | { 188 | "description": "redirect '2>>'", 189 | "command": [ "./cmd stdout stderr 2>> tmp/#.txt && ./cmd stdout stderr 2>> tmp/#.txt && ./cmd incr < tmp/#.txt" ], 190 | "stdout": ["==", "1123"], 191 | "stderr": ["==", ""], 192 | "return_code": ["==", 0] 193 | }, 194 | { 195 | "description": "redirect '>&2'", 196 | "command": [ "./cmd stderr stderr stdout >&2" ], 197 | "stdout": ["==", ""], 198 | "stderr": ["==", "221"], 199 | "return_code": ["==", 0] 200 | }, 201 | { 202 | "description": "redirect '2>&1'", 203 | "command": [ "./cmd stderr stdout stdout 2>&1" ], 204 | "stdout": ["==", "211"], 205 | "stderr": ["==", ""], 206 | "return_code": ["==", 0] 207 | }, 208 | { 209 | "description": "redirect '&>'", 210 | "command": [ "./cmd stderr stdout stdout &> tmp/#.txt && ./cmd incr < tmp/#.txt" ], 211 | "stdout": ["==", "212"], 212 | "stderr": ["==", ""], 213 | "return_code": ["==", 0] 214 | }, 215 | { 216 | "description": "redirect '&>>`*file*'", 217 | "command": [ "./cmd stderr stdout &>> tmp/#.txt && ./cmd stderr stdout &>> tmp/#.txt && ./cmd incr < tmp/#.txt" ], 218 | "stdout": ["==", "2122"], 219 | "stderr": ["==", ""], 220 | "return_code": ["==", 0] 221 | }, 222 | { 223 | "description": "globs fmt *", 224 | "setup": "cd tmp; mkdir #; touch #/aa #/ab #/ac", 225 | "command": [ "./cmd comma tmp/#/a*"], 226 | "stdout": ["==", "tmp/#/aa,tmp/#/ab,tmp/#/ac"], 227 | "stderr": ["==", ""], 228 | "return_code": ["==", 0] 229 | }, 230 | { 231 | "description": "globs fmt [abc]", 232 | "setup": "cd tmp; mkdir #; touch #/aa #/ab #/ac", 233 | "command": [ "./cmd comma tmp/#/a[abc]"], 234 | "stdout": ["==", "tmp/#/aa,tmp/#/ab,tmp/#/ac"], 235 | "stderr": ["==", ""], 236 | "return_code": ["==", 0] 237 | }, 238 | { 239 | "description": "globs fmt ?", 240 | "setup": "cd tmp; mkdir #; touch #/aa #/ab #/ac", 241 | "command": [ "./cmd comma tmp/#/a?"], 242 | "stdout": ["==", "tmp/#/aa,tmp/#/ab,tmp/#/ac"], 243 | "stderr": ["==", ""], 244 | "return_code": ["==", 0] 245 | }, 246 | { 247 | "description": "globs %*p, *", 248 | "setup": "cd tmp; mkdir #; touch #/aa #/ab #/ac", 249 | "command": [ "./cmd comma tmp/#/%*p", "a*" ], 250 | "stdout": ["==", "tmp/#/aa,tmp/#/ab,tmp/#/ac"], 251 | "stderr": ["==", ""], 252 | "return_code": ["==", 0] 253 | }, 254 | { 255 | "description": "filename sandbox escape", 256 | "setup": "mkdir -p tmp/#/x; mkdir -p tmp/#/y", 257 | "command": [ "./cmd comma tmp/#/x/%*p", "../y" ], 258 | "stdout": ["==", ""], 259 | "stderr": ["contains", "systemf: Permission denied: sandboxing tmp/#/x/../y"], 260 | "return_code": ["==", -1] 261 | }, 262 | { 263 | "description": "filename sandbox unescape", 264 | "setup": "mkdir -p tmp/#/x", 265 | "command": [ "./cmd comma tmp/#/x/%*p", "../../../tmp/#/x" ], 266 | "stdout": ["==", "tmp/#/x/../../../tmp/#/x"], 267 | "stderr": ["==", ""], 268 | "return_code": ["==", 0] 269 | }, 270 | { 271 | "description": "true || not-run", 272 | "command": [ "./cmd true || ./cmd stdout" ], 273 | "stdout": ["==", ""], 274 | "stderr": ["==", ""], 275 | "return_code": ["==", 0] 276 | }, 277 | { 278 | "description": "false || run", 279 | "command": [ "./cmd false || ./cmd stderr return 3" ], 280 | "stdout": ["==", ""], 281 | "stderr": ["==", "2"], 282 | "return_code": ["==", 3] 283 | }, 284 | { 285 | "description": "false && not-run", 286 | "command": [ "./cmd false && ./cmd stdout" ], 287 | "stdout": ["==", ""], 288 | "stderr": ["==", ""], 289 | "return_code": ["==", 1] 290 | }, 291 | { 292 | "description": "true && run", 293 | "command": [ "./cmd true && ./cmd stderr return 3" ], 294 | "stdout": ["==", ""], 295 | "stderr": ["==", "2"], 296 | "return_code": ["==", 3] 297 | }, 298 | { 299 | "description": "pipe to subcommand exits when cmd closes", 300 | "command": [ "./cmd count | ./cmd incr" ], 301 | "stdout": ["==", "2"], 302 | "stderr": ["==", ""], 303 | "return_code": ["==", 0] 304 | }, 305 | { 306 | "description": "multiple pipes run properly", 307 | "command": [ "./cmd count | ./cmd incr | ./cmd incr | ./cmd incr" ], 308 | "stdout": ["==", "4"], 309 | "stderr": ["==", ""], 310 | "return_code": ["==", 0] 311 | } 312 | ] 313 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/task.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "systemf-internal.h" 13 | 14 | #define DEBUG 0 15 | #define VA_ARGS(...) , ##__VA_ARGS__ 16 | #define DBG(fmt, ...) if (DEBUG) { printf("%s:%-3d:%24s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__ VA_ARGS(__VA_ARGS__)); } 17 | 18 | typedef struct glob_list_ { 19 | struct glob_list_ *next; 20 | glob_t glob; 21 | } glob_list; 22 | 23 | _sf1_task *_sf1_task_create() { 24 | return calloc(1, sizeof(_sf1_task)); 25 | } 26 | 27 | /* 28 | * _sf1_task_add_redirects - Adds a linked of redirections. 29 | * 30 | * task - Destination Task - Current redirects must be NULL, 31 | * redirects - A linked list of _sf1_redirects where: 32 | * each redirect is malloced 33 | * filename - Is NULL or the destination malloced filename (if appropriate) 34 | * trusted_path - Is the malloced verification path for filename where NULL is current directory. 35 | * append - Specifies if the redirect shoud append to the file. 36 | * 37 | * Note that all malloced memory passed in will be freed with _sf1_task_free(). 38 | * Note that validation of parameters is delayed until that task start happens. 39 | */ 40 | void _sf1_task_add_redirects (_sf1_task *task, _sf1_redirect *redirects) 41 | { 42 | assert(task->redirects == NULL); 43 | task->redirects = redirects; 44 | } 45 | 46 | /* 47 | * _sf1_task_add_arg - Adds the specified argument to the task. 48 | * 49 | * task - Destination Task 50 | * text - The text of the argument. Must be malloced. 51 | * trusted_path - If path verification is to be done this is the pre-realpath path. Either NULL or malloced. 52 | * is_glob - This is a glob expression that glob expansion will be needed for. 53 | * 54 | * Note that all malloced memory passed in will be freed with _sf1_task_free() 55 | */ 56 | _sf1_task_arg *_sf1_task_add_arg (_sf1_task *task, char *text, char *trusted_path, int is_glob) 57 | { 58 | _sf1_task_arg *arg; 59 | 60 | arg = calloc(1, sizeof(_sf1_task_arg)); 61 | if (arg == NULL) { 62 | return NULL; 63 | } 64 | 65 | arg->text = text; 66 | arg->trusted_path = trusted_path; 67 | arg->is_glob = is_glob; 68 | 69 | // Find the last item and append pp 70 | _sf1_task_arg **next_pp = &(task->args); 71 | while (*next_pp) { 72 | next_pp = &((*next_pp)->next); 73 | } 74 | *next_pp = arg; 75 | 76 | return arg; 77 | } 78 | 79 | /* 80 | * Maps a stream to a character string representation. 81 | */ 82 | char *_sf1_stream_name(_sf1_stream stream) { 83 | switch (stream) { 84 | case _SF1_STDIN: return "stdin"; break; 85 | case _SF1_STDOUT: return "stdout"; break; 86 | case _SF1_SHARE: return "shared"; break; 87 | case _SF1_STDERR: return "stderr"; break; 88 | case _SF1_PIPE: return "pipe"; break; 89 | case _SF1_FILE: return "file"; break; 90 | default: assert(0); 91 | } 92 | } 93 | 94 | /* 95 | * Walks all of the redirects and verifies that they are entered in a sane way. 96 | * Returns 0 if not and 1 if good. 97 | */ 98 | int _sf1_redirects_are_sane(_sf1_task *tasks) 99 | { 100 | for (_sf1_task *t = tasks; t; t = t->next) { 101 | int in = 0; 102 | int out = 0; 103 | int err = 0; 104 | for (_sf1_redirect *r = t->redirects; r; r = r ->next) { 105 | int count; 106 | switch (r->stream) { 107 | case _SF1_STDIN: 108 | count = in = in + 1; 109 | break; 110 | case _SF1_STDOUT: 111 | count = out = out + 1; 112 | break; 113 | case _SF1_STDERR: 114 | count = err = err + 1; 115 | break; 116 | default: 117 | assert(0); 118 | } 119 | if (count > 1) { 120 | fprintf(stderr, "ERROR: There should only be one %s per command.", _sf1_stream_name(r->stream)); 121 | return 0; 122 | } 123 | if ((r->target == _SF1_FILE) && !(r->text)) { 124 | // A filename must be supplied. 125 | return 0; 126 | } 127 | } 128 | } 129 | return 1; 130 | } 131 | 132 | /* 133 | * Extracts all the globs from the task arguments. 134 | * task - The task. 135 | * returns: 136 | * 0 on success 137 | * GLOB_NOSPACE for running out of memory, 138 | * GLOB_ABORTED for a read error, and 139 | * GLOB_NOMATCH for when the number of matches doesn't match the specified allowed match count. 140 | */ 141 | int _sf1_extract_glob(_sf1_task *task) 142 | { 143 | for (_sf1_task_arg *a = task->args; a != NULL; a = a->next) { 144 | if (a->is_glob) { 145 | int ret = glob(a->text, 0, NULL, &a->glob); 146 | // FIXME: do a bounds check. 147 | switch (ret) { 148 | case GLOB_NOSPACE: 149 | fprintf(stderr, "systemf: glob out of memory extracting: %s\n", a->text); 150 | return ENOMEM; 151 | case GLOB_ABORTED: 152 | fprintf(stderr, "systemf: glob aborted during extraction: %s\n", a->text); 153 | return EBADF; 154 | case GLOB_NOMATCH: 155 | fprintf(stderr, "systemf: no matches found: %s\n", a->text); 156 | return EINVAL; 157 | } 158 | } 159 | } 160 | return 0; 161 | } 162 | 163 | void _sf1_task_free(_sf1_task *task) 164 | { 165 | _sf1_task *next; 166 | for (; task != NULL; task = next) { 167 | _sf1_task_arg *anext; 168 | for (_sf1_task_arg *a = task->args; a != NULL; a = anext) { 169 | globfree(&a->glob); 170 | free(a->text); 171 | free(a->trusted_path); 172 | anext = a->next; 173 | free(a); 174 | } 175 | 176 | _sf1_redirect *rnext; 177 | for (_sf1_redirect *r = task->redirects; r != NULL; r = rnext) { 178 | free(r->text); 179 | free(r->trusted_path); 180 | rnext = r->next; 181 | free(r); 182 | } 183 | 184 | free(task->argv); 185 | next = task->next; 186 | free(task); 187 | } 188 | return; 189 | } 190 | 191 | /* 192 | * Fills in the files for the tasks. 193 | * 194 | * Returns -1 on failure and 0 on success. 195 | */ 196 | int _sf1_populate_task_files(_sf1_task *task, _sf1_task_files *files) { 197 | int pipefd[2]; 198 | _sf1_redirect *redirect; 199 | int rwrwrw = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; 200 | int prev_out_rd_pipe = files->out_rd_pipe; 201 | 202 | files->in = 0; 203 | files->out = 1; 204 | files->err = 2; 205 | files->out_rd_pipe = 0; 206 | 207 | for (redirect = task->redirects; redirect; redirect = redirect->next) { 208 | if (redirect->stream == _SF1_STDIN) { 209 | if (redirect->target == _SF1_FILE) { 210 | files->in = open(redirect->text, O_RDONLY); 211 | if (files->in < 0) { 212 | fprintf(stderr, "systemf: %s: %s\n", strerror(errno), redirect->text); 213 | return -1; 214 | } 215 | } else { // _SF1_PIPE 216 | files->in = prev_out_rd_pipe; 217 | } 218 | } else if (redirect->stream == _SF1_STDOUT) { 219 | if (redirect->target == _SF1_FILE) { 220 | files->out = open(redirect->text, O_WRONLY | O_CREAT | (redirect->append ? O_APPEND : O_TRUNC), rwrwrw); 221 | if (files->out < 0) { 222 | fprintf(stderr, "systemf: %s: %s\n", strerror(errno), redirect->text); 223 | return -1; 224 | } 225 | } else if (redirect->target == _SF1_SHARE) { 226 | files->out = files->err; 227 | } else { // _SF1_PIPE 228 | if (pipe(pipefd)) { 229 | fprintf(stderr, "systemf: %s opening a pipe\n", strerror(errno)); 230 | return -1; 231 | } 232 | files->out_rd_pipe = pipefd[0]; 233 | files->out = pipefd[1]; 234 | } 235 | } else { // _SF1_STDERR 236 | if (redirect->target == _SF1_FILE) { 237 | files->err = open(redirect->text, O_WRONLY | O_CREAT | (redirect->append ? O_APPEND : O_TRUNC), rwrwrw); 238 | if (files->err < 0) { 239 | fprintf(stderr, "systemf: %s: %s\n\n", strerror(errno), redirect->text); 240 | return -1; 241 | } 242 | } else if (redirect->target == _SF1_SHARE) { 243 | files->err = files->out; 244 | } 245 | } 246 | } 247 | return 0; 248 | } 249 | 250 | /* 251 | * Check all of the arguments for this task that the files are properly sandboxed. 252 | * On success, returns 0. On failure sets the errno and returns -1; 253 | */ 254 | int _sf1_file_sandbox_check_args(_sf1_task *task) { 255 | _sf1_task_arg *arg; 256 | int ret; 257 | 258 | for (arg = task->args; arg != NULL; arg = arg->next) { 259 | if (arg->trusted_path) { 260 | if (arg->is_glob) { 261 | int i; 262 | for (i = 0, ret = 0; (i < arg->glob.gl_pathc) && !ret; i++) { 263 | ret = _sf1_file_sandbox_check(arg->trusted_path, arg->glob.gl_pathv[i]); 264 | } 265 | } else { 266 | ret = _sf1_file_sandbox_check(arg->trusted_path, arg->text); 267 | } 268 | if (ret) { 269 | errno = ret; 270 | return -1; 271 | } 272 | } 273 | } 274 | return 0; 275 | } 276 | 277 | 278 | /* 279 | * Close the child in out and error if they aren't shared with the parent. 280 | */ 281 | void _sf1_close_child_files(_sf1_task_files *files) { 282 | if (files->in > 2) { 283 | close(files->in); 284 | files->in = 0; 285 | } 286 | if (files->out > 2) { 287 | close(files->out); 288 | files->out = 1; 289 | } 290 | if (files->err > 2) { 291 | close(files->err); 292 | files->err = 2; 293 | } 294 | } 295 | 296 | /* 297 | * Close the child in out, error, and pipe. 298 | */ 299 | void _sf1_close_child_files_and_pipe(_sf1_task_files *files) { 300 | if (files->out_rd_pipe) { 301 | close(files->out_rd_pipe); 302 | files->out_rd_pipe = 0; 303 | } 304 | _sf1_close_child_files(files); 305 | } 306 | 307 | int _sf1_tasks_run(_sf1_task *tasks) { 308 | pid_t pid; 309 | _sf1_pid_chain_t *pid_chain = NULL; 310 | int stat; 311 | char **argv; 312 | _sf1_task_arg *arg; 313 | size_t argc = 1; // 1 for terminating NULL 314 | int ret; 315 | int retval = -1; 316 | _sf1_task_files files = {.in=0, .out=1, .err=2, .out_rd_pipe=0}; 317 | 318 | if (!_sf1_redirects_are_sane(tasks)) { 319 | goto exit_error; 320 | } 321 | 322 | // We don't support tasks reuse, so argv MUST be null coming into this. 323 | assert(tasks->argv == NULL); 324 | 325 | for (_sf1_task *task = tasks; task; task = task->next) { 326 | ret = _sf1_extract_glob(task); 327 | if (ret) { 328 | errno = ret; 329 | goto exit_error; 330 | } 331 | 332 | // Count the arguments. 333 | for (arg = task->args; arg != NULL; arg = arg->next) { 334 | if (arg->is_glob) { 335 | argc += arg->glob.gl_pathc; 336 | } else { 337 | argc += 1; 338 | } 339 | } 340 | 341 | if (_sf1_file_sandbox_check_args(task)) { 342 | goto exit_error; 343 | } 344 | 345 | task->argv = malloc(argc * sizeof(char *)); 346 | // FIXME: handle NULL 347 | 348 | argv = task->argv; 349 | for (arg = task->args; arg != NULL; arg = arg->next) { 350 | if (arg->is_glob) { 351 | memcpy(argv, arg->glob.gl_pathv, sizeof(char *) * arg->glob.gl_pathc); 352 | argv += arg->glob.gl_pathc; 353 | } else { 354 | *argv = arg->text; 355 | argv++; 356 | } 357 | } 358 | *argv = NULL; 359 | DBG("_____________________ err exi exs sig tsig\n"); 360 | 361 | if (_sf1_populate_task_files(task, &files)) { 362 | goto exit_error; 363 | } 364 | 365 | // If we don't flush, both forks will send the buffered data and it will be seen twice. 366 | fflush(stdout); 367 | fflush(stderr); 368 | 369 | // FIXME: Determine if the file exists and it is executable before attempting 370 | // to fork which doesn't know how to handle results. 371 | 372 | pid = fork(); 373 | if (pid == 0) { 374 | dup2(files.in, 0); 375 | dup2(files.out, 1); 376 | dup2(files.err, 2); 377 | _sf1_close_upper_fd(); 378 | 379 | DBG("Running %s", task->argv[0]); 380 | stat = execv(*task->argv, task->argv); 381 | DBG("Execv returned with %3d %3d %3d %3d %3d\n", errno, 382 | WIFEXITED(stat), WEXITSTATUS(stat), WIFSIGNALED(stat), WTERMSIG(stat)); 383 | 384 | // If execv exits, we don't have an easy way to send the results back other 385 | // than some extra socket. Instead we should try catching all exceptions 386 | // (file not found, file not executable) before forking. Kill thyself. 387 | kill(getpid(), SIGKILL); 388 | } 389 | _sf1_close_child_files(&files); 390 | 391 | pid_chain = _sf1_pid_chain_add(pid_chain, pid); 392 | if (!pid_chain) { 393 | fprintf(stderr, "systemf: pid_chain out of memory"); 394 | goto exit_error; 395 | } 396 | 397 | // Only wait for completion if this is not piped. 398 | // I.E. "cat | grep" should run the grep before waiting for the cat to complete. 399 | if (files.out_rd_pipe == 0) { 400 | if (_sf1_pid_chain_waitpids(pid_chain, &stat, 0) == 0) { 401 | // FIXME: Make sure this is the right return value and better recover from this. 402 | fprintf(stderr, "waitpid unexpectedly returned %s", strerror(errno)); 403 | goto exit_error; 404 | } 405 | _sf1_pid_chain_clear(pid_chain); 406 | 407 | retval = WEXITSTATUS(stat); 408 | 409 | if (WIFSIGNALED(stat)) { 410 | fprintf(stderr, "waipid exited with signal %s\n", strsignal(WTERMSIG(stat))); 411 | goto exit_error; 412 | } 413 | 414 | DBG("waitpid returned with %3d %3d %3d %3d %3d\n", errno, 415 | WIFEXITED(stat), WEXITSTATUS(stat), WIFSIGNALED(stat), WTERMSIG(stat)); 416 | 417 | if (WIFSIGNALED(stat) || (WIFEXITED(stat) && WEXITSTATUS(stat))) { 418 | if (task->next && (task->next->run_if == _SF1_RUN_IF_PREV_SUCCEEDED)) { 419 | DBG("exiting because previous failed"); 420 | break; 421 | } 422 | } else { 423 | if (task->next && (task->next->run_if == _SF1_RUN_IF_PREV_FAILED)) { 424 | DBG("exiting because previous succeeded"); 425 | break; 426 | } 427 | } 428 | } 429 | } 430 | if (0) { 431 | exit_error: 432 | retval = -1; 433 | } 434 | 435 | // Clean up everything locally created. 436 | _sf1_close_child_files(&files); 437 | _sf1_pid_chain_free(pid_chain); 438 | 439 | return retval; 440 | } 441 | 442 | -------------------------------------------------------------------------------- /m4/ltoptions.m4: -------------------------------------------------------------------------------- 1 | # Helper functions for option handling. -*- Autoconf -*- 2 | # 3 | # Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software 4 | # Foundation, Inc. 5 | # Written by Gary V. Vaughan, 2004 6 | # 7 | # This file is free software; the Free Software Foundation gives 8 | # unlimited permission to copy and/or distribute it, with or without 9 | # modifications, as long as this notice is preserved. 10 | 11 | # serial 8 ltoptions.m4 12 | 13 | # This is to help aclocal find these macros, as it can't see m4_define. 14 | AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) 15 | 16 | 17 | # _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) 18 | # ------------------------------------------ 19 | m4_define([_LT_MANGLE_OPTION], 20 | [[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) 21 | 22 | 23 | # _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) 24 | # --------------------------------------- 25 | # Set option OPTION-NAME for macro MACRO-NAME, and if there is a 26 | # matching handler defined, dispatch to it. Other OPTION-NAMEs are 27 | # saved as a flag. 28 | m4_define([_LT_SET_OPTION], 29 | [m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl 30 | m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), 31 | _LT_MANGLE_DEFUN([$1], [$2]), 32 | [m4_warning([Unknown $1 option '$2'])])[]dnl 33 | ]) 34 | 35 | 36 | # _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) 37 | # ------------------------------------------------------------ 38 | # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. 39 | m4_define([_LT_IF_OPTION], 40 | [m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) 41 | 42 | 43 | # _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) 44 | # ------------------------------------------------------- 45 | # Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME 46 | # are set. 47 | m4_define([_LT_UNLESS_OPTIONS], 48 | [m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), 49 | [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), 50 | [m4_define([$0_found])])])[]dnl 51 | m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 52 | ])[]dnl 53 | ]) 54 | 55 | 56 | # _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) 57 | # ---------------------------------------- 58 | # OPTION-LIST is a space-separated list of Libtool options associated 59 | # with MACRO-NAME. If any OPTION has a matching handler declared with 60 | # LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about 61 | # the unknown option and exit. 62 | m4_defun([_LT_SET_OPTIONS], 63 | [# Set options 64 | m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), 65 | [_LT_SET_OPTION([$1], _LT_Option)]) 66 | 67 | m4_if([$1],[LT_INIT],[ 68 | dnl 69 | dnl Simply set some default values (i.e off) if boolean options were not 70 | dnl specified: 71 | _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no 72 | ]) 73 | _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no 74 | ]) 75 | dnl 76 | dnl If no reference was made to various pairs of opposing options, then 77 | dnl we run the default mode handler for the pair. For example, if neither 78 | dnl 'shared' nor 'disable-shared' was passed, we enable building of shared 79 | dnl archives by default: 80 | _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) 81 | _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) 82 | _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) 83 | _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], 84 | [_LT_ENABLE_FAST_INSTALL]) 85 | _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], 86 | [_LT_WITH_AIX_SONAME([aix])]) 87 | ]) 88 | ])# _LT_SET_OPTIONS 89 | 90 | 91 | ## --------------------------------- ## 92 | ## Macros to handle LT_INIT options. ## 93 | ## --------------------------------- ## 94 | 95 | # _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) 96 | # ----------------------------------------- 97 | m4_define([_LT_MANGLE_DEFUN], 98 | [[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) 99 | 100 | 101 | # LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) 102 | # ----------------------------------------------- 103 | m4_define([LT_OPTION_DEFINE], 104 | [m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl 105 | ])# LT_OPTION_DEFINE 106 | 107 | 108 | # dlopen 109 | # ------ 110 | LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes 111 | ]) 112 | 113 | AU_DEFUN([AC_LIBTOOL_DLOPEN], 114 | [_LT_SET_OPTION([LT_INIT], [dlopen]) 115 | AC_DIAGNOSE([obsolete], 116 | [$0: Remove this warning and the call to _LT_SET_OPTION when you 117 | put the 'dlopen' option into LT_INIT's first parameter.]) 118 | ]) 119 | 120 | dnl aclocal-1.4 backwards compatibility: 121 | dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) 122 | 123 | 124 | # win32-dll 125 | # --------- 126 | # Declare package support for building win32 dll's. 127 | LT_OPTION_DEFINE([LT_INIT], [win32-dll], 128 | [enable_win32_dll=yes 129 | 130 | case $host in 131 | *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) 132 | AC_CHECK_TOOL(AS, as, false) 133 | AC_CHECK_TOOL(DLLTOOL, dlltool, false) 134 | AC_CHECK_TOOL(OBJDUMP, objdump, false) 135 | ;; 136 | esac 137 | 138 | test -z "$AS" && AS=as 139 | _LT_DECL([], [AS], [1], [Assembler program])dnl 140 | 141 | test -z "$DLLTOOL" && DLLTOOL=dlltool 142 | _LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl 143 | 144 | test -z "$OBJDUMP" && OBJDUMP=objdump 145 | _LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl 146 | ])# win32-dll 147 | 148 | AU_DEFUN([AC_LIBTOOL_WIN32_DLL], 149 | [AC_REQUIRE([AC_CANONICAL_HOST])dnl 150 | _LT_SET_OPTION([LT_INIT], [win32-dll]) 151 | AC_DIAGNOSE([obsolete], 152 | [$0: Remove this warning and the call to _LT_SET_OPTION when you 153 | put the 'win32-dll' option into LT_INIT's first parameter.]) 154 | ]) 155 | 156 | dnl aclocal-1.4 backwards compatibility: 157 | dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) 158 | 159 | 160 | # _LT_ENABLE_SHARED([DEFAULT]) 161 | # ---------------------------- 162 | # implement the --enable-shared flag, and supports the 'shared' and 163 | # 'disable-shared' LT_INIT options. 164 | # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. 165 | m4_define([_LT_ENABLE_SHARED], 166 | [m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl 167 | AC_ARG_ENABLE([shared], 168 | [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], 169 | [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], 170 | [p=${PACKAGE-default} 171 | case $enableval in 172 | yes) enable_shared=yes ;; 173 | no) enable_shared=no ;; 174 | *) 175 | enable_shared=no 176 | # Look at the argument we got. We use all the common list separators. 177 | lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, 178 | for pkg in $enableval; do 179 | IFS=$lt_save_ifs 180 | if test "X$pkg" = "X$p"; then 181 | enable_shared=yes 182 | fi 183 | done 184 | IFS=$lt_save_ifs 185 | ;; 186 | esac], 187 | [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) 188 | 189 | _LT_DECL([build_libtool_libs], [enable_shared], [0], 190 | [Whether or not to build shared libraries]) 191 | ])# _LT_ENABLE_SHARED 192 | 193 | LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) 194 | LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) 195 | 196 | # Old names: 197 | AC_DEFUN([AC_ENABLE_SHARED], 198 | [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) 199 | ]) 200 | 201 | AC_DEFUN([AC_DISABLE_SHARED], 202 | [_LT_SET_OPTION([LT_INIT], [disable-shared]) 203 | ]) 204 | 205 | AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) 206 | AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) 207 | 208 | dnl aclocal-1.4 backwards compatibility: 209 | dnl AC_DEFUN([AM_ENABLE_SHARED], []) 210 | dnl AC_DEFUN([AM_DISABLE_SHARED], []) 211 | 212 | 213 | 214 | # _LT_ENABLE_STATIC([DEFAULT]) 215 | # ---------------------------- 216 | # implement the --enable-static flag, and support the 'static' and 217 | # 'disable-static' LT_INIT options. 218 | # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. 219 | m4_define([_LT_ENABLE_STATIC], 220 | [m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl 221 | AC_ARG_ENABLE([static], 222 | [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], 223 | [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], 224 | [p=${PACKAGE-default} 225 | case $enableval in 226 | yes) enable_static=yes ;; 227 | no) enable_static=no ;; 228 | *) 229 | enable_static=no 230 | # Look at the argument we got. We use all the common list separators. 231 | lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, 232 | for pkg in $enableval; do 233 | IFS=$lt_save_ifs 234 | if test "X$pkg" = "X$p"; then 235 | enable_static=yes 236 | fi 237 | done 238 | IFS=$lt_save_ifs 239 | ;; 240 | esac], 241 | [enable_static=]_LT_ENABLE_STATIC_DEFAULT) 242 | 243 | _LT_DECL([build_old_libs], [enable_static], [0], 244 | [Whether or not to build static libraries]) 245 | ])# _LT_ENABLE_STATIC 246 | 247 | LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) 248 | LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) 249 | 250 | # Old names: 251 | AC_DEFUN([AC_ENABLE_STATIC], 252 | [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) 253 | ]) 254 | 255 | AC_DEFUN([AC_DISABLE_STATIC], 256 | [_LT_SET_OPTION([LT_INIT], [disable-static]) 257 | ]) 258 | 259 | AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) 260 | AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) 261 | 262 | dnl aclocal-1.4 backwards compatibility: 263 | dnl AC_DEFUN([AM_ENABLE_STATIC], []) 264 | dnl AC_DEFUN([AM_DISABLE_STATIC], []) 265 | 266 | 267 | 268 | # _LT_ENABLE_FAST_INSTALL([DEFAULT]) 269 | # ---------------------------------- 270 | # implement the --enable-fast-install flag, and support the 'fast-install' 271 | # and 'disable-fast-install' LT_INIT options. 272 | # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. 273 | m4_define([_LT_ENABLE_FAST_INSTALL], 274 | [m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl 275 | AC_ARG_ENABLE([fast-install], 276 | [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], 277 | [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], 278 | [p=${PACKAGE-default} 279 | case $enableval in 280 | yes) enable_fast_install=yes ;; 281 | no) enable_fast_install=no ;; 282 | *) 283 | enable_fast_install=no 284 | # Look at the argument we got. We use all the common list separators. 285 | lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, 286 | for pkg in $enableval; do 287 | IFS=$lt_save_ifs 288 | if test "X$pkg" = "X$p"; then 289 | enable_fast_install=yes 290 | fi 291 | done 292 | IFS=$lt_save_ifs 293 | ;; 294 | esac], 295 | [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) 296 | 297 | _LT_DECL([fast_install], [enable_fast_install], [0], 298 | [Whether or not to optimize for fast installation])dnl 299 | ])# _LT_ENABLE_FAST_INSTALL 300 | 301 | LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) 302 | LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) 303 | 304 | # Old names: 305 | AU_DEFUN([AC_ENABLE_FAST_INSTALL], 306 | [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) 307 | AC_DIAGNOSE([obsolete], 308 | [$0: Remove this warning and the call to _LT_SET_OPTION when you put 309 | the 'fast-install' option into LT_INIT's first parameter.]) 310 | ]) 311 | 312 | AU_DEFUN([AC_DISABLE_FAST_INSTALL], 313 | [_LT_SET_OPTION([LT_INIT], [disable-fast-install]) 314 | AC_DIAGNOSE([obsolete], 315 | [$0: Remove this warning and the call to _LT_SET_OPTION when you put 316 | the 'disable-fast-install' option into LT_INIT's first parameter.]) 317 | ]) 318 | 319 | dnl aclocal-1.4 backwards compatibility: 320 | dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) 321 | dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) 322 | 323 | 324 | # _LT_WITH_AIX_SONAME([DEFAULT]) 325 | # ---------------------------------- 326 | # implement the --with-aix-soname flag, and support the `aix-soname=aix' 327 | # and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT 328 | # is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. 329 | m4_define([_LT_WITH_AIX_SONAME], 330 | [m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl 331 | shared_archive_member_spec= 332 | case $host,$enable_shared in 333 | power*-*-aix[[5-9]]*,yes) 334 | AC_MSG_CHECKING([which variant of shared library versioning to provide]) 335 | AC_ARG_WITH([aix-soname], 336 | [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], 337 | [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], 338 | [case $withval in 339 | aix|svr4|both) 340 | ;; 341 | *) 342 | AC_MSG_ERROR([Unknown argument to --with-aix-soname]) 343 | ;; 344 | esac 345 | lt_cv_with_aix_soname=$with_aix_soname], 346 | [AC_CACHE_VAL([lt_cv_with_aix_soname], 347 | [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) 348 | with_aix_soname=$lt_cv_with_aix_soname]) 349 | AC_MSG_RESULT([$with_aix_soname]) 350 | if test aix != "$with_aix_soname"; then 351 | # For the AIX way of multilib, we name the shared archive member 352 | # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', 353 | # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. 354 | # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, 355 | # the AIX toolchain works better with OBJECT_MODE set (default 32). 356 | if test 64 = "${OBJECT_MODE-32}"; then 357 | shared_archive_member_spec=shr_64 358 | else 359 | shared_archive_member_spec=shr 360 | fi 361 | fi 362 | ;; 363 | *) 364 | with_aix_soname=aix 365 | ;; 366 | esac 367 | 368 | _LT_DECL([], [shared_archive_member_spec], [0], 369 | [Shared archive member basename, for filename based shared library versioning on AIX])dnl 370 | ])# _LT_WITH_AIX_SONAME 371 | 372 | LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) 373 | LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) 374 | LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) 375 | 376 | 377 | # _LT_WITH_PIC([MODE]) 378 | # -------------------- 379 | # implement the --with-pic flag, and support the 'pic-only' and 'no-pic' 380 | # LT_INIT options. 381 | # MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. 382 | m4_define([_LT_WITH_PIC], 383 | [AC_ARG_WITH([pic], 384 | [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], 385 | [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], 386 | [lt_p=${PACKAGE-default} 387 | case $withval in 388 | yes|no) pic_mode=$withval ;; 389 | *) 390 | pic_mode=default 391 | # Look at the argument we got. We use all the common list separators. 392 | lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, 393 | for lt_pkg in $withval; do 394 | IFS=$lt_save_ifs 395 | if test "X$lt_pkg" = "X$lt_p"; then 396 | pic_mode=yes 397 | fi 398 | done 399 | IFS=$lt_save_ifs 400 | ;; 401 | esac], 402 | [pic_mode=m4_default([$1], [default])]) 403 | 404 | _LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl 405 | ])# _LT_WITH_PIC 406 | 407 | LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) 408 | LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) 409 | 410 | # Old name: 411 | AU_DEFUN([AC_LIBTOOL_PICMODE], 412 | [_LT_SET_OPTION([LT_INIT], [pic-only]) 413 | AC_DIAGNOSE([obsolete], 414 | [$0: Remove this warning and the call to _LT_SET_OPTION when you 415 | put the 'pic-only' option into LT_INIT's first parameter.]) 416 | ]) 417 | 418 | dnl aclocal-1.4 backwards compatibility: 419 | dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) 420 | 421 | ## ----------------- ## 422 | ## LTDL_INIT Options ## 423 | ## ----------------- ## 424 | 425 | m4_define([_LTDL_MODE], []) 426 | LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], 427 | [m4_define([_LTDL_MODE], [nonrecursive])]) 428 | LT_OPTION_DEFINE([LTDL_INIT], [recursive], 429 | [m4_define([_LTDL_MODE], [recursive])]) 430 | LT_OPTION_DEFINE([LTDL_INIT], [subproject], 431 | [m4_define([_LTDL_MODE], [subproject])]) 432 | 433 | m4_define([_LTDL_TYPE], []) 434 | LT_OPTION_DEFINE([LTDL_INIT], [installable], 435 | [m4_define([_LTDL_TYPE], [installable])]) 436 | LT_OPTION_DEFINE([LTDL_INIT], [convenience], 437 | [m4_define([_LTDL_TYPE], [convenience])]) 438 | -------------------------------------------------------------------------------- /src/derived-lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef _sf1_yyHEADER_H 2 | #define _sf1_yyHEADER_H 1 3 | #define _sf1_yyIN_HEADER 1 4 | 5 | #line 6 "src/derived-lexer.h" 6 | 7 | #line 8 "src/derived-lexer.h" 8 | 9 | #define YY_INT_ALIGNED short int 10 | 11 | /* A lexical scanner generated by flex */ 12 | 13 | #define FLEX_SCANNER 14 | #define YY_FLEX_MAJOR_VERSION 2 15 | #define YY_FLEX_MINOR_VERSION 6 16 | #define YY_FLEX_SUBMINOR_VERSION 4 17 | #if YY_FLEX_SUBMINOR_VERSION > 0 18 | #define FLEX_BETA 19 | #endif 20 | 21 | #ifdef yy_create_buffer 22 | #define _sf1_yy_create_buffer_ALREADY_DEFINED 23 | #else 24 | #define yy_create_buffer _sf1_yy_create_buffer 25 | #endif 26 | 27 | #ifdef yy_delete_buffer 28 | #define _sf1_yy_delete_buffer_ALREADY_DEFINED 29 | #else 30 | #define yy_delete_buffer _sf1_yy_delete_buffer 31 | #endif 32 | 33 | #ifdef yy_scan_buffer 34 | #define _sf1_yy_scan_buffer_ALREADY_DEFINED 35 | #else 36 | #define yy_scan_buffer _sf1_yy_scan_buffer 37 | #endif 38 | 39 | #ifdef yy_scan_string 40 | #define _sf1_yy_scan_string_ALREADY_DEFINED 41 | #else 42 | #define yy_scan_string _sf1_yy_scan_string 43 | #endif 44 | 45 | #ifdef yy_scan_bytes 46 | #define _sf1_yy_scan_bytes_ALREADY_DEFINED 47 | #else 48 | #define yy_scan_bytes _sf1_yy_scan_bytes 49 | #endif 50 | 51 | #ifdef yy_init_buffer 52 | #define _sf1_yy_init_buffer_ALREADY_DEFINED 53 | #else 54 | #define yy_init_buffer _sf1_yy_init_buffer 55 | #endif 56 | 57 | #ifdef yy_flush_buffer 58 | #define _sf1_yy_flush_buffer_ALREADY_DEFINED 59 | #else 60 | #define yy_flush_buffer _sf1_yy_flush_buffer 61 | #endif 62 | 63 | #ifdef yy_load_buffer_state 64 | #define _sf1_yy_load_buffer_state_ALREADY_DEFINED 65 | #else 66 | #define yy_load_buffer_state _sf1_yy_load_buffer_state 67 | #endif 68 | 69 | #ifdef yy_switch_to_buffer 70 | #define _sf1_yy_switch_to_buffer_ALREADY_DEFINED 71 | #else 72 | #define yy_switch_to_buffer _sf1_yy_switch_to_buffer 73 | #endif 74 | 75 | #ifdef yypush_buffer_state 76 | #define _sf1_yypush_buffer_state_ALREADY_DEFINED 77 | #else 78 | #define yypush_buffer_state _sf1_yypush_buffer_state 79 | #endif 80 | 81 | #ifdef yypop_buffer_state 82 | #define _sf1_yypop_buffer_state_ALREADY_DEFINED 83 | #else 84 | #define yypop_buffer_state _sf1_yypop_buffer_state 85 | #endif 86 | 87 | #ifdef yyensure_buffer_stack 88 | #define _sf1_yyensure_buffer_stack_ALREADY_DEFINED 89 | #else 90 | #define yyensure_buffer_stack _sf1_yyensure_buffer_stack 91 | #endif 92 | 93 | #ifdef yylex 94 | #define _sf1_yylex_ALREADY_DEFINED 95 | #else 96 | #define yylex _sf1_yylex 97 | #endif 98 | 99 | #ifdef yyrestart 100 | #define _sf1_yyrestart_ALREADY_DEFINED 101 | #else 102 | #define yyrestart _sf1_yyrestart 103 | #endif 104 | 105 | #ifdef yylex_init 106 | #define _sf1_yylex_init_ALREADY_DEFINED 107 | #else 108 | #define yylex_init _sf1_yylex_init 109 | #endif 110 | 111 | #ifdef yylex_init_extra 112 | #define _sf1_yylex_init_extra_ALREADY_DEFINED 113 | #else 114 | #define yylex_init_extra _sf1_yylex_init_extra 115 | #endif 116 | 117 | #ifdef yylex_destroy 118 | #define _sf1_yylex_destroy_ALREADY_DEFINED 119 | #else 120 | #define yylex_destroy _sf1_yylex_destroy 121 | #endif 122 | 123 | #ifdef yyget_debug 124 | #define _sf1_yyget_debug_ALREADY_DEFINED 125 | #else 126 | #define yyget_debug _sf1_yyget_debug 127 | #endif 128 | 129 | #ifdef yyset_debug 130 | #define _sf1_yyset_debug_ALREADY_DEFINED 131 | #else 132 | #define yyset_debug _sf1_yyset_debug 133 | #endif 134 | 135 | #ifdef yyget_extra 136 | #define _sf1_yyget_extra_ALREADY_DEFINED 137 | #else 138 | #define yyget_extra _sf1_yyget_extra 139 | #endif 140 | 141 | #ifdef yyset_extra 142 | #define _sf1_yyset_extra_ALREADY_DEFINED 143 | #else 144 | #define yyset_extra _sf1_yyset_extra 145 | #endif 146 | 147 | #ifdef yyget_in 148 | #define _sf1_yyget_in_ALREADY_DEFINED 149 | #else 150 | #define yyget_in _sf1_yyget_in 151 | #endif 152 | 153 | #ifdef yyset_in 154 | #define _sf1_yyset_in_ALREADY_DEFINED 155 | #else 156 | #define yyset_in _sf1_yyset_in 157 | #endif 158 | 159 | #ifdef yyget_out 160 | #define _sf1_yyget_out_ALREADY_DEFINED 161 | #else 162 | #define yyget_out _sf1_yyget_out 163 | #endif 164 | 165 | #ifdef yyset_out 166 | #define _sf1_yyset_out_ALREADY_DEFINED 167 | #else 168 | #define yyset_out _sf1_yyset_out 169 | #endif 170 | 171 | #ifdef yyget_leng 172 | #define _sf1_yyget_leng_ALREADY_DEFINED 173 | #else 174 | #define yyget_leng _sf1_yyget_leng 175 | #endif 176 | 177 | #ifdef yyget_text 178 | #define _sf1_yyget_text_ALREADY_DEFINED 179 | #else 180 | #define yyget_text _sf1_yyget_text 181 | #endif 182 | 183 | #ifdef yyget_lineno 184 | #define _sf1_yyget_lineno_ALREADY_DEFINED 185 | #else 186 | #define yyget_lineno _sf1_yyget_lineno 187 | #endif 188 | 189 | #ifdef yyset_lineno 190 | #define _sf1_yyset_lineno_ALREADY_DEFINED 191 | #else 192 | #define yyset_lineno _sf1_yyset_lineno 193 | #endif 194 | 195 | #ifdef yyget_column 196 | #define _sf1_yyget_column_ALREADY_DEFINED 197 | #else 198 | #define yyget_column _sf1_yyget_column 199 | #endif 200 | 201 | #ifdef yyset_column 202 | #define _sf1_yyset_column_ALREADY_DEFINED 203 | #else 204 | #define yyset_column _sf1_yyset_column 205 | #endif 206 | 207 | #ifdef yywrap 208 | #define _sf1_yywrap_ALREADY_DEFINED 209 | #else 210 | #define yywrap _sf1_yywrap 211 | #endif 212 | 213 | #ifdef yyget_lval 214 | #define _sf1_yyget_lval_ALREADY_DEFINED 215 | #else 216 | #define yyget_lval _sf1_yyget_lval 217 | #endif 218 | 219 | #ifdef yyset_lval 220 | #define _sf1_yyset_lval_ALREADY_DEFINED 221 | #else 222 | #define yyset_lval _sf1_yyset_lval 223 | #endif 224 | 225 | #ifdef yyalloc 226 | #define _sf1_yyalloc_ALREADY_DEFINED 227 | #else 228 | #define yyalloc _sf1_yyalloc 229 | #endif 230 | 231 | #ifdef yyrealloc 232 | #define _sf1_yyrealloc_ALREADY_DEFINED 233 | #else 234 | #define yyrealloc _sf1_yyrealloc 235 | #endif 236 | 237 | #ifdef yyfree 238 | #define _sf1_yyfree_ALREADY_DEFINED 239 | #else 240 | #define yyfree _sf1_yyfree 241 | #endif 242 | 243 | /* First, we deal with platform-specific or compiler-specific issues. */ 244 | 245 | /* begin standard C headers. */ 246 | #include 247 | #include 248 | #include 249 | #include 250 | 251 | /* end standard C headers. */ 252 | 253 | /* flex integer type definitions */ 254 | 255 | #ifndef FLEXINT_H 256 | #define FLEXINT_H 257 | 258 | /* C99 systems have . Non-C99 systems may or may not. */ 259 | 260 | #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L 261 | 262 | /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, 263 | * if you want the limit (max/min) macros for int types. 264 | */ 265 | #ifndef __STDC_LIMIT_MACROS 266 | #define __STDC_LIMIT_MACROS 1 267 | #endif 268 | 269 | #include 270 | typedef int8_t flex_int8_t; 271 | typedef uint8_t flex_uint8_t; 272 | typedef int16_t flex_int16_t; 273 | typedef uint16_t flex_uint16_t; 274 | typedef int32_t flex_int32_t; 275 | typedef uint32_t flex_uint32_t; 276 | #else 277 | typedef signed char flex_int8_t; 278 | typedef short int flex_int16_t; 279 | typedef int flex_int32_t; 280 | typedef unsigned char flex_uint8_t; 281 | typedef unsigned short int flex_uint16_t; 282 | typedef unsigned int flex_uint32_t; 283 | 284 | /* Limits of integral types. */ 285 | #ifndef INT8_MIN 286 | #define INT8_MIN (-128) 287 | #endif 288 | #ifndef INT16_MIN 289 | #define INT16_MIN (-32767-1) 290 | #endif 291 | #ifndef INT32_MIN 292 | #define INT32_MIN (-2147483647-1) 293 | #endif 294 | #ifndef INT8_MAX 295 | #define INT8_MAX (127) 296 | #endif 297 | #ifndef INT16_MAX 298 | #define INT16_MAX (32767) 299 | #endif 300 | #ifndef INT32_MAX 301 | #define INT32_MAX (2147483647) 302 | #endif 303 | #ifndef UINT8_MAX 304 | #define UINT8_MAX (255U) 305 | #endif 306 | #ifndef UINT16_MAX 307 | #define UINT16_MAX (65535U) 308 | #endif 309 | #ifndef UINT32_MAX 310 | #define UINT32_MAX (4294967295U) 311 | #endif 312 | 313 | #ifndef SIZE_MAX 314 | #define SIZE_MAX (~(size_t)0) 315 | #endif 316 | 317 | #endif /* ! C99 */ 318 | 319 | #endif /* ! FLEXINT_H */ 320 | 321 | /* begin standard C++ headers. */ 322 | 323 | /* TODO: this is always defined, so inline it */ 324 | #define yyconst const 325 | 326 | #if defined(__GNUC__) && __GNUC__ >= 3 327 | #define yynoreturn __attribute__((__noreturn__)) 328 | #else 329 | #define yynoreturn 330 | #endif 331 | 332 | /* An opaque pointer. */ 333 | #ifndef YY_TYPEDEF_YY_SCANNER_T 334 | #define YY_TYPEDEF_YY_SCANNER_T 335 | typedef void* yyscan_t; 336 | #endif 337 | 338 | /* For convenience, these vars (plus the bison vars far below) 339 | are macros in the reentrant scanner. */ 340 | #define yyin yyg->yyin_r 341 | #define yyout yyg->yyout_r 342 | #define yyextra yyg->yyextra_r 343 | #define yyleng yyg->yyleng_r 344 | #define yytext yyg->yytext_r 345 | #define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) 346 | #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) 347 | #define yy_flex_debug yyg->yy_flex_debug_r 348 | 349 | /* Size of default input buffer. */ 350 | #ifndef YY_BUF_SIZE 351 | #ifdef __ia64__ 352 | /* On IA-64, the buffer size is 16k, not 8k. 353 | * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. 354 | * Ditto for the __ia64__ case accordingly. 355 | */ 356 | #define YY_BUF_SIZE 32768 357 | #else 358 | #define YY_BUF_SIZE 16384 359 | #endif /* __ia64__ */ 360 | #endif 361 | 362 | #ifndef YY_TYPEDEF_YY_BUFFER_STATE 363 | #define YY_TYPEDEF_YY_BUFFER_STATE 364 | typedef struct yy_buffer_state *YY_BUFFER_STATE; 365 | #endif 366 | 367 | #ifndef YY_TYPEDEF_YY_SIZE_T 368 | #define YY_TYPEDEF_YY_SIZE_T 369 | typedef size_t yy_size_t; 370 | #endif 371 | 372 | #ifndef YY_STRUCT_YY_BUFFER_STATE 373 | #define YY_STRUCT_YY_BUFFER_STATE 374 | struct yy_buffer_state 375 | { 376 | FILE *yy_input_file; 377 | 378 | char *yy_ch_buf; /* input buffer */ 379 | char *yy_buf_pos; /* current position in input buffer */ 380 | 381 | /* Size of input buffer in bytes, not including room for EOB 382 | * characters. 383 | */ 384 | int yy_buf_size; 385 | 386 | /* Number of characters read into yy_ch_buf, not including EOB 387 | * characters. 388 | */ 389 | int yy_n_chars; 390 | 391 | /* Whether we "own" the buffer - i.e., we know we created it, 392 | * and can realloc() it to grow it, and should free() it to 393 | * delete it. 394 | */ 395 | int yy_is_our_buffer; 396 | 397 | /* Whether this is an "interactive" input source; if so, and 398 | * if we're using stdio for input, then we want to use getc() 399 | * instead of fread(), to make sure we stop fetching input after 400 | * each newline. 401 | */ 402 | int yy_is_interactive; 403 | 404 | /* Whether we're considered to be at the beginning of a line. 405 | * If so, '^' rules will be active on the next match, otherwise 406 | * not. 407 | */ 408 | int yy_at_bol; 409 | 410 | int yy_bs_lineno; /**< The line count. */ 411 | int yy_bs_column; /**< The column count. */ 412 | 413 | /* Whether to try to fill the input buffer when we reach the 414 | * end of it. 415 | */ 416 | int yy_fill_buffer; 417 | 418 | int yy_buffer_status; 419 | 420 | }; 421 | #endif /* !YY_STRUCT_YY_BUFFER_STATE */ 422 | 423 | void yyrestart ( FILE *input_file , yyscan_t yyscanner ); 424 | void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); 425 | YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); 426 | void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); 427 | void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); 428 | void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); 429 | void yypop_buffer_state ( yyscan_t yyscanner ); 430 | 431 | YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); 432 | YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); 433 | YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); 434 | 435 | void *yyalloc ( yy_size_t , yyscan_t yyscanner ); 436 | void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); 437 | void yyfree ( void * , yyscan_t yyscanner ); 438 | 439 | /* Begin user sect3 */ 440 | 441 | #define _sf1_yywrap(yyscanner) (/*CONSTCOND*/1) 442 | #define YY_SKIP_YYWRAP 443 | 444 | #define yytext_ptr yytext_r 445 | 446 | #ifdef YY_HEADER_EXPORT_START_CONDITIONS 447 | #define INITIAL 0 448 | 449 | #endif 450 | 451 | #ifndef YY_NO_UNISTD_H 452 | /* Special case for "unistd.h", since it is non-ANSI. We include it way 453 | * down here because we want the user's section 1 to have been scanned first. 454 | * The user has a chance to override it with an option. 455 | */ 456 | #include 457 | #endif 458 | 459 | #ifndef YY_EXTRA_TYPE 460 | #define YY_EXTRA_TYPE void * 461 | #endif 462 | 463 | int yylex_init (yyscan_t* scanner); 464 | 465 | int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); 466 | 467 | /* Accessor methods to globals. 468 | These are made visible to non-reentrant scanners for convenience. */ 469 | 470 | int yylex_destroy ( yyscan_t yyscanner ); 471 | 472 | int yyget_debug ( yyscan_t yyscanner ); 473 | 474 | void yyset_debug ( int debug_flag , yyscan_t yyscanner ); 475 | 476 | YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); 477 | 478 | void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); 479 | 480 | FILE *yyget_in ( yyscan_t yyscanner ); 481 | 482 | void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); 483 | 484 | FILE *yyget_out ( yyscan_t yyscanner ); 485 | 486 | void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); 487 | 488 | int yyget_leng ( yyscan_t yyscanner ); 489 | 490 | char *yyget_text ( yyscan_t yyscanner ); 491 | 492 | int yyget_lineno ( yyscan_t yyscanner ); 493 | 494 | void yyset_lineno ( int _line_number , yyscan_t yyscanner ); 495 | 496 | int yyget_column ( yyscan_t yyscanner ); 497 | 498 | void yyset_column ( int _column_no , yyscan_t yyscanner ); 499 | 500 | YYSTYPE * yyget_lval ( yyscan_t yyscanner ); 501 | 502 | void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); 503 | 504 | /* Macros after this point can all be overridden by user definitions in 505 | * section 1. 506 | */ 507 | 508 | #ifndef YY_SKIP_YYWRAP 509 | #ifdef __cplusplus 510 | extern "C" int yywrap ( yyscan_t yyscanner ); 511 | #else 512 | extern int yywrap ( yyscan_t yyscanner ); 513 | #endif 514 | #endif 515 | 516 | #ifndef yytext_ptr 517 | static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); 518 | #endif 519 | 520 | #ifdef YY_NEED_STRLEN 521 | static int yy_flex_strlen ( const char * , yyscan_t yyscanner); 522 | #endif 523 | 524 | #ifndef YY_NO_INPUT 525 | 526 | #endif 527 | 528 | /* Amount of stuff to slurp up with each read. */ 529 | #ifndef YY_READ_BUF_SIZE 530 | #ifdef __ia64__ 531 | /* On IA-64, the buffer size is 16k, not 8k */ 532 | #define YY_READ_BUF_SIZE 16384 533 | #else 534 | #define YY_READ_BUF_SIZE 8192 535 | #endif /* __ia64__ */ 536 | #endif 537 | 538 | /* Number of entries by which start-condition stack grows. */ 539 | #ifndef YY_START_STACK_INCR 540 | #define YY_START_STACK_INCR 25 541 | #endif 542 | 543 | /* Default declaration of generated scanner - a define so the user can 544 | * easily add parameters. 545 | */ 546 | #ifndef YY_DECL 547 | #define YY_DECL_IS_OURS 1 548 | 549 | extern int yylex \ 550 | (YYSTYPE * yylval_param , yyscan_t yyscanner); 551 | 552 | #define YY_DECL int yylex \ 553 | (YYSTYPE * yylval_param , yyscan_t yyscanner) 554 | #endif /* !YY_DECL */ 555 | 556 | /* yy_get_previous_state - get the state just before the EOB char was reached */ 557 | 558 | #undef YY_NEW_FILE 559 | #undef YY_FLUSH_BUFFER 560 | #undef yy_set_bol 561 | #undef yy_new_buffer 562 | #undef yy_set_interactive 563 | #undef YY_DO_BEFORE_ACTION 564 | 565 | #ifdef YY_DECL_IS_OURS 566 | #undef YY_DECL_IS_OURS 567 | #undef YY_DECL 568 | #endif 569 | 570 | #ifndef _sf1_yy_create_buffer_ALREADY_DEFINED 571 | #undef yy_create_buffer 572 | #endif 573 | #ifndef _sf1_yy_delete_buffer_ALREADY_DEFINED 574 | #undef yy_delete_buffer 575 | #endif 576 | #ifndef _sf1_yy_scan_buffer_ALREADY_DEFINED 577 | #undef yy_scan_buffer 578 | #endif 579 | #ifndef _sf1_yy_scan_string_ALREADY_DEFINED 580 | #undef yy_scan_string 581 | #endif 582 | #ifndef _sf1_yy_scan_bytes_ALREADY_DEFINED 583 | #undef yy_scan_bytes 584 | #endif 585 | #ifndef _sf1_yy_init_buffer_ALREADY_DEFINED 586 | #undef yy_init_buffer 587 | #endif 588 | #ifndef _sf1_yy_flush_buffer_ALREADY_DEFINED 589 | #undef yy_flush_buffer 590 | #endif 591 | #ifndef _sf1_yy_load_buffer_state_ALREADY_DEFINED 592 | #undef yy_load_buffer_state 593 | #endif 594 | #ifndef _sf1_yy_switch_to_buffer_ALREADY_DEFINED 595 | #undef yy_switch_to_buffer 596 | #endif 597 | #ifndef _sf1_yypush_buffer_state_ALREADY_DEFINED 598 | #undef yypush_buffer_state 599 | #endif 600 | #ifndef _sf1_yypop_buffer_state_ALREADY_DEFINED 601 | #undef yypop_buffer_state 602 | #endif 603 | #ifndef _sf1_yyensure_buffer_stack_ALREADY_DEFINED 604 | #undef yyensure_buffer_stack 605 | #endif 606 | #ifndef _sf1_yylex_ALREADY_DEFINED 607 | #undef yylex 608 | #endif 609 | #ifndef _sf1_yyrestart_ALREADY_DEFINED 610 | #undef yyrestart 611 | #endif 612 | #ifndef _sf1_yylex_init_ALREADY_DEFINED 613 | #undef yylex_init 614 | #endif 615 | #ifndef _sf1_yylex_init_extra_ALREADY_DEFINED 616 | #undef yylex_init_extra 617 | #endif 618 | #ifndef _sf1_yylex_destroy_ALREADY_DEFINED 619 | #undef yylex_destroy 620 | #endif 621 | #ifndef _sf1_yyget_debug_ALREADY_DEFINED 622 | #undef yyget_debug 623 | #endif 624 | #ifndef _sf1_yyset_debug_ALREADY_DEFINED 625 | #undef yyset_debug 626 | #endif 627 | #ifndef _sf1_yyget_extra_ALREADY_DEFINED 628 | #undef yyget_extra 629 | #endif 630 | #ifndef _sf1_yyset_extra_ALREADY_DEFINED 631 | #undef yyset_extra 632 | #endif 633 | #ifndef _sf1_yyget_in_ALREADY_DEFINED 634 | #undef yyget_in 635 | #endif 636 | #ifndef _sf1_yyset_in_ALREADY_DEFINED 637 | #undef yyset_in 638 | #endif 639 | #ifndef _sf1_yyget_out_ALREADY_DEFINED 640 | #undef yyget_out 641 | #endif 642 | #ifndef _sf1_yyset_out_ALREADY_DEFINED 643 | #undef yyset_out 644 | #endif 645 | #ifndef _sf1_yyget_leng_ALREADY_DEFINED 646 | #undef yyget_leng 647 | #endif 648 | #ifndef _sf1_yyget_text_ALREADY_DEFINED 649 | #undef yyget_text 650 | #endif 651 | #ifndef _sf1_yyget_lineno_ALREADY_DEFINED 652 | #undef yyget_lineno 653 | #endif 654 | #ifndef _sf1_yyset_lineno_ALREADY_DEFINED 655 | #undef yyset_lineno 656 | #endif 657 | #ifndef _sf1_yyget_column_ALREADY_DEFINED 658 | #undef yyget_column 659 | #endif 660 | #ifndef _sf1_yyset_column_ALREADY_DEFINED 661 | #undef yyset_column 662 | #endif 663 | #ifndef _sf1_yywrap_ALREADY_DEFINED 664 | #undef yywrap 665 | #endif 666 | #ifndef _sf1_yyget_lval_ALREADY_DEFINED 667 | #undef yyget_lval 668 | #endif 669 | #ifndef _sf1_yyset_lval_ALREADY_DEFINED 670 | #undef yyset_lval 671 | #endif 672 | #ifndef _sf1_yyget_lloc_ALREADY_DEFINED 673 | #undef yyget_lloc 674 | #endif 675 | #ifndef _sf1_yyset_lloc_ALREADY_DEFINED 676 | #undef yyset_lloc 677 | #endif 678 | #ifndef _sf1_yyalloc_ALREADY_DEFINED 679 | #undef yyalloc 680 | #endif 681 | #ifndef _sf1_yyrealloc_ALREADY_DEFINED 682 | #undef yyrealloc 683 | #endif 684 | #ifndef _sf1_yyfree_ALREADY_DEFINED 685 | #undef yyfree 686 | #endif 687 | #ifndef _sf1_yytext_ALREADY_DEFINED 688 | #undef yytext 689 | #endif 690 | #ifndef _sf1_yyleng_ALREADY_DEFINED 691 | #undef yyleng 692 | #endif 693 | #ifndef _sf1_yyin_ALREADY_DEFINED 694 | #undef yyin 695 | #endif 696 | #ifndef _sf1_yyout_ALREADY_DEFINED 697 | #undef yyout 698 | #endif 699 | #ifndef _sf1_yy_flex_debug_ALREADY_DEFINED 700 | #undef yy_flex_debug 701 | #endif 702 | #ifndef _sf1_yylineno_ALREADY_DEFINED 703 | #undef yylineno 704 | #endif 705 | #ifndef _sf1_yytables_fload_ALREADY_DEFINED 706 | #undef yytables_fload 707 | #endif 708 | #ifndef _sf1_yytables_destroy_ALREADY_DEFINED 709 | #undef yytables_destroy 710 | #endif 711 | #ifndef _sf1_yyTABLES_NAME_ALREADY_DEFINED 712 | #undef yyTABLES_NAME 713 | #endif 714 | 715 | #line 84 "src/lexer.l" 716 | 717 | 718 | #line 719 "src/derived-lexer.h" 719 | #undef _sf1_yyIN_HEADER 720 | #endif /* _sf1_yyHEADER_H */ 721 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![C/C++ CI](https://github.com/yonhan3/systemf/workflows/C/C++%20CI/badge.svg)](https://github.com/yonhan3/systemf/actions) 2 | | Please Note: Systemf() is 0.9 Beta | 3 | | ------------------------------------- | 4 | | Systemf() is at the maturity level that it can be tested by interested parties. See [Future Work](#future-work) below for more information as to what the MVP for version 1.0 would be. | 5 | 6 | # systemf 7 | Prepared statement support for the system command. 8 | 9 | 10 | ## synopsis 11 | 12 | #include 13 | 14 | int systemf1(const char *fmt, ...); 15 | 16 | ## Features 17 | 18 | 1. Calls directly to execv() instead of /bin/sh 19 | 2. Uses a format string to break arguments into parameters. 20 | 3. Uses printf like parameters to build the command. 21 | 4. Support for limited shell capabilities like piping, redirecting, and running multiple commands in one call. 22 | 5. File globbing support. 23 | 6. File sandboxing. 24 | 7. Output capture (still in development). 25 | 26 | ## Example, 27 | 28 | Consider a simple command that takes user input and calls system with it. Without systemf() you would have to do this: 29 | 30 | ``` 31 | int example_func(char *user_input) { 32 | char fmt[] = "/bin/mymagicfunc %s"; 33 | char *buf = malloc(sizeof(fmt) + strlen(user_input)); 34 | int result; 35 | sprintf(buf, fmt, user_input); 36 | 37 | if (buf == NULL) { 38 | return -1; 39 | } 40 | result = system(buf); 41 | free(buf); 42 | return result; 43 | } 44 | ``` 45 | With systemf, all you would have to do is this: 46 | ``` 47 | int example_func(char *user_input) { 48 | return systemf1("/bin/mymagicfunc %s", user_input); 49 | } 50 | ``` 51 | 52 | But that isn't the reason systemf() was created (but it is a great advantage). 53 | There is a big security advantage. With `systemf()`, 54 | user_input is sent as a single argument and since `systemf()` has its own limit 55 | parsing capabilities, `/bin/sh` is not involved at all. So if they did something 56 | like, `user_input = "goodbye ; rm -rf /"`, the first example would try to execute 57 | the `rm -rf /` while the second would just send the whole string as a single 58 | argument to /bin/mymagicfunc. 59 | 60 | This doesn't solve everything. If `/bin/mymagicfunc` had an injection issue, it might still cause the code to be run, but you can't prevent everything. 61 | 62 | ## Quick Tour of Through Examples 63 | 64 | The easiest way to explain the basics how `systemf1()` works is through a few examples. 65 | 66 | **Example 1: Basics** 67 | 68 | * `systemf1("/bin/echo The cat%s %d tail%s.", "'', tails, tails == 1 ? "" : "s");` 69 | 70 | In the above example, `systemf1()` takes the format input, breaks it into parameters 71 | by the spaces in each command, and sends it to execv. For example if `tails` 72 | were 2, it would call execv with these arguments: 73 | `["/bin/echo", "The", "cat's", "2", "tails."]`. Also, note that `systemf1()` 74 | doesn't support `'` in the format string. This is becuase `systemf1()` supports 75 | no quoting or escaping. 76 | 77 | **Example 2: Parameter Splitting** 78 | 79 | Now take the following call to `systemf1()` into account: 80 | 81 | `systemf1("/bin/echo %s", "this line has spaces");` 82 | 83 | `systemf1()` only breaks lines into parameters by spaces and glob expansion. So in the above case, the arguments to execv will be: `["/bin/echo", "this line has spaces"]` and it would **not** break the %s into spaces. 84 | 85 | **Example 3: File Globbing** 86 | 87 | `systemf1()` also supports file globbing in the format string and as a glob paramerter, but not as a string. Consider these three variations: 88 | 89 | 1. `systemf1("/bin/echo *.c");` 90 | 2. `systemf1("/bin/echo %*p", "*.c");` 91 | 3. `systemf1("/bin/echo %s", "*.c");` 92 | 93 | The first two will find every `c` file in the current directory and pass those as individual parameters to execv. `["/bin/echo", "a.c", "b.c", "c.c"]`. While the third will send the text in verbatim: `["/bin/echo", "*.c"]` and since `echo` does not do glob expansion, literally `*.c` will be printed. 94 | 95 | There are a caveats to the above. If the glob pattern matches nothing, the 96 | processing will stop, an error message will be printed, and `-1` will be returned. 97 | 98 | Also, note that `systemf1()` supports filename sandboxing. That is a more advanced 99 | subject than this introduction. For more information see [Filename Sandboxing](#filename-sandboxing) below. 100 | 101 | 102 | 103 | 104 | ## Format String and Argument Parsing 105 | 106 | The `fmt` argument to `systemf1()` specifies the how the code will be called and 107 | allows a convenient way to bring in parameterized user input. Think of it as 108 | limited shell with most of what you would need when calling out to system, but 109 | protections from the common mistakes when calling system. The below table 110 | summarizes which characters are allowed in the format string and their meanings. 111 | 112 | | Token | Meaning | 113 | |:------------:| ------- | 114 | | `a-z` `A-Z` ` 0-9` `.` `-` | Nonspecial characters allowed in `fmt`. (0) | 115 | | *space tab* | *Spaces* and *tabs* are interpreted as parameter separators. | 116 | | `%s` | Replace this with the string in the next available argument. (5) | 117 | | `%d` | Replace this with the integer in the next available argument. (5) | 118 | | `%p` | Like `%s`, but also [filename sandboxed](#filename-sandboxing) | 119 | | `%*p` | Interpret the supplied parameter as a file glob. | 120 | | `%!p` | Like `%s`, but a trusted parameter for [filename sandboxing](#filename-sandboxing) | 121 | | `;` | Command separator run if previous command exits cleanly. | 122 | | `|` | Command separator like `;` but also pipes stdout from prev into stdin | 123 | | `&&` | Command separator run if previous command exits cleanly with zero status. | 124 | | `||` | Command separator run if previous command exits cleanly with nonzero status. | 125 | | `<`*file* | Supply the stdin from the specified *file*. (1)(2) | 126 | | `>`*file* | Redirect the stdout into the specified *file*. (1)(2) | 127 | | `>>`*file* | Append the stdout into the specified *file*. (1)(3) | 128 | | `2>`*file* | Redirect the stderr into the specified *file*. (1)(2) | 129 | | `2>>`*file* | Append the stderr into the specified *file*. (1)(3) | 130 | | `>&2` | Redirect the stdout into the stderr. (4) | 131 | | `2>&1` | Redirect stderr into stdout. (4) | 132 | | `&>`*file* | Redirect stderr and stdout into the specified *file*. (1)(2) | 133 | | `&>>`*file* | Append stderr and stdout into the specified *file*. (1)(2) | 134 | 135 | - (0) All tokens below in the table take precedence during parsing. 136 | - (1) There is an optional space between the redirect and the filename. 137 | - (2) Replace the file if it exists. 138 | - (3) Create the file if it does not exist. 139 | - (4) `systemf1()` currently has no support of swapping the stdout and stderr. 140 | - (5) Currently, no formatting specifiers are supported (like `%5d` or `%-10s`) 141 | 142 | ### File Sandboxing 143 | 144 | `systemf` has a non-obtrusive filename sandboxing system. Before each process is executed, all arguments for that command are run through the following steps. 145 | 146 | 1. Determine which arguments are known file references with untrusted data. 147 | 2. Determine the trusted part of that argument's path. 148 | 3. Verify that the final paths are contained in the trusted path. 149 | 150 | #### 1. Determine which arguments are file references with untrusted data. 151 | 152 | Systemf looks at each argument before the commands is launched and determines 153 | if they are candidates for filename sandboxing. An argument is a candidate if it contains any of the following: 154 | 155 | 1. Wildcards in the format string. (`[]`, `*`, `?`) 156 | 2. Any `%p` parameter. (`%p`, `%!p`, `%*p`) 157 | 158 | The target must also include untrusted data. That can be any of `%d`, `%s`, `%p`, `%*p`. `%!p`, on the other hand, is always trusted. 159 | 160 | #### 2. Determine the trusted path of that argument. 161 | 162 | If an argument is deemed a file reference, it is next scanned up for the longest 163 | trusted path not including wildcards. A path is trusted if it is a part of the 164 | `fmt` string, or if it is a %!p. This is best shown through examples. 165 | 166 | | # | Command | Trusted Path | 167 | | - | ------- | ------------ | 168 | | 1 | `systemf1("cmd ./a/b/c*/%s", "untrusted");` | `./a/b/` | 169 | | 2 | `systemf1("cmd ./a/b/c%s/*", "untrusted");` | `./a/b/` | 170 | | 3 | `systemf1("cmd %p/x/y", "untrusted");` | `./` | 171 | | 4 | `systemf1("cmd %!p/x/y", "trusted");` | *NA* | 172 | | 5 | `systemf1("cmd %s/x/y/%!p", "untrusted", "trusted");` | `./` | 173 | | 6 | `systemf1("cmd %s/x/y", "untrusted");` | *NA* | 174 | | 7 | `systemf1("./a/%s %s/x/y", "untrusted");` | `./a/` | 175 | 176 | 1. Since the path at `/c*` contains a wildcard it is not included in the trusted path. 177 | 2. Since `/c%s` contains untrusted data, it is not included in the path. 178 | 3. Since `%p` is untrusted and at the very beginning, the current directory is assumed. 179 | 4. Since `%!p` is trusted, this contains no untrusted data and is not sandboxed. 180 | 5. Even though there is a `%!p` at the end, the untrusted `%s` at the beginning forces a trusted path to the current directory. 181 | 6. Since there are no wildcards or `%p` variants, this is not considered a path even if it probably is. 182 | 7. This demonstrates sandboxing also happens on the command itself. The command must live in a subdirectory of `a`. 183 | 184 | #### 3. Verify that the final paths are contained in the trusted path. 185 | 186 | The final step is to verify that all generated paths are contained in the trusted path. This is done by collapsing all `..` patterns in the paths and verfying that 187 | the trusted path and the beginning of each path matches. 188 | 189 | Consider the following code: 190 | ``` 191 | systemf1("mkdir -p a/b/c/%p", "../../b/c/f"); 192 | ``` 193 | the underlying `systemf` code will have to decide if `a/b/c/../../b/c/f` exists in the trusted path, `a/b/c/`. I collapses the latter to `a/b/c/f` and determines that it matches the trusted path. 194 | 195 | There were some consideration of preventing symbolic links from causing an escape of the sandbox, but ultimately the confusion added by such a change was greater than the security benefilts. See [No Plan for Chroot Jail Equivalence for Filename Sandboxing](#no-plan-for-chroot-jail-equivalence-for-filename-sandboxing) for more details. 196 | 197 | 198 | ## Return Values 199 | 200 | The base systemf1() will have the same return values as the system() function. 201 | 202 | The following is copied from http://man7.org/linux/man-pages/man3/system.3.html : 203 | 204 | * If any child process could not be created, or its status could not 205 | be retrieved, the return value is -1 and errno is set to indicate 206 | the error. 207 | 208 | * If all spawned child processes succeed, then the return value is the 209 | termination status of the last spawned child process. 210 | 211 | ## Why is There a "1" in the Systemf1 Name? 212 | 213 | The driving force behind developing `systemf` was to have a more secure system. It seems that as a tool becomes more popular, it gets more security scrutiny. The developers of `systemf` expect that they will have missed something fundamental that will require a non-backward compatible change to the code. When that day comes, they will have to choose between breaking code and creating new functions. 214 | 215 | By making the systemf versioned in its name, it is more obvious the version that is being run and gives for a more graceful transition. Perhaps one day, when the implementation is considered rock-solid, the final version can drop the numbering system altogether. (Any bets how long it will take to detect a new fatal security vulnerability in the design after that step is taken?) 216 | 217 | ## Future Work 218 | 219 | Systemf is now a minimal viable product with the release of 1.0. The following feature enhancement 220 | are planned for future minor version releases. 221 | 222 | | Title | Description | 223 | | ----- | ----------- | 224 | | [PATH Support](#path-support) | Currently all executables must include a path. This will add limited path searching and updating. | 225 | | [Capture Support](#capture-support) | Functions that allow for capturing the standard output and standard error to strings. | 226 | | [STDIN String & File Support](#stdin-string-and-file-support) | Functions that allow for a string or buffers to be suppled for the standard input. | 227 | | [Error Message Redirection](#error-message-redirection) | Redirect stderr messages from `systemf` itself. | 228 | 229 | ### Features not currently planned. 230 | 231 | These features require more discussion and some highly needed use cases to be added. 232 | 233 | | Title | Description | 234 | | ----- | ----------- | 235 | | [Background Support](#no-plan-for-background-support) | Running commands in the background. | 236 | | [variables](#no-plan-for-variable-support) | Variable expansion like $HOME or ~ may not be supported. | 237 | | [variable cleaning](#no-plan-for-variable-cleaning) | Other than PATH, no other environment variables will be reset (like IFS). | 238 | | [chroot equivalence](#no-plan-for-chroot-jail-equivalence-for-filename-sandboxing) | No plan for chroot jail equivalence for filename sandboxing 239 | 240 | 241 | 242 | ### PATH Support 243 | **Still being developed.** 244 | 245 | `Systemf` protects against [CWE-426: Untrusted Search Path](https://cwe.mitre.org/data/definitions/426.html) by ignoring the PATH environment variable and trusting a limit path. For systems that supply it, `confstr(_CS_PATH, ...)` will be used. For other systems, the `./configure` will need to determine this. 246 | 247 | Each executable will have the ability to augment this path for that executable with the command `systemf1_update_path(path, location)` where `path` is a colon separated list of directories and location can be one of `SYSTEMF1_PATH_PREPEND`, `SYSTEMF1_PATH_APPEND`, and `SYSTEMF1_PATH_REPLACE`. 248 | Systemf by default only allows absolute paths and a very limited PATH parsing. 249 | 250 | ### Capture Support 251 | **Still being developed.** 252 | 253 | `Systemf` will support capturing the standard output to strings. These strings may either be supplied or allocated. There are two base commands for this: 254 | 255 | ``` 256 | systemf1_capture_rtn systemf1_capture( 257 | char *stdout_buf, 258 | size_t max_stdout_buf_len, 259 | char *stderr_buf, 260 | size_t max_stderr_buf_len, 261 | fmt, 262 | ...); 263 | 264 | systemf1_capture_rtn *systemf1_capture_a( 265 | size_t max_stdout_buf_len, 266 | size_t max_stderr_buf_len, 267 | fmt, 268 | ...); 269 | ``` 270 | 271 | `systemf1_capture_rtn` contains information about the captured results. For the former, it will be passed back by value and the latter, it will be allocated and returned. A single `free()` of the latter will be required because the captured data will be opaquely appended to the structure. The structure will contain the following fields: 272 | 273 | | Field | Description | 274 | | ----- | ----------- | 275 | | stdout | A buffer pointer containing the standard output. `out_buf[out_buf_len] will always contain the nul terminator. | 276 | | stdout_len | The number of characters written to out_buf excluding the nul terminator. This will never be greater than max_out_buf_len - 1 | 277 | | stdout_total | The number of total bytes received from the stdout if `max_stdout_buf_len` were infinite. | 278 | | stderr | Similar to `stdout` but for stderr | 279 | | stderr_len | Similar to `stdout_len` but for `stderr` | 280 | | stderr_total | Similar to `stdout_total` but for `stderr` | 281 | | retval | The same return value as `systemf1()` would normally return. | 282 | 283 | There are some corner cases for `systemf1_capture()`. 284 | * If `stdout_buf` is NULL, `max_stdout_buf_len` will be ignored. The returned stdout will be NULL and `stdout_len` will be zero, but `stdout_total` will be accurate. 285 | * If `max_stdout_buf_len` is 0, the code will act as if `stdout_buf` were NULL. 286 | * The same corner cases exist for `stderr_buf` and `max_stderr_buf_len`. 287 | 288 | There are some corner cases for `systemf1_capture_a()`. 289 | * A `max_stdout_buf_len` of 0 considered to be equivalent to a length of 1. A one byte buffer will be allocated and returned filled with a nul value. Infinite buffer size is not supported. 290 | * The same corner cases exist for `max_stderr_buf_len`. 291 | 292 | ### Stdin String and File Support 293 | **Still being developed.** 294 | 295 | Varients of the systemf1 suite will take either a string, a buffer, or a FILE pointer and use that as the standard input. These variants will take one of the above as their first argument and in the case of the buffer, a length argument. 296 | This will create a wide varienty of new functions: 297 | 298 | ``` 299 | systemf1_sin(char *string, ...); 300 | systemf1_bin(char *buf, buflen,...); 301 | systemf1_fin(FILE *file, ...); 302 | 303 | systemf1_sin_capture(char *string, ...); 304 | systemf1_bin_capture(char *buf, buflen, ...); 305 | systemf1_fin_capture1(FILE *file, ...); 306 | 307 | systemf1_sin_capture_a(char *string, ...); 308 | systemf1_bin_capture_a(char *buf, buflen, ...); 309 | systemf1_fin_capture_a(FILE *file, ...); 310 | ``` 311 | 312 | ### Error Message Redirection 313 | **Still being developed.** 314 | 315 | `systemf` will print error messages to the standard error in some situations. These include invalid format strings, sandboxing violations, commands not found, and file globbing problems. Global setting command `systemf1_log_to(FILE *file)` will be added. It will return the current log. Supplying `file=NULL` completely disables logging. This does not affect the normal stderr and stdout processing of the commands themselves. 316 | 317 | ### No Plan for Background Support 318 | 319 | The standard shell contains facilities to run commands in the background with the 320 | `&` parameter. The main reason this is not currently planned is that a much better understanding of how the shell handles the processing in needed. This support is likely to be the most requested of all the current "No Plan" items. 321 | 322 | ### No Plan for Variable Support 323 | 324 | `Systemf` does not support variable expansion in the format string. Thus, 325 | `systemf1("/bin/echo $HOME")` will cause a parse error because `$` is not supported 326 | in the format string. There were two reasons not to support this. The first was 327 | that it simple added complexity to the system when the developers were uncertain 328 | how needed such a capability was. The second was that if such a capability is 329 | added, all security ramification should be considered and discussed first. 330 | 331 | For now, there is no variable support. In the future, there may be. 332 | 333 | ### No Plan for Variable Cleaning 334 | 335 | The SEI CERT C Coding Standard includes [ENV03-C Sanitize the environment when invoking external programs](https://wiki.sei.cmu.edu/confluence/display/c/ENV03-C.+Sanitize+the+environment+when+invoking+external+programs) and its examples include such items as resetting environment variables such as the PATH. Because it is such a common vulnerability, `Systemf` does sanitize the PATH. But because the library isn't able to guess every variation of needs of environment variables, it does not sanitize other variables. 336 | 337 | ### No Plan for Chroot Jail Equivalence for Filename Sandboxing 338 | 339 | Filename sandboxing is a feature in `systemf` that prevents user input from escaping outside of the current file path. It works well for the most basic operations, but it lacks the protections that a chroot jail might have. As an example, consider a chroot located in `/jail/`. Unless mounted within the jail, the path `/somewhere-else/` is not accessable. 340 | 341 | If someone instead simply launched code with `systemf` in it from `/newpath/` and 342 | in this contrived example, there was a `/newpath/escape/` which was a symbolic link 343 | to `/somewhere-else/` (`cd /newpath; ln -s /somewhere-else/ escape`) a call to 344 | `systemf1("./%p", "escape/bin/bam");`, the symbolic link would be followed and 345 | the executable in `/somewhere-else/bin/bam` could be run. 346 | 347 | In the chroot jail case, the symbolic link couldn't be followed because it would be considered to point to the external path: `/newpath/somewhere-else` that does not 348 | exist. 349 | 350 | It was considered that to get around this with filename sandboxing, the realpaths of 351 | the trusted path and the full path could be compared. It was decided that this 352 | would add too much complexity, be hard to explain, confuse the users, and prevent real needs to follow symbolic links. For these reasons, this approach for such 353 | a corner case was abandoned. 354 | 355 | 356 | ## Building 357 | 358 | To build, run `./configure && make build`. 359 | 360 | If you are a developer of `systemf`, see the [developer instructions](DEVELOP.md). 361 | 362 | ## Issues and feature requests. 363 | 364 | Systemf is currently in a [temporary location](https://github.com/yonhan3/systemf). 365 | Issues may be raised [there](https://github.com/yonhan3/systemf/issues), but may 366 | not get transferred to its [permanant home](https://github.com/cisco/systemf). 367 | --------------------------------------------------------------------------------